Procházet zdrojové kódy

Initial Code v0.1.0

jomjol před 6 roky
rodič
revize
4fe26dc0d8
100 změnil soubory, kde provedl 38718 přidání a 0 odebrání
  1. 5 0
      .gitignore
  2. 5 0
      code/.gitignore
  3. 8 0
      code/CMakeLists.txt
  4. 39 0
      code/include/README
  5. 46 0
      code/lib/README
  6. 157 0
      code/lib/connect_wlan/Helper.cpp
  7. 22 0
      code/lib/connect_wlan/Helper.h
  8. 162 0
      code/lib/connect_wlan/connect_wlan.cpp
  9. 13 0
      code/lib/connect_wlan/connect_wlan.h
  10. 128 0
      code/lib/conversions/esp_jpg_decode.c
  11. 43 0
      code/lib/conversions/esp_jpg_decode.h
  12. 126 0
      code/lib/conversions/img_converters.h
  13. 723 0
      code/lib/conversions/jpge.cpp
  14. 142 0
      code/lib/conversions/jpge.h
  15. 315 0
      code/lib/conversions/to_bmp.c
  16. 242 0
      code/lib/conversions/to_jpg.cpp
  17. 298 0
      code/lib/conversions/yuv.c
  18. 29 0
      code/lib/conversions/yuv.h
  19. 1520 0
      code/lib/driver/camera.c
  20. 49 0
      code/lib/driver/camera_common.h
  21. 200 0
      code/lib/driver/esp_camera.h
  22. 260 0
      code/lib/driver/sccb.c
  23. 18 0
      code/lib/driver/sccb.h
  24. 28 0
      code/lib/driver/sensor.c
  25. 191 0
      code/lib/driver/sensor.h
  26. 432 0
      code/lib/driver/twi.c
  27. 38 0
      code/lib/driver/twi.h
  28. 64 0
      code/lib/driver/xclk.c
  29. 7 0
      code/lib/driver/xclk.h
  30. 254 0
      code/lib/jomjol_controlcamera/ClassControllCamera.cpp
  31. 46 0
      code/lib/jomjol_controlcamera/ClassControllCamera.h
  32. 97 0
      code/lib/jomjol_controlcamera/camera_define.h
  33. 200 0
      code/lib/jomjol_controlcamera/esp_camera.h
  34. 126 0
      code/lib/jomjol_controlcamera/img_converters.h
  35. 191 0
      code/lib/jomjol_controlcamera/sensor.h
  36. 175 0
      code/lib/jomjol_controlcamera/server_camera.cpp
  37. 14 0
      code/lib/jomjol_controlcamera/server_camera.h
  38. 539 0
      code/lib/jomjol_fileserver_ota/server_file.cpp
  39. 3 0
      code/lib/jomjol_fileserver_ota/server_file.h
  40. 408 0
      code/lib/jomjol_fileserver_ota/server_ota.cpp
  41. 10 0
      code/lib/jomjol_fileserver_ota/server_ota.h
  42. 110 0
      code/lib/jomjol_flowcontroll/ClassFlow.cpp
  43. 40 0
      code/lib/jomjol_flowcontroll/ClassFlow.h
  44. 108 0
      code/lib/jomjol_flowcontroll/ClassFlowAlignment.cpp
  45. 28 0
      code/lib/jomjol_flowcontroll/ClassFlowAlignment.h
  46. 257 0
      code/lib/jomjol_flowcontroll/ClassFlowAnalog.cpp
  47. 36 0
      code/lib/jomjol_flowcontroll/ClassFlowAnalog.h
  48. 212 0
      code/lib/jomjol_flowcontroll/ClassFlowControll.cpp
  49. 41 0
      code/lib/jomjol_flowcontroll/ClassFlowControll.h
  50. 210 0
      code/lib/jomjol_flowcontroll/ClassFlowDigit.cpp
  51. 37 0
      code/lib/jomjol_flowcontroll/ClassFlowDigit.h
  52. 154 0
      code/lib/jomjol_flowcontroll/ClassFlowMakeImage.cpp
  53. 41 0
      code/lib/jomjol_flowcontroll/ClassFlowMakeImage.h
  54. 302 0
      code/lib/jomjol_flowcontroll/ClassFlowPostProcessing.cpp
  55. 42 0
      code/lib/jomjol_flowcontroll/ClassFlowPostProcessing.h
  56. 157 0
      code/lib/jomjol_flowcontroll/Helper.cpp
  57. 22 0
      code/lib/jomjol_flowcontroll/Helper.h
  58. 101 0
      code/lib/jomjol_flowcontroll/camera_define.h
  59. 87 0
      code/lib/jomjol_image_proc/CFindTemplate._h_
  60. 366 0
      code/lib/jomjol_image_proc/CFindTemplate.cp__p
  61. 496 0
      code/lib/jomjol_image_proc/CFindTemplate.cpp
  62. 95 0
      code/lib/jomjol_image_proc/CFindTemplate.h
  63. 157 0
      code/lib/jomjol_image_proc/Helper.cpp
  64. 22 0
      code/lib/jomjol_image_proc/Helper.h
  65. 5002 0
      code/lib/jomjol_image_proc/bitmap_image.hpp
  66. 11 0
      code/lib/jomjol_image_proc/make_stb.cpp
  67. 7657 0
      code/lib/jomjol_image_proc/stb_image.h
  68. 2598 0
      code/lib/jomjol_image_proc/stb_image_resize.h
  69. 1671 0
      code/lib/jomjol_image_proc/stb_image_write.h
  70. 41 0
      code/lib/jomjol_logfile/ClassLogFile.cpp
  71. 17 0
      code/lib/jomjol_logfile/ClassLogFile.h
  72. 290 0
      code/lib/jomjol_tfliteclass/CTfLiteClass.cpp
  73. 72 0
      code/lib/jomjol_tfliteclass/CTfLiteClass.h
  74. 137 0
      code/lib/jomjol_time_sntp/time_sntp.cpp
  75. 20 0
      code/lib/jomjol_time_sntp/time_sntp.h
  76. 580 0
      code/lib/sensors/ov2640.c
  77. 13 0
      code/lib/sensors/ov2640.h
  78. 216 0
      code/lib/sensors/ov2640_regs.h
  79. 485 0
      code/lib/sensors/ov2640_settings.h
  80. 1033 0
      code/lib/sensors/ov3660.c
  81. 16 0
      code/lib/sensors/ov3660.h
  82. 211 0
      code/lib/sensors/ov3660_regs.h
  83. 318 0
      code/lib/sensors/ov3660_settings.h
  84. 1105 0
      code/lib/sensors/ov5640.c
  85. 9 0
      code/lib/sensors/ov5640.h
  86. 213 0
      code/lib/sensors/ov5640_regs.h
  87. 334 0
      code/lib/sensors/ov5640_settings.h
  88. 557 0
      code/lib/sensors/ov7725.c
  89. 14 0
      code/lib/sensors/ov7725.h
  90. 335 0
      code/lib/sensors/ov7725_regs.h
  91. 25 0
      code/lib/tfmicro/CMakeLists.txt
  92. 900 0
      code/lib/tfmicro/fixedpoint/fixedpoint.h
  93. 384 0
      code/lib/tfmicro/fixedpoint/fixedpoint_sse.h
  94. 398 0
      code/lib/tfmicro/flatbuffers/base.h
  95. 2783 0
      code/lib/tfmicro/flatbuffers/flatbuffers.h
  96. 307 0
      code/lib/tfmicro/flatbuffers/stl_emulation.h
  97. 166 0
      code/lib/tfmicro/internal/detect_platform.h
  98. 11 0
      code/lib/tfmicro/kissfft/COPYING
  99. 164 0
      code/lib/tfmicro/kissfft/_kiss_fft_guts.h
  100. 131 0
      code/lib/tfmicro/kissfft/kiss_fft.h

+ 5 - 0
.gitignore

@@ -1,3 +1,8 @@
+# Remove if certain files are committed on purpose
+.pio/
+.vscode/
+.code-workspace
+
 CMakeLists.txt.user
 CMakeCache.txt
 CMakeFiles

+ 5 - 0
code/.gitignore

@@ -0,0 +1,5 @@
+.pio
+.vscode/.browse.c_cpp.db*
+.vscode/c_cpp_properties.json
+.vscode/launch.json
+.vscode/ipch

+ 8 - 0
code/CMakeLists.txt

@@ -0,0 +1,8 @@
+cmake_minimum_required(VERSION 3.16.0)
+
+list(APPEND EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
+
+set(PROJECT_VER "0.0.9.3")
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(esp32cam-server-only)

+ 39 - 0
code/include/README

@@ -0,0 +1,39 @@
+
+This directory is intended for project header files.
+
+A header file is a file containing C declarations and macro definitions
+to be shared between several project source files. You request the use of a
+header file in your project source file (C, C++, etc) located in `src` folder
+by including it, with the C preprocessing directive `#include'.
+
+```src/main.c
+
+#include "header.h"
+
+int main (void)
+{
+ ...
+}
+```
+
+Including a header file produces the same results as copying the header file
+into each source file that needs it. Such copying would be time-consuming
+and error-prone. With a header file, the related declarations appear
+in only one place. If they need to be changed, they can be changed in one
+place, and programs that include the header file will automatically use the
+new version when next recompiled. The header file eliminates the labor of
+finding and changing all the copies as well as the risk that a failure to
+find one copy will result in inconsistencies within a program.
+
+In C, the usual convention is to give header files names that end with `.h'.
+It is most portable to use only letters, digits, dashes, and underscores in
+header file names, and at most one dot.
+
+Read more about using header files in official GCC documentation:
+
+* Include Syntax
+* Include Operation
+* Once-Only Headers
+* Computed Includes
+
+https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

+ 46 - 0
code/lib/README

@@ -0,0 +1,46 @@
+
+This directory is intended for project specific (private) libraries.
+PlatformIO will compile them to static libraries and link into executable file.
+
+The source code of each library should be placed in a an own separate directory
+("lib/your_library_name/[here are source files]").
+
+For example, see a structure of the following two libraries `Foo` and `Bar`:
+
+|--lib
+|  |
+|  |--Bar
+|  |  |--docs
+|  |  |--examples
+|  |  |--src
+|  |     |- Bar.c
+|  |     |- Bar.h
+|  |  |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
+|  |
+|  |--Foo
+|  |  |- Foo.c
+|  |  |- Foo.h
+|  |
+|  |- README --> THIS FILE
+|
+|- platformio.ini
+|--src
+   |- main.c
+
+and a contents of `src/main.c`:
+```
+#include <Foo.h>
+#include <Bar.h>
+
+int main (void)
+{
+  ...
+}
+
+```
+
+PlatformIO Library Dependency Finder will find automatically dependent
+libraries scanning project source files.
+
+More information about PlatformIO Library Dependency Finder
+- https://docs.platformio.org/page/librarymanager/ldf.html

+ 157 - 0
code/lib/connect_wlan/Helper.cpp

@@ -0,0 +1,157 @@
+//#pragma warning(disable : 4996)
+
+#include "Helper.h"
+
+//#define ISWINDOWS_TRUE
+
+using namespace std;
+
+std::string FormatFileName(std::string input)
+{
+#ifdef ISWINDOWS_TRUE
+    input.erase(0, 1);
+    std::string os = "/";
+    std::string ns = "\\";
+    FindReplace(input, os, ns);
+#endif
+    return input;
+}
+
+
+
+
+void FindReplace(std::string& line, std::string& oldString, std::string& newString) {
+    const size_t oldSize = oldString.length();
+
+    // do nothing if line is shorter than the string to find
+    if (oldSize > line.length()) return;
+
+    const size_t newSize = newString.length();
+    for (size_t pos = 0; ; pos += newSize) {
+        // Locate the substring to replace
+        pos = line.find(oldString, pos);
+        if (pos == std::string::npos) return;
+        if (oldSize == newSize) {
+            // if they're same size, use std::string::replace
+            line.replace(pos, oldSize, newString);
+        }
+        else {
+            // if not same size, replace by erasing and inserting
+            line.erase(pos, oldSize);
+            line.insert(pos, newString);
+        }
+    }
+}
+
+
+
+
+bool ctype_space(const char c, string adddelimiter)
+{
+	if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 11)
+	{
+		return true;
+	}
+	if (adddelimiter.find(c) != string::npos)
+		return true;
+
+	return false;
+}
+
+string trim(string istring, string adddelimiter)
+{
+	bool trimmed = false;
+
+	if (ctype_space(istring[istring.length() - 1], adddelimiter))
+	{
+		istring.erase(istring.length() - 1);
+		trimmed = true;
+	}
+
+	if (ctype_space(istring[0], adddelimiter))
+	{
+		istring.erase(0, 1);
+		trimmed = true;
+	}
+
+	if ((trimmed == false) || (istring.size() == 0))
+	{
+		return istring;
+	}
+	else
+	{
+		return trim(istring, adddelimiter);
+	}
+}
+
+size_t findDelimiterPos(string input, string delimiter)
+{
+	size_t pos = std::string::npos;
+	size_t zw;
+	string akt_del;
+
+	for (int anz = 0; anz < delimiter.length(); ++anz)
+	{
+		akt_del = delimiter[anz];
+		if ((zw = input.find(akt_del)) != std::string::npos)
+		{
+			if (pos != std::string::npos)
+			{
+				if (zw < pos)
+					pos = zw;
+			}
+			else
+				pos = zw;
+		}
+	}
+	return pos;
+}
+
+
+void CopyFile(string input, string output)
+{
+	input = FormatFileName(input);
+	output = FormatFileName(output);
+
+	char cTemp;
+	FILE* fpSourceFile = fopen(input.c_str(), "rb");
+	FILE* fpTargetFile = fopen(output.c_str(), "wb");
+
+	// Code Section
+
+	// Read From The Source File - "Copy"
+	while (fread(&cTemp, 1, 1, fpSourceFile) == 1)
+	{
+		// Write To The Target File - "Paste"
+		fwrite(&cTemp, 1, 1, fpTargetFile);
+	}
+
+	// Close The Files
+	fclose(fpSourceFile);
+	fclose(fpTargetFile);
+}
+
+
+string getFileType(string filename)
+{
+	int lastpos = filename.find(".", 0);
+	int neu_pos;
+	while ((neu_pos = filename.find(".", lastpos + 1)) > -1)
+	{
+		lastpos = neu_pos;
+	}
+
+	string zw = filename.substr(lastpos + 1, filename.size() - lastpos);
+
+	return zw;
+}
+
+
+string toUpper(string in)
+{
+	for (int i = 0; i < in.length(); ++i)
+		in[i] = toupper(in[i]);
+	
+	return in;
+}
+

+ 22 - 0
code/lib/connect_wlan/Helper.h

@@ -0,0 +1,22 @@
+#pragma once
+#include <string>
+#include <fstream>
+
+
+using namespace std;
+
+std::string FormatFileName(std::string input);
+void FindReplace(std::string& line, std::string& oldString, std::string& newString);
+
+void CopyFile(string input, string output);
+
+size_t findDelimiterPos(string input, string delimiter);
+//string trim(string istring);
+string trim(string istring, string adddelimiter = "");
+bool ctype_space(const char c, string adddelimiter);
+
+string getFileType(string filename);
+
+string toUpper(string in);
+
+

+ 162 - 0
code/lib/connect_wlan/connect_wlan.cpp

@@ -0,0 +1,162 @@
+#include "connect_wlan.h"
+
+
+
+#include <string.h>
+#include "esp_wifi.h"
+#include "esp_event_loop.h"
+#include "freertos/event_groups.h"
+#include "esp_log.h"
+
+#include <fstream>
+#include <string>
+#include <vector>
+
+#include "Helper.h"
+
+
+
+
+static const char *MAIN_TAG = "connect_wlan";
+
+std::string ssid;
+std::string passphrase;
+
+static EventGroupHandle_t wifi_event_group;
+
+
+#define BLINK_GPIO GPIO_NUM_33
+
+
+std::vector<string> ZerlegeZeile(std::string input)
+{
+	std::vector<string> Output;
+	std::string delimiter = " =,";
+
+	input = trim(input, delimiter);
+	size_t pos = findDelimiterPos(input, delimiter);
+	std::string token;
+	while (pos != std::string::npos) {
+		token = input.substr(0, pos);
+		token = trim(token, delimiter);
+		Output.push_back(token);
+		input.erase(0, pos + 1);
+		input = trim(input, delimiter);
+		pos = findDelimiterPos(input, delimiter);
+	}
+	Output.push_back(input);
+
+	return Output;
+}
+
+
+
+
+void wifi_connect(){
+    wifi_config_t cfg = { };
+    strcpy((char*)cfg.sta.ssid, (const char*)ssid.c_str());
+    strcpy((char*)cfg.sta.password, (const char*)passphrase.c_str());
+    
+    ESP_ERROR_CHECK( esp_wifi_disconnect() );
+    ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &cfg) );
+    ESP_ERROR_CHECK( esp_wifi_connect() );
+}
+
+void blinkstatus(int dauer)
+{
+    gpio_reset_pin(BLINK_GPIO);
+    gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
+    for (int i = 0; i < 3; ++i)
+    {
+        gpio_set_level(BLINK_GPIO, 0);
+        vTaskDelay(dauer / portTICK_PERIOD_MS);
+        gpio_set_level(BLINK_GPIO, 1);
+        vTaskDelay(dauer / portTICK_PERIOD_MS);          
+    }
+}
+
+static esp_err_t event_handler(void *ctx, system_event_t *event)
+{
+    switch(event->event_id) {
+    case SYSTEM_EVENT_STA_START:
+        blinkstatus(200);
+        wifi_connect();
+        break;
+    case SYSTEM_EVENT_STA_GOT_IP:
+        xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
+        blinkstatus(1000);
+        break;
+    case SYSTEM_EVENT_STA_DISCONNECTED:
+        blinkstatus(200);
+        esp_wifi_connect();
+        xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
+        break;
+    default:
+        break;
+    }
+    return ESP_OK;
+}
+
+void initialise_wifi(std::string _ssid, std::string _passphrase)
+{
+    ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL) );
+    wifi_event_group = xEventGroupCreate();  
+    ssid = _ssid;
+    passphrase = _passphrase;
+    esp_log_level_set("wifi", ESP_LOG_NONE); // disable wifi driver logging
+    tcpip_adapter_init();
+    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
+    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
+    ESP_ERROR_CHECK( esp_wifi_start() );
+    esp_err_t ret = tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA ,"icircuit");
+    if(ret != ESP_OK ){
+      ESP_LOGE(MAIN_TAG,"failed to set hostname:%d",ret);  
+    }
+    xEventGroupWaitBits(wifi_event_group,CONNECTED_BIT,true,true,portMAX_DELAY);
+    tcpip_adapter_ip_info_t ip_info;
+    ESP_ERROR_CHECK(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info));
+    printf("IP :  %s\n", ip4addr_ntoa(&ip_info.ip));    
+}
+
+
+void LoadWlanFromFile(std::string fn, std::string &_ssid, std::string &_passphrase)
+{
+    string line = "";
+    std::vector<string> zerlegt;
+
+    FILE* pFile;
+    fn = FormatFileName(fn);
+    pFile = fopen(fn.c_str(), "r");
+
+    if (pFile == NULL)
+        return;
+
+    char zw[1024];
+    fgets(zw, 1024, pFile);
+//    printf("%s", zw);
+    line = std::string(zw);
+
+    while ((line.size() > 0) || !(feof(pFile)))
+    {
+//        printf("%s", line.c_str());
+        zerlegt = ZerlegeZeile(line);
+        if ((zerlegt.size() > 1) && (toUpper(zerlegt[0]) == "SSID"))
+            _ssid = zerlegt[1];
+        if ((zerlegt.size() > 1) && (toUpper(zerlegt[0]) == "PASSWORD"))
+            _passphrase = zerlegt[1];
+
+        if (fgets(zw, 1024, pFile) == NULL)
+        {
+            line = "";
+        }
+        else
+        {
+            line = std::string(zw);
+        }
+    }
+
+    fclose(pFile);
+}
+
+

+ 13 - 0
code/lib/connect_wlan/connect_wlan.h

@@ -0,0 +1,13 @@
+#ifndef CONNECT_WLAN_H
+#define CONNECT_WLAN_H
+
+#include <string>
+#include "driver/gpio.h"
+
+const int CONNECTED_BIT = BIT0;
+
+void initialise_wifi(std::string _ssid, std::string _passphrase);
+
+void LoadWlanFromFile(std::string fn, std::string &_ssid, std::string &_passphrase);
+
+#endif

+ 128 - 0
code/lib/conversions/esp_jpg_decode.c

@@ -0,0 +1,128 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "esp_jpg_decode.h"
+
+#include "esp_system.h"
+#if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+
+#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
+#include "esp32/rom/tjpgd.h"
+#else 
+#error Target CONFIG_IDF_TARGET is not supported
+#endif
+#else // ESP32 Before IDF 4.0
+#include "rom/tjpgd.h"
+#endif
+
+#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
+#include "esp32-hal-log.h"
+#define TAG ""
+#else
+#include "esp_log.h"
+static const char* TAG = "esp_jpg_decode";
+#endif
+
+typedef struct {
+        jpg_scale_t scale;
+        jpg_reader_cb reader;
+        jpg_writer_cb writer;
+        void * arg;
+        size_t len;
+        size_t index;
+} esp_jpg_decoder_t;
+
+static const char * jd_errors[] = {
+    "Succeeded",
+    "Interrupted by output function",
+    "Device error or wrong termination of input stream",
+    "Insufficient memory pool for the image",
+    "Insufficient stream input buffer",
+    "Parameter error",
+    "Data format error",
+    "Right format but not supported",
+    "Not supported JPEG standard"
+};
+
+static uint32_t _jpg_write(JDEC *decoder, void *bitmap, JRECT *rect)
+{
+    uint16_t x = rect->left;
+    uint16_t y = rect->top;
+    uint16_t w = rect->right + 1 - x;
+    uint16_t h = rect->bottom + 1 - y;
+    uint8_t *data = (uint8_t *)bitmap;
+
+    esp_jpg_decoder_t * jpeg = (esp_jpg_decoder_t *)decoder->device;
+
+    if (jpeg->writer) {
+        return jpeg->writer(jpeg->arg, x, y, w, h, data);
+    }
+    return 0;
+}
+
+static uint32_t _jpg_read(JDEC *decoder, uint8_t *buf, uint32_t len)
+{
+    esp_jpg_decoder_t * jpeg = (esp_jpg_decoder_t *)decoder->device;
+    if (jpeg->len && len > (jpeg->len - jpeg->index)) {
+        len = jpeg->len - jpeg->index;
+    }
+    if (len) {
+        len = jpeg->reader(jpeg->arg, jpeg->index, buf, len);
+        if (!len) {
+            ESP_LOGE(TAG, "Read Fail at %u/%u", jpeg->index, jpeg->len);
+        }
+        jpeg->index += len;
+    }
+    return len;
+}
+
+esp_err_t esp_jpg_decode(size_t len, jpg_scale_t scale, jpg_reader_cb reader, jpg_writer_cb writer, void * arg)
+{
+    static uint8_t work[3100];
+    JDEC decoder;
+    esp_jpg_decoder_t jpeg;
+
+    jpeg.len = len;
+    jpeg.reader = reader;
+    jpeg.writer = writer;
+    jpeg.arg = arg;
+    jpeg.scale = scale;
+    jpeg.index = 0;
+
+    JRESULT jres = jd_prepare(&decoder, _jpg_read, work, 3100, &jpeg);
+    if(jres != JDR_OK){
+        ESP_LOGE(TAG, "JPG Header Parse Failed! %s", jd_errors[jres]);
+        return ESP_FAIL;
+    }
+
+    uint16_t output_width = decoder.width / (1 << (uint8_t)(jpeg.scale));
+    uint16_t output_height = decoder.height / (1 << (uint8_t)(jpeg.scale));
+
+    //output start
+    writer(arg, 0, 0, output_width, output_height, NULL);
+    //output write
+    jres = jd_decomp(&decoder, _jpg_write, (uint8_t)jpeg.scale);
+    //output end
+    writer(arg, output_width, output_height, output_width, output_height, NULL);
+
+    if (jres != JDR_OK) {
+        ESP_LOGE(TAG, "JPG Decompression Failed! %s", jd_errors[jres]);
+        return ESP_FAIL;
+    }
+    //check if all data has been consumed.
+    if (len && jpeg.index < len) {
+        _jpg_read(&decoder, NULL, len - jpeg.index);
+    }
+
+    return ESP_OK;
+}
+

+ 43 - 0
code/lib/conversions/esp_jpg_decode.h

@@ -0,0 +1,43 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef _ESP_JPG_DECODE_H_
+#define _ESP_JPG_DECODE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "esp_err.h"
+
+typedef enum {
+    JPG_SCALE_NONE,
+    JPG_SCALE_2X,
+    JPG_SCALE_4X,
+    JPG_SCALE_8X,
+    JPG_SCALE_MAX = JPG_SCALE_8X
+} jpg_scale_t;
+
+typedef size_t (* jpg_reader_cb)(void * arg, size_t index, uint8_t *buf, size_t len);
+typedef bool (* jpg_writer_cb)(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data);
+
+esp_err_t esp_jpg_decode(size_t len, jpg_scale_t scale, jpg_reader_cb reader, jpg_writer_cb writer, void * arg);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ESP_JPG_DECODE_H_ */

+ 126 - 0
code/lib/conversions/img_converters.h

@@ -0,0 +1,126 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef _IMG_CONVERTERS_H_
+#define _IMG_CONVERTERS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "esp_camera.h"
+
+typedef size_t (* jpg_out_cb)(void * arg, size_t index, const void* data, size_t len);
+
+/**
+ * @brief Convert image buffer to JPEG
+ *
+ * @param src       Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format
+ * @param src_len   Length in bytes of the source buffer
+ * @param width     Width in pixels of the source image
+ * @param height    Height in pixels of the source image
+ * @param format    Format of the source image
+ * @param quality   JPEG quality of the resulting image
+ * @param cp        Callback to be called to write the bytes of the output JPEG
+ * @param arg       Pointer to be passed to the callback
+ *
+ * @return true on success
+ */
+bool fmt2jpg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void * arg);
+
+/**
+ * @brief Convert camera frame buffer to JPEG
+ *
+ * @param fb        Source camera frame buffer
+ * @param quality   JPEG quality of the resulting image
+ * @param cp        Callback to be called to write the bytes of the output JPEG
+ * @param arg       Pointer to be passed to the callback
+ *
+ * @return true on success
+ */
+bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg);
+
+/**
+ * @brief Convert image buffer to JPEG buffer
+ *
+ * @param src       Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format
+ * @param src_len   Length in bytes of the source buffer
+ * @param width     Width in pixels of the source image
+ * @param height    Height in pixels of the source image
+ * @param format    Format of the source image
+ * @param quality   JPEG quality of the resulting image
+ * @param out       Pointer to be populated with the address of the resulting buffer
+ * @param out_len   Pointer to be populated with the length of the output buffer
+ *
+ * @return true on success
+ */
+bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len);
+
+/**
+ * @brief Convert camera frame buffer to JPEG buffer
+ *
+ * @param fb        Source camera frame buffer
+ * @param quality   JPEG quality of the resulting image
+ * @param out       Pointer to be populated with the address of the resulting buffer
+ * @param out_len   Pointer to be populated with the length of the output buffer
+ *
+ * @return true on success
+ */
+bool frame2jpg(camera_fb_t * fb, uint8_t quality, uint8_t ** out, size_t * out_len);
+
+/**
+ * @brief Convert image buffer to BMP buffer
+ *
+ * @param src       Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format
+ * @param src_len   Length in bytes of the source buffer
+ * @param width     Width in pixels of the source image
+ * @param height    Height in pixels of the source image
+ * @param format    Format of the source image
+ * @param out       Pointer to be populated with the address of the resulting buffer
+ * @param out_len   Pointer to be populated with the length of the output buffer
+ *
+ * @return true on success
+ */
+bool fmt2bmp(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t ** out, size_t * out_len);
+
+/**
+ * @brief Convert camera frame buffer to BMP buffer
+ *
+ * @param fb        Source camera frame buffer
+ * @param out       Pointer to be populated with the address of the resulting buffer
+ * @param out_len   Pointer to be populated with the length of the output buffer
+ *
+ * @return true on success
+ */
+bool frame2bmp(camera_fb_t * fb, uint8_t ** out, size_t * out_len);
+
+/**
+ * @brief Convert image buffer to RGB888 buffer (used for face detection)
+ *
+ * @param src       Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format
+ * @param src_len   Length in bytes of the source buffer
+ * @param format    Format of the source image
+ * @param rgb_buf   Pointer to the output buffer (width * height * 3)
+ *
+ * @return true on success
+ */
+bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IMG_CONVERTERS_H_ */

+ 723 - 0
code/lib/conversions/jpge.cpp

@@ -0,0 +1,723 @@
+// jpge.cpp - C++ class for JPEG compression.
+// Public domain, Rich Geldreich <richgel99@gmail.com>
+// v1.01, Dec. 18, 2010 - Initial release
+// v1.02, Apr. 6, 2011 - Removed 2x2 ordered dither in H2V1 chroma subsampling method load_block_16_8_8(). (The rounding factor was 2, when it should have been 1. Either way, it wasn't helping.)
+// v1.03, Apr. 16, 2011 - Added support for optimized Huffman code tables, optimized dynamic memory allocation down to only 1 alloc.
+//                        Also from Alex Evans: Added RGBA support, linear memory allocator (no longer needed in v1.03).
+// v1.04, May. 19, 2012: Forgot to set m_pFile ptr to NULL in cfile_stream::close(). Thanks to Owen Kaluza for reporting this bug.
+//                       Code tweaks to fix VS2008 static code analysis warnings (all looked harmless).
+//                       Code review revealed method load_block_16_8_8() (used for the non-default H2V1 sampling mode to downsample chroma) somehow didn't get the rounding factor fix from v1.02.
+
+#include "jpge.h"
+
+#include <stdint.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <malloc.h>
+#include "esp_heap_caps.h"
+
+#define JPGE_MAX(a,b) (((a)>(b))?(a):(b))
+#define JPGE_MIN(a,b) (((a)<(b))?(a):(b))
+
+namespace jpge {
+
+    static inline void *jpge_malloc(size_t nSize) {
+        void * b = malloc(nSize);
+        if(b){
+            return b;
+        }
+        return heap_caps_malloc(nSize, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+    }
+    static inline void jpge_free(void *p) { free(p); }
+
+    // Various JPEG enums and tables.
+    enum { M_SOF0 = 0xC0, M_DHT = 0xC4, M_SOI = 0xD8, M_EOI = 0xD9, M_SOS = 0xDA, M_DQT = 0xDB, M_APP0 = 0xE0 };
+    enum { DC_LUM_CODES = 12, AC_LUM_CODES = 256, DC_CHROMA_CODES = 12, AC_CHROMA_CODES = 256, MAX_HUFF_SYMBOLS = 257, MAX_HUFF_CODESIZE = 32 };
+
+    static const uint8 s_zag[64] = { 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 };
+    static const int16 s_std_lum_quant[64] = { 16,11,12,14,12,10,16,14,13,14,18,17,16,19,24,40,26,24,22,22,24,49,35,37,29,40,58,51,61,60,57,51,56,55,64,72,92,78,64,68,87,69,55,56,80,109,81,87,95,98,103,104,103,62,77,113,121,112,100,120,92,101,103,99 };
+    static const int16 s_std_croma_quant[64] = { 17,18,18,24,21,24,47,26,26,47,99,66,56,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,99,99,99,99,99,99,99,99,99,99,99,99 };
+    static const uint8 s_dc_lum_bits[17] = { 0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 };
+    static const uint8 s_dc_lum_val[DC_LUM_CODES] = { 0,1,2,3,4,5,6,7,8,9,10,11 };
+    static const uint8 s_ac_lum_bits[17] = { 0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d };
+    static const uint8 s_ac_lum_val[AC_LUM_CODES]  = {
+        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
+    };
+    static const uint8 s_dc_chroma_bits[17] = { 0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0 };
+    static const uint8 s_dc_chroma_val[DC_CHROMA_CODES]  = { 0,1,2,3,4,5,6,7,8,9,10,11 };
+    static const uint8 s_ac_chroma_bits[17] = { 0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77 };
+    static const uint8 s_ac_chroma_val[AC_CHROMA_CODES] = {
+        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
+    };
+
+    const int YR = 19595, YG = 38470, YB = 7471, CB_R = -11059, CB_G = -21709, CB_B = 32768, CR_R = 32768, CR_G = -27439, CR_B = -5329;
+
+    static int32 m_last_quality = 0;
+    static int32 m_quantization_tables[2][64];
+
+    static bool m_huff_initialized = false;
+    static uint m_huff_codes[4][256];
+    static uint8 m_huff_code_sizes[4][256];
+    static uint8 m_huff_bits[4][17];
+    static uint8 m_huff_val[4][256];
+
+    static inline uint8 clamp(int i) {
+        if (i < 0) {
+            i = 0;
+        } else if (i > 255){
+            i = 255;
+        }
+        return static_cast<uint8>(i);
+    }
+
+    static void RGB_to_YCC(uint8* pDst, const uint8 *pSrc, int num_pixels) {
+        for ( ; num_pixels; pDst += 3, pSrc += 3, num_pixels--) {
+            const int r = pSrc[0], g = pSrc[1], b = pSrc[2];
+            pDst[0] = static_cast<uint8>((r * YR + g * YG + b * YB + 32768) >> 16);
+            pDst[1] = clamp(128 + ((r * CB_R + g * CB_G + b * CB_B + 32768) >> 16));
+            pDst[2] = clamp(128 + ((r * CR_R + g * CR_G + b * CR_B + 32768) >> 16));
+        }
+    }
+
+    static void RGB_to_Y(uint8* pDst, const uint8 *pSrc, int num_pixels) {
+        for ( ; num_pixels; pDst++, pSrc += 3, num_pixels--) {
+            pDst[0] = static_cast<uint8>((pSrc[0] * YR + pSrc[1] * YG + pSrc[2] * YB + 32768) >> 16);
+        }
+    }
+
+    static void Y_to_YCC(uint8* pDst, const uint8* pSrc, int num_pixels) {
+        for( ; num_pixels; pDst += 3, pSrc++, num_pixels--) {
+            pDst[0] = pSrc[0];
+            pDst[1] = 128;
+            pDst[2] = 128;
+        }
+    }
+
+    // Forward DCT - DCT derived from jfdctint.
+    enum { CONST_BITS = 13, ROW_BITS = 2 };
+#define DCT_DESCALE(x, n) (((x) + (((int32)1) << ((n) - 1))) >> (n))
+#define DCT_MUL(var, c) (static_cast<int16>(var) * static_cast<int32>(c))
+#define DCT1D(s0, s1, s2, s3, s4, s5, s6, s7) \
+    int32 t0 = s0 + s7, t7 = s0 - s7, t1 = s1 + s6, t6 = s1 - s6, t2 = s2 + s5, t5 = s2 - s5, t3 = s3 + s4, t4 = s3 - s4; \
+    int32 t10 = t0 + t3, t13 = t0 - t3, t11 = t1 + t2, t12 = t1 - t2; \
+    int32 u1 = DCT_MUL(t12 + t13, 4433); \
+    s2 = u1 + DCT_MUL(t13, 6270); \
+    s6 = u1 + DCT_MUL(t12, -15137); \
+    u1 = t4 + t7; \
+    int32 u2 = t5 + t6, u3 = t4 + t6, u4 = t5 + t7; \
+    int32 z5 = DCT_MUL(u3 + u4, 9633); \
+    t4 = DCT_MUL(t4, 2446); t5 = DCT_MUL(t5, 16819); \
+    t6 = DCT_MUL(t6, 25172); t7 = DCT_MUL(t7, 12299); \
+    u1 = DCT_MUL(u1, -7373); u2 = DCT_MUL(u2, -20995); \
+    u3 = DCT_MUL(u3, -16069); u4 = DCT_MUL(u4, -3196); \
+    u3 += z5; u4 += z5; \
+    s0 = t10 + t11; s1 = t7 + u1 + u4; s3 = t6 + u2 + u3; s4 = t10 - t11; s5 = t5 + u2 + u4; s7 = t4 + u1 + u3;
+
+    static void DCT2D(int32 *p) {
+        int32 c, *q = p;
+        for (c = 7; c >= 0; c--, q += 8) {
+            int32 s0 = q[0], s1 = q[1], s2 = q[2], s3 = q[3], s4 = q[4], s5 = q[5], s6 = q[6], s7 = q[7];
+            DCT1D(s0, s1, s2, s3, s4, s5, s6, s7);
+            q[0] = s0 << ROW_BITS; q[1] = DCT_DESCALE(s1, CONST_BITS-ROW_BITS); q[2] = DCT_DESCALE(s2, CONST_BITS-ROW_BITS); q[3] = DCT_DESCALE(s3, CONST_BITS-ROW_BITS);
+            q[4] = s4 << ROW_BITS; q[5] = DCT_DESCALE(s5, CONST_BITS-ROW_BITS); q[6] = DCT_DESCALE(s6, CONST_BITS-ROW_BITS); q[7] = DCT_DESCALE(s7, CONST_BITS-ROW_BITS);
+        }
+        for (q = p, c = 7; c >= 0; c--, q++) {
+            int32 s0 = q[0*8], s1 = q[1*8], s2 = q[2*8], s3 = q[3*8], s4 = q[4*8], s5 = q[5*8], s6 = q[6*8], s7 = q[7*8];
+            DCT1D(s0, s1, s2, s3, s4, s5, s6, s7);
+            q[0*8] = DCT_DESCALE(s0, ROW_BITS+3); q[1*8] = DCT_DESCALE(s1, CONST_BITS+ROW_BITS+3); q[2*8] = DCT_DESCALE(s2, CONST_BITS+ROW_BITS+3); q[3*8] = DCT_DESCALE(s3, CONST_BITS+ROW_BITS+3);
+            q[4*8] = DCT_DESCALE(s4, ROW_BITS+3); q[5*8] = DCT_DESCALE(s5, CONST_BITS+ROW_BITS+3); q[6*8] = DCT_DESCALE(s6, CONST_BITS+ROW_BITS+3); q[7*8] = DCT_DESCALE(s7, CONST_BITS+ROW_BITS+3);
+        }
+    }
+
+    // Compute the actual canonical Huffman codes/code sizes given the JPEG huff bits and val arrays.
+    static void compute_huffman_table(uint *codes, uint8 *code_sizes, uint8 *bits, uint8 *val)
+    {
+        int i, l, last_p, si;
+        static uint8 huff_size[257];
+        static uint huff_code[257];
+        uint code;
+
+        int p = 0;
+        for (l = 1; l <= 16; l++) {
+            for (i = 1; i <= bits[l]; i++) {
+                huff_size[p++] = (char)l;
+            }
+        }
+
+        huff_size[p] = 0;
+        last_p = p; // write sentinel
+
+        code = 0; si = huff_size[0]; p = 0;
+
+        while (huff_size[p]) {
+            while (huff_size[p] == si) {
+                huff_code[p++] = code++;
+            }
+            code <<= 1;
+            si++;
+        }
+
+        memset(codes, 0, sizeof(codes[0])*256);
+        memset(code_sizes, 0, sizeof(code_sizes[0])*256);
+        for (p = 0; p < last_p; p++) {
+            codes[val[p]]      = huff_code[p];
+            code_sizes[val[p]] = huff_size[p];
+        }
+    }
+
+    void jpeg_encoder::flush_output_buffer()
+    {
+        if (m_out_buf_left != JPGE_OUT_BUF_SIZE) {
+            m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(m_out_buf, JPGE_OUT_BUF_SIZE - m_out_buf_left);
+        }
+        m_pOut_buf = m_out_buf;
+        m_out_buf_left = JPGE_OUT_BUF_SIZE;
+    }
+
+    void jpeg_encoder::emit_byte(uint8 i)
+    {
+        *m_pOut_buf++ = i;
+        if (--m_out_buf_left == 0) {
+            flush_output_buffer();
+        }
+    }
+
+    void jpeg_encoder::put_bits(uint bits, uint len)
+    {
+        uint8 c = 0;
+        m_bit_buffer |= ((uint32)bits << (24 - (m_bits_in += len)));
+        while (m_bits_in >= 8) {
+            c = (uint8)((m_bit_buffer >> 16) & 0xFF);
+            emit_byte(c);
+            if (c == 0xFF) {
+                emit_byte(0);
+            }
+            m_bit_buffer <<= 8;
+            m_bits_in -= 8;
+        }
+    }
+
+    void jpeg_encoder::emit_word(uint i)
+    {
+        emit_byte(uint8(i >> 8)); emit_byte(uint8(i & 0xFF));
+    }
+
+    // JPEG marker generation.
+    void jpeg_encoder::emit_marker(int marker)
+    {
+        emit_byte(uint8(0xFF)); emit_byte(uint8(marker));
+    }
+
+    // Emit JFIF marker
+    void jpeg_encoder::emit_jfif_app0()
+    {
+        emit_marker(M_APP0);
+        emit_word(2 + 4 + 1 + 2 + 1 + 2 + 2 + 1 + 1);
+        emit_byte(0x4A); emit_byte(0x46); emit_byte(0x49); emit_byte(0x46); /* Identifier: ASCII "JFIF" */
+        emit_byte(0);
+        emit_byte(1);      /* Major version */
+        emit_byte(1);      /* Minor version */
+        emit_byte(0);      /* Density unit */
+        emit_word(1);
+        emit_word(1);
+        emit_byte(0);      /* No thumbnail image */
+        emit_byte(0);
+    }
+
+    // Emit quantization tables
+    void jpeg_encoder::emit_dqt()
+    {
+        for (int i = 0; i < ((m_num_components == 3) ? 2 : 1); i++)
+        {
+            emit_marker(M_DQT);
+            emit_word(64 + 1 + 2);
+            emit_byte(static_cast<uint8>(i));
+            for (int j = 0; j < 64; j++)
+                emit_byte(static_cast<uint8>(m_quantization_tables[i][j]));
+        }
+    }
+
+    // Emit start of frame marker
+    void jpeg_encoder::emit_sof()
+    {
+        emit_marker(M_SOF0);                           /* baseline */
+        emit_word(3 * m_num_components + 2 + 5 + 1);
+        emit_byte(8);                                  /* precision */
+        emit_word(m_image_y);
+        emit_word(m_image_x);
+        emit_byte(m_num_components);
+        for (int i = 0; i < m_num_components; i++)
+        {
+            emit_byte(static_cast<uint8>(i + 1));                                   /* component ID     */
+            emit_byte((m_comp_h_samp[i] << 4) + m_comp_v_samp[i]);  /* h and v sampling */
+            emit_byte(i > 0);                                   /* quant. table num */
+        }
+    }
+
+    // Emit Huffman table.
+    void jpeg_encoder::emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag)
+    {
+        emit_marker(M_DHT);
+
+        int length = 0;
+        for (int i = 1; i <= 16; i++)
+            length += bits[i];
+
+        emit_word(length + 2 + 1 + 16);
+        emit_byte(static_cast<uint8>(index + (ac_flag << 4)));
+
+        for (int i = 1; i <= 16; i++)
+            emit_byte(bits[i]);
+
+        for (int i = 0; i < length; i++)
+            emit_byte(val[i]);
+    }
+
+    // Emit all Huffman tables.
+    void jpeg_encoder::emit_dhts()
+    {
+        emit_dht(m_huff_bits[0+0], m_huff_val[0+0], 0, false);
+        emit_dht(m_huff_bits[2+0], m_huff_val[2+0], 0, true);
+        if (m_num_components == 3) {
+            emit_dht(m_huff_bits[0+1], m_huff_val[0+1], 1, false);
+            emit_dht(m_huff_bits[2+1], m_huff_val[2+1], 1, true);
+        }
+    }
+
+    // emit start of scan
+    void jpeg_encoder::emit_sos()
+    {
+        emit_marker(M_SOS);
+        emit_word(2 * m_num_components + 2 + 1 + 3);
+        emit_byte(m_num_components);
+        for (int i = 0; i < m_num_components; i++)
+        {
+            emit_byte(static_cast<uint8>(i + 1));
+            if (i == 0)
+                emit_byte((0 << 4) + 0);
+            else
+                emit_byte((1 << 4) + 1);
+        }
+        emit_byte(0);     /* spectral selection */
+        emit_byte(63);
+        emit_byte(0);
+    }
+
+    void jpeg_encoder::load_block_8_8_grey(int x)
+    {
+        uint8 *pSrc;
+        sample_array_t *pDst = m_sample_array;
+        x <<= 3;
+        for (int i = 0; i < 8; i++, pDst += 8)
+        {
+            pSrc = m_mcu_lines[i] + x;
+            pDst[0] = pSrc[0] - 128; pDst[1] = pSrc[1] - 128; pDst[2] = pSrc[2] - 128; pDst[3] = pSrc[3] - 128;
+            pDst[4] = pSrc[4] - 128; pDst[5] = pSrc[5] - 128; pDst[6] = pSrc[6] - 128; pDst[7] = pSrc[7] - 128;
+        }
+    }
+
+    void jpeg_encoder::load_block_8_8(int x, int y, int c)
+    {
+        uint8 *pSrc;
+        sample_array_t *pDst = m_sample_array;
+        x = (x * (8 * 3)) + c;
+        y <<= 3;
+        for (int i = 0; i < 8; i++, pDst += 8)
+        {
+            pSrc = m_mcu_lines[y + i] + x;
+            pDst[0] = pSrc[0 * 3] - 128; pDst[1] = pSrc[1 * 3] - 128; pDst[2] = pSrc[2 * 3] - 128; pDst[3] = pSrc[3 * 3] - 128;
+            pDst[4] = pSrc[4 * 3] - 128; pDst[5] = pSrc[5 * 3] - 128; pDst[6] = pSrc[6 * 3] - 128; pDst[7] = pSrc[7 * 3] - 128;
+        }
+    }
+
+    void jpeg_encoder::load_block_16_8(int x, int c)
+    {
+        uint8 *pSrc1, *pSrc2;
+        sample_array_t *pDst = m_sample_array;
+        x = (x * (16 * 3)) + c;
+        int a = 0, b = 2;
+        for (int i = 0; i < 16; i += 2, pDst += 8)
+        {
+            pSrc1 = m_mcu_lines[i + 0] + x;
+            pSrc2 = m_mcu_lines[i + 1] + x;
+            pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3] + pSrc2[ 0 * 3] + pSrc2[ 1 * 3] + a) >> 2) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3] + pSrc2[ 2 * 3] + pSrc2[ 3 * 3] + b) >> 2) - 128;
+            pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3] + pSrc2[ 4 * 3] + pSrc2[ 5 * 3] + a) >> 2) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3] + pSrc2[ 6 * 3] + pSrc2[ 7 * 3] + b) >> 2) - 128;
+            pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3] + pSrc2[ 8 * 3] + pSrc2[ 9 * 3] + a) >> 2) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3] + pSrc2[10 * 3] + pSrc2[11 * 3] + b) >> 2) - 128;
+            pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3] + pSrc2[12 * 3] + pSrc2[13 * 3] + a) >> 2) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3] + pSrc2[14 * 3] + pSrc2[15 * 3] + b) >> 2) - 128;
+            int temp = a; a = b; b = temp;
+        }
+    }
+
+    void jpeg_encoder::load_block_16_8_8(int x, int c)
+    {
+        uint8 *pSrc1;
+        sample_array_t *pDst = m_sample_array;
+        x = (x * (16 * 3)) + c;
+        for (int i = 0; i < 8; i++, pDst += 8)
+        {
+            pSrc1 = m_mcu_lines[i + 0] + x;
+            pDst[0] = ((pSrc1[ 0 * 3] + pSrc1[ 1 * 3]) >> 1) - 128; pDst[1] = ((pSrc1[ 2 * 3] + pSrc1[ 3 * 3]) >> 1) - 128;
+            pDst[2] = ((pSrc1[ 4 * 3] + pSrc1[ 5 * 3]) >> 1) - 128; pDst[3] = ((pSrc1[ 6 * 3] + pSrc1[ 7 * 3]) >> 1) - 128;
+            pDst[4] = ((pSrc1[ 8 * 3] + pSrc1[ 9 * 3]) >> 1) - 128; pDst[5] = ((pSrc1[10 * 3] + pSrc1[11 * 3]) >> 1) - 128;
+            pDst[6] = ((pSrc1[12 * 3] + pSrc1[13 * 3]) >> 1) - 128; pDst[7] = ((pSrc1[14 * 3] + pSrc1[15 * 3]) >> 1) - 128;
+        }
+    }
+
+    void jpeg_encoder::load_quantized_coefficients(int component_num)
+    {
+        int32 *q = m_quantization_tables[component_num > 0];
+        int16 *pDst = m_coefficient_array;
+        for (int i = 0; i < 64; i++)
+        {
+            sample_array_t j = m_sample_array[s_zag[i]];
+            if (j < 0)
+            {
+                if ((j = -j + (*q >> 1)) < *q)
+                    *pDst++ = 0;
+                else
+                    *pDst++ = static_cast<int16>(-(j / *q));
+            }
+            else
+            {
+                if ((j = j + (*q >> 1)) < *q)
+                    *pDst++ = 0;
+                else
+                    *pDst++ = static_cast<int16>((j / *q));
+            }
+            q++;
+        }
+    }
+
+    void jpeg_encoder::code_coefficients_pass_two(int component_num)
+    {
+        int i, j, run_len, nbits, temp1, temp2;
+        int16 *pSrc = m_coefficient_array;
+        uint *codes[2];
+        uint8 *code_sizes[2];
+
+        if (component_num == 0)
+        {
+            codes[0] = m_huff_codes[0 + 0]; codes[1] = m_huff_codes[2 + 0];
+            code_sizes[0] = m_huff_code_sizes[0 + 0]; code_sizes[1] = m_huff_code_sizes[2 + 0];
+        }
+        else
+        {
+            codes[0] = m_huff_codes[0 + 1]; codes[1] = m_huff_codes[2 + 1];
+            code_sizes[0] = m_huff_code_sizes[0 + 1]; code_sizes[1] = m_huff_code_sizes[2 + 1];
+        }
+
+        temp1 = temp2 = pSrc[0] - m_last_dc_val[component_num];
+        m_last_dc_val[component_num] = pSrc[0];
+
+        if (temp1 < 0)
+        {
+            temp1 = -temp1; temp2--;
+        }
+
+        nbits = 0;
+        while (temp1)
+        {
+            nbits++; temp1 >>= 1;
+        }
+
+        put_bits(codes[0][nbits], code_sizes[0][nbits]);
+        if (nbits) put_bits(temp2 & ((1 << nbits) - 1), nbits);
+
+        for (run_len = 0, i = 1; i < 64; i++)
+        {
+            if ((temp1 = m_coefficient_array[i]) == 0)
+                run_len++;
+            else
+            {
+                while (run_len >= 16)
+                {
+                    put_bits(codes[1][0xF0], code_sizes[1][0xF0]);
+                    run_len -= 16;
+                }
+                if ((temp2 = temp1) < 0)
+                {
+                    temp1 = -temp1;
+                    temp2--;
+                }
+                nbits = 1;
+                while (temp1 >>= 1)
+                    nbits++;
+                j = (run_len << 4) + nbits;
+                put_bits(codes[1][j], code_sizes[1][j]);
+                put_bits(temp2 & ((1 << nbits) - 1), nbits);
+                run_len = 0;
+            }
+        }
+        if (run_len)
+            put_bits(codes[1][0], code_sizes[1][0]);
+    }
+
+    void jpeg_encoder::code_block(int component_num)
+    {
+        DCT2D(m_sample_array);
+        load_quantized_coefficients(component_num);
+        code_coefficients_pass_two(component_num);
+    }
+
+    void jpeg_encoder::process_mcu_row()
+    {
+        if (m_num_components == 1)
+        {
+            for (int i = 0; i < m_mcus_per_row; i++)
+            {
+                load_block_8_8_grey(i); code_block(0);
+            }
+        }
+        else if ((m_comp_h_samp[0] == 1) && (m_comp_v_samp[0] == 1))
+        {
+            for (int i = 0; i < m_mcus_per_row; i++)
+            {
+                load_block_8_8(i, 0, 0); code_block(0); load_block_8_8(i, 0, 1); code_block(1); load_block_8_8(i, 0, 2); code_block(2);
+            }
+        }
+        else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 1))
+        {
+            for (int i = 0; i < m_mcus_per_row; i++)
+            {
+                load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0);
+                load_block_16_8_8(i, 1); code_block(1); load_block_16_8_8(i, 2); code_block(2);
+            }
+        }
+        else if ((m_comp_h_samp[0] == 2) && (m_comp_v_samp[0] == 2))
+        {
+            for (int i = 0; i < m_mcus_per_row; i++)
+            {
+                load_block_8_8(i * 2 + 0, 0, 0); code_block(0); load_block_8_8(i * 2 + 1, 0, 0); code_block(0);
+                load_block_8_8(i * 2 + 0, 1, 0); code_block(0); load_block_8_8(i * 2 + 1, 1, 0); code_block(0);
+                load_block_16_8(i, 1); code_block(1); load_block_16_8(i, 2); code_block(2);
+            }
+        }
+    }
+
+    void jpeg_encoder::load_mcu(const void *pSrc)
+    {
+        const uint8* Psrc = reinterpret_cast<const uint8*>(pSrc);
+
+        uint8* pDst = m_mcu_lines[m_mcu_y_ofs]; // OK to write up to m_image_bpl_xlt bytes to pDst
+
+        if (m_num_components == 1) {
+            if (m_image_bpp == 3)
+                RGB_to_Y(pDst, Psrc, m_image_x);
+            else
+                memcpy(pDst, Psrc, m_image_x);
+        } else {
+            if (m_image_bpp == 3)
+                RGB_to_YCC(pDst, Psrc, m_image_x);
+            else
+                Y_to_YCC(pDst, Psrc, m_image_x);
+        }
+
+        // Possibly duplicate pixels at end of scanline if not a multiple of 8 or 16
+        if (m_num_components == 1)
+            memset(m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt, pDst[m_image_bpl_xlt - 1], m_image_x_mcu - m_image_x);
+        else
+        {
+            const uint8 y = pDst[m_image_bpl_xlt - 3 + 0], cb = pDst[m_image_bpl_xlt - 3 + 1], cr = pDst[m_image_bpl_xlt - 3 + 2];
+            uint8 *q = m_mcu_lines[m_mcu_y_ofs] + m_image_bpl_xlt;
+            for (int i = m_image_x; i < m_image_x_mcu; i++)
+            {
+                *q++ = y; *q++ = cb; *q++ = cr;
+            }
+        }
+
+        if (++m_mcu_y_ofs == m_mcu_y)
+        {
+            process_mcu_row();
+            m_mcu_y_ofs = 0;
+        }
+    }
+
+    // Quantization table generation.
+    void jpeg_encoder::compute_quant_table(int32 *pDst, const int16 *pSrc)
+    {
+        int32 q;
+        if (m_params.m_quality < 50)
+            q = 5000 / m_params.m_quality;
+        else
+            q = 200 - m_params.m_quality * 2;
+        for (int i = 0; i < 64; i++)
+        {
+            int32 j = *pSrc++; j = (j * q + 50L) / 100L;
+            *pDst++ = JPGE_MIN(JPGE_MAX(j, 1), 255);
+        }
+    }
+
+    // Higher-level methods.
+    bool jpeg_encoder::jpg_open(int p_x_res, int p_y_res, int src_channels)
+    {
+        m_num_components = 3;
+        switch (m_params.m_subsampling)
+        {
+            case Y_ONLY:
+            {
+                m_num_components = 1;
+                m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1;
+                m_mcu_x          = 8; m_mcu_y          = 8;
+                break;
+            }
+            case H1V1:
+            {
+                m_comp_h_samp[0] = 1; m_comp_v_samp[0] = 1;
+                m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1;
+                m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1;
+                m_mcu_x          = 8; m_mcu_y          = 8;
+                break;
+            }
+            case H2V1:
+            {
+                m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 1;
+                m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1;
+                m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1;
+                m_mcu_x          = 16; m_mcu_y         = 8;
+                break;
+            }
+            case H2V2:
+            {
+                m_comp_h_samp[0] = 2; m_comp_v_samp[0] = 2;
+                m_comp_h_samp[1] = 1; m_comp_v_samp[1] = 1;
+                m_comp_h_samp[2] = 1; m_comp_v_samp[2] = 1;
+                m_mcu_x          = 16; m_mcu_y         = 16;
+            }
+        }
+
+        m_image_x        = p_x_res; m_image_y = p_y_res;
+        m_image_bpp      = src_channels;
+        m_image_bpl      = m_image_x * src_channels;
+        m_image_x_mcu    = (m_image_x + m_mcu_x - 1) & (~(m_mcu_x - 1));
+        m_image_y_mcu    = (m_image_y + m_mcu_y - 1) & (~(m_mcu_y - 1));
+        m_image_bpl_xlt  = m_image_x * m_num_components;
+        m_image_bpl_mcu  = m_image_x_mcu * m_num_components;
+        m_mcus_per_row   = m_image_x_mcu / m_mcu_x;
+
+        if ((m_mcu_lines[0] = static_cast<uint8*>(jpge_malloc(m_image_bpl_mcu * m_mcu_y))) == NULL) {
+            return false;
+        }
+        for (int i = 1; i < m_mcu_y; i++)
+            m_mcu_lines[i] = m_mcu_lines[i-1] + m_image_bpl_mcu;
+
+        if(m_last_quality != m_params.m_quality){
+            m_last_quality = m_params.m_quality;
+            compute_quant_table(m_quantization_tables[0], s_std_lum_quant);
+            compute_quant_table(m_quantization_tables[1], s_std_croma_quant);
+        }
+
+        if(!m_huff_initialized){
+            m_huff_initialized = true;
+
+            memcpy(m_huff_bits[0+0], s_dc_lum_bits, 17);    memcpy(m_huff_val[0+0], s_dc_lum_val, DC_LUM_CODES);
+            memcpy(m_huff_bits[2+0], s_ac_lum_bits, 17);    memcpy(m_huff_val[2+0], s_ac_lum_val, AC_LUM_CODES);
+            memcpy(m_huff_bits[0+1], s_dc_chroma_bits, 17); memcpy(m_huff_val[0+1], s_dc_chroma_val, DC_CHROMA_CODES);
+            memcpy(m_huff_bits[2+1], s_ac_chroma_bits, 17); memcpy(m_huff_val[2+1], s_ac_chroma_val, AC_CHROMA_CODES);
+
+            compute_huffman_table(&m_huff_codes[0+0][0], &m_huff_code_sizes[0+0][0], m_huff_bits[0+0], m_huff_val[0+0]);
+            compute_huffman_table(&m_huff_codes[2+0][0], &m_huff_code_sizes[2+0][0], m_huff_bits[2+0], m_huff_val[2+0]);
+            compute_huffman_table(&m_huff_codes[0+1][0], &m_huff_code_sizes[0+1][0], m_huff_bits[0+1], m_huff_val[0+1]);
+            compute_huffman_table(&m_huff_codes[2+1][0], &m_huff_code_sizes[2+1][0], m_huff_bits[2+1], m_huff_val[2+1]);
+        }
+
+        m_out_buf_left = JPGE_OUT_BUF_SIZE;
+        m_pOut_buf = m_out_buf;
+        m_bit_buffer = 0;
+        m_bits_in = 0;
+        m_mcu_y_ofs = 0;
+        m_pass_num = 2;
+        memset(m_last_dc_val, 0, 3 * sizeof(m_last_dc_val[0]));
+
+        // Emit all markers at beginning of image file.
+        emit_marker(M_SOI);
+        emit_jfif_app0();
+        emit_dqt();
+        emit_sof();
+        emit_dhts();
+        emit_sos();
+
+        return m_all_stream_writes_succeeded;
+    }
+
+    bool jpeg_encoder::process_end_of_image()
+    {
+        if (m_mcu_y_ofs) {
+            if (m_mcu_y_ofs < 16) { // check here just to shut up static analysis
+                for (int i = m_mcu_y_ofs; i < m_mcu_y; i++) {
+                    memcpy(m_mcu_lines[i], m_mcu_lines[m_mcu_y_ofs - 1], m_image_bpl_mcu);
+                }
+            }
+            process_mcu_row();
+        }
+
+        put_bits(0x7F, 7);
+        emit_marker(M_EOI);
+        flush_output_buffer();
+        m_all_stream_writes_succeeded = m_all_stream_writes_succeeded && m_pStream->put_buf(NULL, 0);
+        m_pass_num++; // purposely bump up m_pass_num, for debugging
+        return true;
+    }
+
+    void jpeg_encoder::clear()
+    {
+        m_mcu_lines[0] = NULL;
+        m_pass_num = 0;
+        m_all_stream_writes_succeeded = true;
+    }
+
+    jpeg_encoder::jpeg_encoder()
+    {
+        clear();
+    }
+
+    jpeg_encoder::~jpeg_encoder()
+    {
+        deinit();
+    }
+
+    bool jpeg_encoder::init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params)
+    {
+        deinit();
+        if (((!pStream) || (width < 1) || (height < 1)) || ((src_channels != 1) && (src_channels != 3) && (src_channels != 4)) || (!comp_params.check())) return false;
+        m_pStream = pStream;
+        m_params = comp_params;
+        return jpg_open(width, height, src_channels);
+    }
+
+    void jpeg_encoder::deinit()
+    {
+        jpge_free(m_mcu_lines[0]);
+        clear();
+    }
+
+    bool jpeg_encoder::process_scanline(const void* pScanline)
+    {
+        if ((m_pass_num < 1) || (m_pass_num > 2)) {
+            return false;
+        }
+        if (m_all_stream_writes_succeeded) {
+            if (!pScanline) {
+                if (!process_end_of_image()) {
+                    return false;
+                }
+            } else {
+                load_mcu(pScanline);
+            }
+        }
+        return m_all_stream_writes_succeeded;
+    }
+
+} // namespace jpge

+ 142 - 0
code/lib/conversions/jpge.h

@@ -0,0 +1,142 @@
+// jpge.h - C++ class for JPEG compression.
+// Public domain, Rich Geldreich <richgel99@gmail.com>
+// Alex Evans: Added RGBA support, linear memory allocator.
+#ifndef JPEG_ENCODER_H
+#define JPEG_ENCODER_H
+
+namespace jpge
+{
+    typedef unsigned char  uint8;
+    typedef signed short   int16;
+    typedef signed int     int32;
+    typedef unsigned short uint16;
+    typedef unsigned int   uint32;
+    typedef unsigned int   uint;
+
+    // JPEG chroma subsampling factors. Y_ONLY (grayscale images) and H2V2 (color images) are the most common.
+    enum subsampling_t { Y_ONLY = 0, H1V1 = 1, H2V1 = 2, H2V2 = 3 };
+
+    // JPEG compression parameters structure.
+    struct params {
+            inline params() : m_quality(85), m_subsampling(H2V2) { }
+
+            inline bool check() const {
+                if ((m_quality < 1) || (m_quality > 100)) {
+                    return false;
+                }
+                if ((uint)m_subsampling > (uint)H2V2) {
+                    return false;
+                }
+                return true;
+            }
+
+            // Quality: 1-100, higher is better. Typical values are around 50-95.
+            int m_quality;
+
+            // m_subsampling:
+            // 0 = Y (grayscale) only
+            // 1 = H1V1 subsampling (YCbCr 1x1x1, 3 blocks per MCU)
+            // 2 = H2V1 subsampling (YCbCr 2x1x1, 4 blocks per MCU)
+            // 3 = H2V2 subsampling (YCbCr 4x1x1, 6 blocks per MCU-- very common)
+            subsampling_t m_subsampling;
+    };
+    
+    // Output stream abstract class - used by the jpeg_encoder class to write to the output stream.
+    // put_buf() is generally called with len==JPGE_OUT_BUF_SIZE bytes, but for headers it'll be called with smaller amounts.
+    class output_stream {
+        public:
+            virtual ~output_stream() { };
+            virtual bool put_buf(const void* Pbuf, int len) = 0;
+            virtual uint get_size() const = 0;
+    };
+    
+    // Lower level jpeg_encoder class - useful if more control is needed than the above helper functions.
+    class jpeg_encoder {
+        public:
+            jpeg_encoder();
+            ~jpeg_encoder();
+
+            // Initializes the compressor.
+            // pStream: The stream object to use for writing compressed data.
+            // params - Compression parameters structure, defined above.
+            // width, height  - Image dimensions.
+            // channels - May be 1, or 3. 1 indicates grayscale, 3 indicates RGB source data.
+            // Returns false on out of memory or if a stream write fails.
+            bool init(output_stream *pStream, int width, int height, int src_channels, const params &comp_params = params());
+
+            // Call this method with each source scanline.
+            // width * src_channels bytes per scanline is expected (RGB or Y format).
+            // You must call with NULL after all scanlines are processed to finish compression.
+            // Returns false on out of memory or if a stream write fails.
+            bool process_scanline(const void* pScanline);
+
+            // Deinitializes the compressor, freeing any allocated memory. May be called at any time.
+            void deinit();
+
+        private:
+            jpeg_encoder(const jpeg_encoder &);
+            jpeg_encoder &operator =(const jpeg_encoder &);
+
+            typedef int32 sample_array_t;
+            enum { JPGE_OUT_BUF_SIZE = 512 };
+
+            output_stream *m_pStream;
+            params m_params;
+            uint8 m_num_components;
+            uint8 m_comp_h_samp[3], m_comp_v_samp[3];
+            int m_image_x, m_image_y, m_image_bpp, m_image_bpl;
+            int m_image_x_mcu, m_image_y_mcu;
+            int m_image_bpl_xlt, m_image_bpl_mcu;
+            int m_mcus_per_row;
+            int m_mcu_x, m_mcu_y;
+            uint8 *m_mcu_lines[16];
+            uint8 m_mcu_y_ofs;
+            sample_array_t m_sample_array[64];
+            int16 m_coefficient_array[64];
+
+            int m_last_dc_val[3];
+            uint8 m_out_buf[JPGE_OUT_BUF_SIZE];
+            uint8 *m_pOut_buf;
+            uint m_out_buf_left;
+            uint32 m_bit_buffer;
+            uint m_bits_in;
+            uint8 m_pass_num;
+            bool m_all_stream_writes_succeeded;
+
+            bool jpg_open(int p_x_res, int p_y_res, int src_channels);
+
+            void flush_output_buffer();
+            void put_bits(uint bits, uint len);
+
+            void emit_byte(uint8 i);
+            void emit_word(uint i);
+            void emit_marker(int marker);
+
+            void emit_jfif_app0();
+            void emit_dqt();
+            void emit_sof();
+            void emit_dht(uint8 *bits, uint8 *val, int index, bool ac_flag);
+            void emit_dhts();
+            void emit_sos();
+
+            void compute_quant_table(int32 *dst, const int16 *src);
+            void load_quantized_coefficients(int component_num);
+
+            void load_block_8_8_grey(int x);
+            void load_block_8_8(int x, int y, int c);
+            void load_block_16_8(int x, int c);
+            void load_block_16_8_8(int x, int c);
+
+            void code_coefficients_pass_two(int component_num);
+            void code_block(int component_num);
+
+            void process_mcu_row();
+            bool process_end_of_image();
+            void load_mcu(const void* src);
+            void clear();
+            void init();
+    };
+    
+} // namespace jpge
+
+#endif // JPEG_ENCODER

+ 315 - 0
code/lib/conversions/to_bmp.c

@@ -0,0 +1,315 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include <stddef.h>
+#include <string.h>
+#include "img_converters.h"
+#include "soc/efuse_reg.h"
+#include "esp_heap_caps.h"
+#include "yuv.h"
+#include "sdkconfig.h"
+#include "esp_jpg_decode.h"
+
+#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
+#include "esp32-hal-log.h"
+#define TAG ""
+#else
+#include "esp_log.h"
+static const char* TAG = "to_bmp";
+#endif
+
+static const int BMP_HEADER_LEN = 54;
+
+typedef struct {
+    uint32_t filesize;
+    uint32_t reserved;
+    uint32_t fileoffset_to_pixelarray;
+    uint32_t dibheadersize;
+    int32_t width;
+    int32_t height;
+    uint16_t planes;
+    uint16_t bitsperpixel;
+    uint32_t compression;
+    uint32_t imagesize;
+    uint32_t ypixelpermeter;
+    uint32_t xpixelpermeter;
+    uint32_t numcolorspallette;
+    uint32_t mostimpcolor;
+} bmp_header_t;
+
+typedef struct {
+        uint16_t width;
+        uint16_t height;
+        uint16_t data_offset;
+        const uint8_t *input;
+        uint8_t *output;
+} rgb_jpg_decoder;
+
+static void *_malloc(size_t size)
+{
+    return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+}
+
+//output buffer and image width
+static bool _rgb_write(void * arg, uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t *data)
+{
+    rgb_jpg_decoder * jpeg = (rgb_jpg_decoder *)arg;
+    if(!data){
+        if(x == 0 && y == 0){
+            //write start
+            jpeg->width = w;
+            jpeg->height = h;
+            //if output is null, this is BMP
+            if(!jpeg->output){
+                jpeg->output = (uint8_t *)_malloc((w*h*3)+jpeg->data_offset);
+                if(!jpeg->output){
+                    return false;
+                }
+            }
+        } else {
+            //write end
+        }
+        return true;
+    }
+
+    size_t jw = jpeg->width*3;
+    size_t t = y * jw;
+    size_t b = t + (h * jw);
+    size_t l = x * 3;
+    uint8_t *out = jpeg->output+jpeg->data_offset;
+    uint8_t *o = out;
+    size_t iy, ix;
+
+    w = w * 3;
+
+    for(iy=t; iy<b; iy+=jw) {
+        o = out+iy+l;
+        for(ix=0; ix<w; ix+= 3) {
+            o[ix] = data[ix+2];
+            o[ix+1] = data[ix+1];
+            o[ix+2] = data[ix];
+        }
+        data+=w;
+    }
+    return true;
+}
+
+//input buffer
+static uint32_t _jpg_read(void * arg, size_t index, uint8_t *buf, size_t len)
+{
+    rgb_jpg_decoder * jpeg = (rgb_jpg_decoder *)arg;
+    if(buf) {
+        memcpy(buf, jpeg->input + index, len);
+    }
+    return len;
+}
+
+static bool jpg2rgb888(const uint8_t *src, size_t src_len, uint8_t * out, jpg_scale_t scale)
+{
+    rgb_jpg_decoder jpeg;
+    jpeg.width = 0;
+    jpeg.height = 0;
+    jpeg.input = src;
+    jpeg.output = out;
+    jpeg.data_offset = 0;
+
+    if(esp_jpg_decode(src_len, scale, _jpg_read, _rgb_write, (void*)&jpeg) != ESP_OK){
+        return false;
+    }
+    return true;
+}
+
+bool jpg2bmp(const uint8_t *src, size_t src_len, uint8_t ** out, size_t * out_len)
+{
+
+    rgb_jpg_decoder jpeg;
+    jpeg.width = 0;
+    jpeg.height = 0;
+    jpeg.input = src;
+    jpeg.output = NULL;
+    jpeg.data_offset = BMP_HEADER_LEN;
+
+    if(esp_jpg_decode(src_len, JPG_SCALE_NONE, _jpg_read, _rgb_write, (void*)&jpeg) != ESP_OK){
+        return false;
+    }
+
+    size_t output_size = jpeg.width*jpeg.height*3;
+
+    jpeg.output[0] = 'B';
+    jpeg.output[1] = 'M';
+    bmp_header_t * bitmap  = (bmp_header_t*)&jpeg.output[2];
+    bitmap->reserved = 0;
+    bitmap->filesize = output_size+BMP_HEADER_LEN;
+    bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN;
+    bitmap->dibheadersize = 40;
+    bitmap->width = jpeg.width;
+    bitmap->height = -jpeg.height;//set negative for top to bottom
+    bitmap->planes = 1;
+    bitmap->bitsperpixel = 24;
+    bitmap->compression = 0;
+    bitmap->imagesize = output_size;
+    bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI
+    bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI
+    bitmap->numcolorspallette = 0;
+    bitmap->mostimpcolor = 0;
+
+    *out = jpeg.output;
+    *out_len = output_size+BMP_HEADER_LEN;
+
+    return true;
+}
+
+bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf)
+{
+    int pix_count = 0;
+    if(format == PIXFORMAT_JPEG) {
+        return jpg2rgb888(src_buf, src_len, rgb_buf, JPG_SCALE_NONE);
+    } else if(format == PIXFORMAT_RGB888) {
+        memcpy(rgb_buf, src_buf, src_len);
+    } else if(format == PIXFORMAT_RGB565) {
+        int i;
+        uint8_t hb, lb;
+        pix_count = src_len / 2;
+        for(i=0; i<pix_count; i++) {
+            hb = *src_buf++;
+            lb = *src_buf++;
+            *rgb_buf++ = (lb & 0x1F) << 3;
+            *rgb_buf++ = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
+            *rgb_buf++ = hb & 0xF8;
+        }
+    } else if(format == PIXFORMAT_GRAYSCALE) {
+        int i;
+        uint8_t b;
+        pix_count = src_len;
+        for(i=0; i<pix_count; i++) {
+            b = *src_buf++;
+            *rgb_buf++ = b;
+            *rgb_buf++ = b;
+            *rgb_buf++ = b;
+        }
+    } else if(format == PIXFORMAT_YUV422) {
+        pix_count = src_len / 2;
+        int i, maxi = pix_count / 2;
+        uint8_t y0, y1, u, v;
+        uint8_t r, g, b;
+        for(i=0; i<maxi; i++) {
+            y0 = *src_buf++;
+            u = *src_buf++;
+            y1 = *src_buf++;
+            v = *src_buf++;
+
+            yuv2rgb(y0, u, v, &r, &g, &b);
+            *rgb_buf++ = b;
+            *rgb_buf++ = g;
+            *rgb_buf++ = r;
+
+            yuv2rgb(y1, u, v, &r, &g, &b);
+            *rgb_buf++ = b;
+            *rgb_buf++ = g;
+            *rgb_buf++ = r;
+        }
+    }
+    return true;
+}
+
+bool fmt2bmp(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t ** out, size_t * out_len)
+{
+    if(format == PIXFORMAT_JPEG) {
+        return jpg2bmp(src, src_len, out, out_len);
+    }
+
+    *out = NULL;
+    *out_len = 0;
+
+    int pix_count = width*height;
+    size_t out_size = (pix_count * 3) + BMP_HEADER_LEN;
+    uint8_t * out_buf = (uint8_t *)_malloc(out_size);
+    if(!out_buf) {
+        ESP_LOGE(TAG, "_malloc failed! %u", out_size);
+        return false;
+    }
+
+    out_buf[0] = 'B';
+    out_buf[1] = 'M';
+    bmp_header_t * bitmap  = (bmp_header_t*)&out_buf[2];
+    bitmap->reserved = 0;
+    bitmap->filesize = out_size;
+    bitmap->fileoffset_to_pixelarray = BMP_HEADER_LEN;
+    bitmap->dibheadersize = 40;
+    bitmap->width = width;
+    bitmap->height = -height;//set negative for top to bottom
+    bitmap->planes = 1;
+    bitmap->bitsperpixel = 24;
+    bitmap->compression = 0;
+    bitmap->imagesize = pix_count * 3;
+    bitmap->ypixelpermeter = 0x0B13 ; //2835 , 72 DPI
+    bitmap->xpixelpermeter = 0x0B13 ; //2835 , 72 DPI
+    bitmap->numcolorspallette = 0;
+    bitmap->mostimpcolor = 0;
+
+    uint8_t * rgb_buf = out_buf + BMP_HEADER_LEN;
+    uint8_t * src_buf = src;
+
+
+    //convert data to RGB888
+    if(format == PIXFORMAT_RGB888) {
+        memcpy(rgb_buf, src_buf, pix_count*3);
+    } else if(format == PIXFORMAT_RGB565) {
+        int i;
+        uint8_t hb, lb;
+        for(i=0; i<pix_count; i++) {
+            hb = *src_buf++;
+            lb = *src_buf++;
+            *rgb_buf++ = (lb & 0x1F) << 3;
+            *rgb_buf++ = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
+            *rgb_buf++ = hb & 0xF8;
+        }
+    } else if(format == PIXFORMAT_GRAYSCALE) {
+        int i;
+        uint8_t b;
+        for(i=0; i<pix_count; i++) {
+            b = *src_buf++;
+            *rgb_buf++ = b;
+            *rgb_buf++ = b;
+            *rgb_buf++ = b;
+        }
+    } else if(format == PIXFORMAT_YUV422) {
+        int i, maxi = pix_count / 2;
+        uint8_t y0, y1, u, v;
+        uint8_t r, g, b;
+        for(i=0; i<maxi; i++) {
+            y0 = *src_buf++;
+            u = *src_buf++;
+            y1 = *src_buf++;
+            v = *src_buf++;
+
+            yuv2rgb(y0, u, v, &r, &g, &b);
+            *rgb_buf++ = b;
+            *rgb_buf++ = g;
+            *rgb_buf++ = r;
+
+            yuv2rgb(y1, u, v, &r, &g, &b);
+            *rgb_buf++ = b;
+            *rgb_buf++ = g;
+            *rgb_buf++ = r;
+        }
+    }
+    *out = out_buf;
+    *out_len = out_size;
+    return true;
+}
+
+bool frame2bmp(camera_fb_t * fb, uint8_t ** out, size_t * out_len)
+{
+    return fmt2bmp(fb->buf, fb->len, fb->width, fb->height, fb->format, out, out_len);
+}

+ 242 - 0
code/lib/conversions/to_jpg.cpp

@@ -0,0 +1,242 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include <stddef.h>
+#include <string.h>
+#include "esp_attr.h"
+#include "soc/efuse_reg.h"
+#include "esp_heap_caps.h"
+#include <esp_camera.h>
+#include "img_converters.h"
+#include "jpge.h"
+#include "yuv.h"
+
+#include "esp_system.h"
+#if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+
+#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
+#include "esp32/spiram.h"
+#else 
+#error Target CONFIG_IDF_TARGET is not supported
+#endif
+#else // ESP32 Before IDF 4.0
+#include "esp_spiram.h"
+#endif
+
+#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
+#include "esp32-hal-log.h"
+#define TAG ""
+#else
+#include "esp_log.h"
+static const char* TAG = "to_jpg";
+#endif
+
+static void *_malloc(size_t size)
+{
+    void * res = malloc(size);
+    if(res) {
+        return res;
+    }
+    return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+}
+
+static IRAM_ATTR void convert_line_format(uint8_t * src, pixformat_t format, uint8_t * dst, size_t width, size_t in_channels, size_t line)
+{
+    int i=0, o=0, l=0;
+    if(format == PIXFORMAT_GRAYSCALE) {
+        memcpy(dst, src + line * width, width);
+    } else if(format == PIXFORMAT_RGB888) {
+        l = width * 3;
+        src += l * line;
+        for(i=0; i<l; i+=3) {
+            dst[o++] = src[i+2];
+            dst[o++] = src[i+1];
+            dst[o++] = src[i];
+        }
+    } else if(format == PIXFORMAT_RGB565) {
+        l = width * 2;
+        src += l * line;
+        for(i=0; i<l; i+=2) {
+            dst[o++] = src[i] & 0xF8;
+            dst[o++] = (src[i] & 0x07) << 5 | (src[i+1] & 0xE0) >> 3;
+            dst[o++] = (src[i+1] & 0x1F) << 3;
+        }
+    } else if(format == PIXFORMAT_YUV422) {
+        uint8_t y0, y1, u, v;
+        uint8_t r, g, b;
+        l = width * 2;
+        src += l * line;
+        for(i=0; i<l; i+=4) {
+            y0 = src[i];
+            u = src[i+1];
+            y1 = src[i+2];
+            v = src[i+3];
+
+            yuv2rgb(y0, u, v, &r, &g, &b);
+            dst[o++] = r;
+            dst[o++] = g;
+            dst[o++] = b;
+
+            yuv2rgb(y1, u, v, &r, &g, &b);
+            dst[o++] = r;
+            dst[o++] = g;
+            dst[o++] = b;
+        }
+    }
+}
+
+bool convert_image(uint8_t *src, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpge::output_stream *dst_stream)
+{
+    int num_channels = 3;
+    jpge::subsampling_t subsampling = jpge::H2V2;
+
+    if(format == PIXFORMAT_GRAYSCALE) {
+        num_channels = 1;
+        subsampling = jpge::Y_ONLY;
+    }
+
+    if(!quality) {
+        quality = 1;
+    } else if(quality > 100) {
+        quality = 100;
+    }
+
+    jpge::params comp_params = jpge::params();
+    comp_params.m_subsampling = subsampling;
+    comp_params.m_quality = quality;
+
+    jpge::jpeg_encoder dst_image;
+
+    if (!dst_image.init(dst_stream, width, height, num_channels, comp_params)) {
+        ESP_LOGE(TAG, "JPG encoder init failed");
+        return false;
+    }
+
+    uint8_t* line = (uint8_t*)_malloc(width * num_channels);
+    if(!line) {
+        ESP_LOGE(TAG, "Scan line malloc failed");
+        return false;
+    }
+
+    for (int i = 0; i < height; i++) {
+        convert_line_format(src, format, line, width, num_channels, i);
+        if (!dst_image.process_scanline(line)) {
+            ESP_LOGE(TAG, "JPG process line %u failed", i);
+            free(line);
+            return false;
+        }
+    }
+    free(line);
+
+    if (!dst_image.process_scanline(NULL)) {
+        ESP_LOGE(TAG, "JPG image finish failed");
+        return false;
+    }
+    dst_image.deinit();
+    return true;
+}
+
+class callback_stream : public jpge::output_stream {
+protected:
+    jpg_out_cb ocb;
+    void * oarg;
+    size_t index;
+
+public:
+    callback_stream(jpg_out_cb cb, void * arg) : ocb(cb), oarg(arg), index(0) { }
+    virtual ~callback_stream() { }
+    virtual bool put_buf(const void* data, int len)
+    {
+        index += ocb(oarg, index, data, len);
+        return true;
+    }
+    virtual size_t get_size() const
+    {
+        return index;
+    }
+};
+
+bool fmt2jpg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void * arg)
+{
+    callback_stream dst_stream(cb, arg);
+    return convert_image(src, width, height, format, quality, &dst_stream);
+}
+
+bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg)
+{
+    return fmt2jpg_cb(fb->buf, fb->len, fb->width, fb->height, fb->format, quality, cb, arg);
+}
+
+
+
+class memory_stream : public jpge::output_stream {
+protected:
+    uint8_t *out_buf;
+    size_t max_len, index;
+
+public:
+    memory_stream(void *pBuf, uint buf_size) : out_buf(static_cast<uint8_t*>(pBuf)), max_len(buf_size), index(0) { }
+
+    virtual ~memory_stream() { }
+
+    virtual bool put_buf(const void* pBuf, int len)
+    {
+        if (!pBuf) {
+            //end of image
+            return true;
+        }
+        if ((size_t)len > (max_len - index)) {
+            ESP_LOGW(TAG, "JPG output overflow: %d bytes", len - (max_len - index));
+            len = max_len - index;
+        }
+        if (len) {
+            memcpy(out_buf + index, pBuf, len);
+            index += len;
+        }
+        return true;
+    }
+
+    virtual size_t get_size() const
+    {
+        return index;
+    }
+};
+
+bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len)
+{
+    //todo: allocate proper buffer for holding JPEG data
+    //this should be enough for CIF frame size
+//    int jpg_buf_len = 64*1024;
+    int jpg_buf_len = 256*1024;     // Anpassung wg. zu kleiner Bitmaps
+
+
+    uint8_t * jpg_buf = (uint8_t *)_malloc(jpg_buf_len);
+    if(jpg_buf == NULL) {
+        ESP_LOGE(TAG, "JPG buffer malloc failed");
+        return false;
+    }
+    memory_stream dst_stream(jpg_buf, jpg_buf_len);
+
+    if(!convert_image(src, width, height, format, quality, &dst_stream)) {
+        free(jpg_buf);
+        return false;
+    }
+
+    *out = jpg_buf;
+    *out_len = dst_stream.get_size();
+    return true;
+}
+
+bool frame2jpg(camera_fb_t * fb, uint8_t quality, uint8_t ** out, size_t * out_len)
+{
+    return fmt2jpg(fb->buf, fb->len, fb->width, fb->height, fb->format, quality, out, out_len);
+}

+ 298 - 0
code/lib/conversions/yuv.c

@@ -0,0 +1,298 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include "yuv.h"
+#include "esp_attr.h"
+
+typedef struct {
+        int16_t vY;
+        int16_t vVr;
+        int16_t vVg;
+        int16_t vUg;
+        int16_t vUb;
+} yuv_table_row;
+
+static const yuv_table_row yuv_table[256] = {
+    //  Y    Vr    Vg    Ug    Ub     // #
+    {  -18, -204,   50,  104, -258 }, // 0
+    {  -17, -202,   49,  103, -256 }, // 1
+    {  -16, -201,   49,  102, -254 }, // 2
+    {  -15, -199,   48,  101, -252 }, // 3
+    {  -13, -197,   48,  100, -250 }, // 4
+    {  -12, -196,   48,   99, -248 }, // 5
+    {  -11, -194,   47,   99, -246 }, // 6
+    {  -10, -193,   47,   98, -244 }, // 7
+    {   -9, -191,   46,   97, -242 }, // 8
+    {   -8, -189,   46,   96, -240 }, // 9
+    {   -6, -188,   46,   95, -238 }, // 10
+    {   -5, -186,   45,   95, -236 }, // 11
+    {   -4, -185,   45,   94, -234 }, // 12
+    {   -3, -183,   44,   93, -232 }, // 13
+    {   -2, -181,   44,   92, -230 }, // 14
+    {   -1, -180,   44,   91, -228 }, // 15
+    {    0, -178,   43,   91, -226 }, // 16
+    {    1, -177,   43,   90, -223 }, // 17
+    {    2, -175,   43,   89, -221 }, // 18
+    {    3, -173,   42,   88, -219 }, // 19
+    {    4, -172,   42,   87, -217 }, // 20
+    {    5, -170,   41,   86, -215 }, // 21
+    {    6, -169,   41,   86, -213 }, // 22
+    {    8, -167,   41,   85, -211 }, // 23
+    {    9, -165,   40,   84, -209 }, // 24
+    {   10, -164,   40,   83, -207 }, // 25
+    {   11, -162,   39,   82, -205 }, // 26
+    {   12, -161,   39,   82, -203 }, // 27
+    {   13, -159,   39,   81, -201 }, // 28
+    {   15, -158,   38,   80, -199 }, // 29
+    {   16, -156,   38,   79, -197 }, // 30
+    {   17, -154,   37,   78, -195 }, // 31
+    {   18, -153,   37,   78, -193 }, // 32
+    {   19, -151,   37,   77, -191 }, // 33
+    {   20, -150,   36,   76, -189 }, // 34
+    {   22, -148,   36,   75, -187 }, // 35
+    {   23, -146,   35,   74, -185 }, // 36
+    {   24, -145,   35,   73, -183 }, // 37
+    {   25, -143,   35,   73, -181 }, // 38
+    {   26, -142,   34,   72, -179 }, // 39
+    {   27, -140,   34,   71, -177 }, // 40
+    {   29, -138,   34,   70, -175 }, // 41
+    {   30, -137,   33,   69, -173 }, // 42
+    {   31, -135,   33,   69, -171 }, // 43
+    {   32, -134,   32,   68, -169 }, // 44
+    {   33, -132,   32,   67, -167 }, // 45
+    {   34, -130,   32,   66, -165 }, // 46
+    {   36, -129,   31,   65, -163 }, // 47
+    {   37, -127,   31,   65, -161 }, // 48
+    {   38, -126,   30,   64, -159 }, // 49
+    {   39, -124,   30,   63, -157 }, // 50
+    {   40, -122,   30,   62, -155 }, // 51
+    {   41, -121,   29,   61, -153 }, // 52
+    {   43, -119,   29,   60, -151 }, // 53
+    {   44, -118,   28,   60, -149 }, // 54
+    {   45, -116,   28,   59, -147 }, // 55
+    {   46, -114,   28,   58, -145 }, // 56
+    {   47, -113,   27,   57, -143 }, // 57
+    {   48, -111,   27,   56, -141 }, // 58
+    {   50, -110,   26,   56, -139 }, // 59
+    {   51, -108,   26,   55, -137 }, // 60
+    {   52, -106,   26,   54, -135 }, // 61
+    {   53, -105,   25,   53, -133 }, // 62
+    {   54, -103,   25,   52, -131 }, // 63
+    {   55, -102,   25,   52, -129 }, // 64
+    {   57, -100,   24,   51, -127 }, // 65
+    {   58,  -98,   24,   50, -125 }, // 66
+    {   59,  -97,   23,   49, -123 }, // 67
+    {   60,  -95,   23,   48, -121 }, // 68
+    {   61,  -94,   23,   47, -119 }, // 69
+    {   62,  -92,   22,   47, -117 }, // 70
+    {   64,  -90,   22,   46, -115 }, // 71
+    {   65,  -89,   21,   45, -113 }, // 72
+    {   66,  -87,   21,   44, -110 }, // 73
+    {   67,  -86,   21,   43, -108 }, // 74
+    {   68,  -84,   20,   43, -106 }, // 75
+    {   69,  -82,   20,   42, -104 }, // 76
+    {   71,  -81,   19,   41, -102 }, // 77
+    {   72,  -79,   19,   40, -100 }, // 78
+    {   73,  -78,   19,   39,  -98 }, // 79
+    {   74,  -76,   18,   39,  -96 }, // 80
+    {   75,  -75,   18,   38,  -94 }, // 81
+    {   76,  -73,   17,   37,  -92 }, // 82
+    {   77,  -71,   17,   36,  -90 }, // 83
+    {   79,  -70,   17,   35,  -88 }, // 84
+    {   80,  -68,   16,   34,  -86 }, // 85
+    {   81,  -67,   16,   34,  -84 }, // 86
+    {   82,  -65,   16,   33,  -82 }, // 87
+    {   83,  -63,   15,   32,  -80 }, // 88
+    {   84,  -62,   15,   31,  -78 }, // 89
+    {   86,  -60,   14,   30,  -76 }, // 90
+    {   87,  -59,   14,   30,  -74 }, // 91
+    {   88,  -57,   14,   29,  -72 }, // 92
+    {   89,  -55,   13,   28,  -70 }, // 93
+    {   90,  -54,   13,   27,  -68 }, // 94
+    {   91,  -52,   12,   26,  -66 }, // 95
+    {   93,  -51,   12,   26,  -64 }, // 96
+    {   94,  -49,   12,   25,  -62 }, // 97
+    {   95,  -47,   11,   24,  -60 }, // 98
+    {   96,  -46,   11,   23,  -58 }, // 99
+    {   97,  -44,   10,   22,  -56 }, // 100
+    {   98,  -43,   10,   21,  -54 }, // 101
+    {  100,  -41,   10,   21,  -52 }, // 102
+    {  101,  -39,    9,   20,  -50 }, // 103
+    {  102,  -38,    9,   19,  -48 }, // 104
+    {  103,  -36,    8,   18,  -46 }, // 105
+    {  104,  -35,    8,   17,  -44 }, // 106
+    {  105,  -33,    8,   17,  -42 }, // 107
+    {  107,  -31,    7,   16,  -40 }, // 108
+    {  108,  -30,    7,   15,  -38 }, // 109
+    {  109,  -28,    7,   14,  -36 }, // 110
+    {  110,  -27,    6,   13,  -34 }, // 111
+    {  111,  -25,    6,   13,  -32 }, // 112
+    {  112,  -23,    5,   12,  -30 }, // 113
+    {  114,  -22,    5,   11,  -28 }, // 114
+    {  115,  -20,    5,   10,  -26 }, // 115
+    {  116,  -19,    4,    9,  -24 }, // 116
+    {  117,  -17,    4,    8,  -22 }, // 117
+    {  118,  -15,    3,    8,  -20 }, // 118
+    {  119,  -14,    3,    7,  -18 }, // 119
+    {  121,  -12,    3,    6,  -16 }, // 120
+    {  122,  -11,    2,    5,  -14 }, // 121
+    {  123,   -9,    2,    4,  -12 }, // 122
+    {  124,   -7,    1,    4,  -10 }, // 123
+    {  125,   -6,    1,    3,   -8 }, // 124
+    {  126,   -4,    1,    2,   -6 }, // 125
+    {  128,   -3,    0,    1,   -4 }, // 126
+    {  129,   -1,    0,    0,   -2 }, // 127
+    {  130,    0,    0,    0,    0 }, // 128
+    {  131,    1,    0,    0,    2 }, // 129
+    {  132,    3,    0,   -1,    4 }, // 130
+    {  133,    4,   -1,   -2,    6 }, // 131
+    {  135,    6,   -1,   -3,    8 }, // 132
+    {  136,    7,   -1,   -4,   10 }, // 133
+    {  137,    9,   -2,   -4,   12 }, // 134
+    {  138,   11,   -2,   -5,   14 }, // 135
+    {  139,   12,   -3,   -6,   16 }, // 136
+    {  140,   14,   -3,   -7,   18 }, // 137
+    {  142,   15,   -3,   -8,   20 }, // 138
+    {  143,   17,   -4,   -8,   22 }, // 139
+    {  144,   19,   -4,   -9,   24 }, // 140
+    {  145,   20,   -5,  -10,   26 }, // 141
+    {  146,   22,   -5,  -11,   28 }, // 142
+    {  147,   23,   -5,  -12,   30 }, // 143
+    {  148,   25,   -6,  -13,   32 }, // 144
+    {  150,   27,   -6,  -13,   34 }, // 145
+    {  151,   28,   -7,  -14,   36 }, // 146
+    {  152,   30,   -7,  -15,   38 }, // 147
+    {  153,   31,   -7,  -16,   40 }, // 148
+    {  154,   33,   -8,  -17,   42 }, // 149
+    {  155,   35,   -8,  -17,   44 }, // 150
+    {  157,   36,   -8,  -18,   46 }, // 151
+    {  158,   38,   -9,  -19,   48 }, // 152
+    {  159,   39,   -9,  -20,   50 }, // 153
+    {  160,   41,  -10,  -21,   52 }, // 154
+    {  161,   43,  -10,  -21,   54 }, // 155
+    {  162,   44,  -10,  -22,   56 }, // 156
+    {  164,   46,  -11,  -23,   58 }, // 157
+    {  165,   47,  -11,  -24,   60 }, // 158
+    {  166,   49,  -12,  -25,   62 }, // 159
+    {  167,   51,  -12,  -26,   64 }, // 160
+    {  168,   52,  -12,  -26,   66 }, // 161
+    {  169,   54,  -13,  -27,   68 }, // 162
+    {  171,   55,  -13,  -28,   70 }, // 163
+    {  172,   57,  -14,  -29,   72 }, // 164
+    {  173,   59,  -14,  -30,   74 }, // 165
+    {  174,   60,  -14,  -30,   76 }, // 166
+    {  175,   62,  -15,  -31,   78 }, // 167
+    {  176,   63,  -15,  -32,   80 }, // 168
+    {  178,   65,  -16,  -33,   82 }, // 169
+    {  179,   67,  -16,  -34,   84 }, // 170
+    {  180,   68,  -16,  -34,   86 }, // 171
+    {  181,   70,  -17,  -35,   88 }, // 172
+    {  182,   71,  -17,  -36,   90 }, // 173
+    {  183,   73,  -17,  -37,   92 }, // 174
+    {  185,   75,  -18,  -38,   94 }, // 175
+    {  186,   76,  -18,  -39,   96 }, // 176
+    {  187,   78,  -19,  -39,   98 }, // 177
+    {  188,   79,  -19,  -40,  100 }, // 178
+    {  189,   81,  -19,  -41,  102 }, // 179
+    {  190,   82,  -20,  -42,  104 }, // 180
+    {  192,   84,  -20,  -43,  106 }, // 181
+    {  193,   86,  -21,  -43,  108 }, // 182
+    {  194,   87,  -21,  -44,  110 }, // 183
+    {  195,   89,  -21,  -45,  113 }, // 184
+    {  196,   90,  -22,  -46,  115 }, // 185
+    {  197,   92,  -22,  -47,  117 }, // 186
+    {  199,   94,  -23,  -47,  119 }, // 187
+    {  200,   95,  -23,  -48,  121 }, // 188
+    {  201,   97,  -23,  -49,  123 }, // 189
+    {  202,   98,  -24,  -50,  125 }, // 190
+    {  203,  100,  -24,  -51,  127 }, // 191
+    {  204,  102,  -25,  -52,  129 }, // 192
+    {  206,  103,  -25,  -52,  131 }, // 193
+    {  207,  105,  -25,  -53,  133 }, // 194
+    {  208,  106,  -26,  -54,  135 }, // 195
+    {  209,  108,  -26,  -55,  137 }, // 196
+    {  210,  110,  -26,  -56,  139 }, // 197
+    {  211,  111,  -27,  -56,  141 }, // 198
+    {  213,  113,  -27,  -57,  143 }, // 199
+    {  214,  114,  -28,  -58,  145 }, // 200
+    {  215,  116,  -28,  -59,  147 }, // 201
+    {  216,  118,  -28,  -60,  149 }, // 202
+    {  217,  119,  -29,  -60,  151 }, // 203
+    {  218,  121,  -29,  -61,  153 }, // 204
+    {  219,  122,  -30,  -62,  155 }, // 205
+    {  221,  124,  -30,  -63,  157 }, // 206
+    {  222,  126,  -30,  -64,  159 }, // 207
+    {  223,  127,  -31,  -65,  161 }, // 208
+    {  224,  129,  -31,  -65,  163 }, // 209
+    {  225,  130,  -32,  -66,  165 }, // 210
+    {  226,  132,  -32,  -67,  167 }, // 211
+    {  228,  134,  -32,  -68,  169 }, // 212
+    {  229,  135,  -33,  -69,  171 }, // 213
+    {  230,  137,  -33,  -69,  173 }, // 214
+    {  231,  138,  -34,  -70,  175 }, // 215
+    {  232,  140,  -34,  -71,  177 }, // 216
+    {  233,  142,  -34,  -72,  179 }, // 217
+    {  235,  143,  -35,  -73,  181 }, // 218
+    {  236,  145,  -35,  -73,  183 }, // 219
+    {  237,  146,  -35,  -74,  185 }, // 220
+    {  238,  148,  -36,  -75,  187 }, // 221
+    {  239,  150,  -36,  -76,  189 }, // 222
+    {  240,  151,  -37,  -77,  191 }, // 223
+    {  242,  153,  -37,  -78,  193 }, // 224
+    {  243,  154,  -37,  -78,  195 }, // 225
+    {  244,  156,  -38,  -79,  197 }, // 226
+    {  245,  158,  -38,  -80,  199 }, // 227
+    {  246,  159,  -39,  -81,  201 }, // 228
+    {  247,  161,  -39,  -82,  203 }, // 229
+    {  249,  162,  -39,  -82,  205 }, // 230
+    {  250,  164,  -40,  -83,  207 }, // 231
+    {  251,  165,  -40,  -84,  209 }, // 232
+    {  252,  167,  -41,  -85,  211 }, // 233
+    {  253,  169,  -41,  -86,  213 }, // 234
+    {  254,  170,  -41,  -86,  215 }, // 235
+    {  256,  172,  -42,  -87,  217 }, // 236
+    {  257,  173,  -42,  -88,  219 }, // 237
+    {  258,  175,  -43,  -89,  221 }, // 238
+    {  259,  177,  -43,  -90,  223 }, // 239
+    {  260,  178,  -43,  -91,  226 }, // 240
+    {  261,  180,  -44,  -91,  228 }, // 241
+    {  263,  181,  -44,  -92,  230 }, // 242
+    {  264,  183,  -44,  -93,  232 }, // 243
+    {  265,  185,  -45,  -94,  234 }, // 244
+    {  266,  186,  -45,  -95,  236 }, // 245
+    {  267,  188,  -46,  -95,  238 }, // 246
+    {  268,  189,  -46,  -96,  240 }, // 247
+    {  270,  191,  -46,  -97,  242 }, // 248
+    {  271,  193,  -47,  -98,  244 }, // 249
+    {  272,  194,  -47,  -99,  246 }, // 250
+    {  273,  196,  -48,  -99,  248 }, // 251
+    {  274,  197,  -48, -100,  250 }, // 252
+    {  275,  199,  -48, -101,  252 }, // 253
+    {  277,  201,  -49, -102,  254 }, // 254
+    {  278,  202,  -49, -103,  256 }  // 255
+};
+
+#define YUYV_CONSTRAIN(v) ((v)<0)?0:(((v)>255)?255:(v))
+
+void IRAM_ATTR yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b)
+{
+    int16_t ri, gi, bi;
+
+    ri = yuv_table[y].vY + yuv_table[v].vVr;
+    gi = yuv_table[y].vY + yuv_table[u].vUg + yuv_table[v].vVg;
+    bi = yuv_table[y].vY + yuv_table[u].vUb;
+
+    *r = YUYV_CONSTRAIN(ri);
+    *g = YUYV_CONSTRAIN(gi);
+    *b = YUYV_CONSTRAIN(bi);
+}

+ 29 - 0
code/lib/conversions/yuv.h

@@ -0,0 +1,29 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef _CONVERSIONS_YUV_H_
+#define _CONVERSIONS_YUV_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+void yuv2rgb(uint8_t y, uint8_t u, uint8_t v, uint8_t *r, uint8_t *g, uint8_t *b);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _CONVERSIONS_YUV_H_ */

+ 1520 - 0
code/lib/driver/camera.c

@@ -0,0 +1,1520 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "time.h"
+#include "sys/time.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+#include "soc/soc.h"
+#include "soc/gpio_sig_map.h"
+#include "soc/i2s_reg.h"
+#include "soc/i2s_struct.h"
+#include "soc/io_mux_reg.h"
+#include "driver/gpio.h"
+#include "driver/rtc_io.h"
+#include "driver/periph_ctrl.h"
+#include "esp_intr_alloc.h"
+#include "esp_system.h"
+#include "nvs_flash.h"
+#include "nvs.h"
+#include "sensor.h"
+#include "sccb.h"
+#include "esp_camera.h"
+#include "camera_common.h"
+#include "xclk.h"
+
+#define CONFIG_OV2640_SUPPORT 1
+//#define CONFIG_OV7725_SUPPORT 1
+//#define CONFIG_OV7725_SUPPORT 1
+//#define CONFIG_OV3660_SUPPORT 1
+//#define CONFIG_OV5640_SUPPORT 1
+
+
+#if CONFIG_OV2640_SUPPORT
+#include "ov2640.h"
+#endif
+#if CONFIG_OV7725_SUPPORT
+#include "ov7725.h"
+#endif
+#if CONFIG_OV3660_SUPPORT
+#include "ov3660.h"
+#endif
+#if CONFIG_OV5640_SUPPORT
+#include "ov5640.h"
+#endif
+
+typedef enum {
+    CAMERA_NONE = 0,
+    CAMERA_UNKNOWN = 1,
+    CAMERA_OV7725 = 7725,
+    CAMERA_OV2640 = 2640,
+    CAMERA_OV3660 = 3660,
+    CAMERA_OV5640 = 5640,
+} camera_model_t;
+
+#define REG_PID        0x0A
+#define REG_VER        0x0B
+#define REG_MIDH       0x1C
+#define REG_MIDL       0x1D
+
+#define REG16_CHIDH     0x300A
+#define REG16_CHIDL     0x300B
+
+#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
+#include "esp32-hal-log.h"
+#define TAG ""
+#else
+#include "esp_log.h"
+static const char* TAG = "camera";
+#endif
+static const char* CAMERA_SENSOR_NVS_KEY = "sensor";
+static const char* CAMERA_PIXFORMAT_NVS_KEY = "pixformat";
+
+typedef void (*dma_filter_t)(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst);
+
+typedef struct camera_fb_s {
+    uint8_t * buf;
+    size_t len;
+    size_t width;
+    size_t height;
+    pixformat_t format;
+    struct timeval timestamp;
+    size_t size;
+    uint8_t ref;
+    uint8_t bad;
+    struct camera_fb_s * next;
+} camera_fb_int_t;
+
+typedef struct fb_s {
+    uint8_t * buf;
+    size_t len;
+    struct fb_s * next;
+} fb_item_t;
+
+typedef struct {
+    camera_config_t config;
+    sensor_t sensor;
+
+    camera_fb_int_t *fb;
+    size_t fb_size;
+    size_t data_size;
+
+    size_t width;
+    size_t height;
+    size_t in_bytes_per_pixel;
+    size_t fb_bytes_per_pixel;
+
+    size_t dma_received_count;
+    size_t dma_filtered_count;
+    size_t dma_per_line;
+    size_t dma_buf_width;
+    size_t dma_sample_count;
+
+    lldesc_t *dma_desc;
+    dma_elem_t **dma_buf;
+    size_t dma_desc_count;
+    size_t dma_desc_cur;
+
+    i2s_sampling_mode_t sampling_mode;
+    dma_filter_t dma_filter;
+    intr_handle_t i2s_intr_handle;
+    QueueHandle_t data_ready;
+    QueueHandle_t fb_in;
+    QueueHandle_t fb_out;
+
+    SemaphoreHandle_t frame_ready;
+    TaskHandle_t dma_filter_task;
+} camera_state_t;
+
+camera_state_t* s_state = NULL;
+
+static void i2s_init();
+static int i2s_run();
+static void IRAM_ATTR vsync_isr(void* arg);
+static void IRAM_ATTR i2s_isr(void* arg);
+static esp_err_t dma_desc_init();
+static void dma_desc_deinit();
+static void dma_filter_task(void *pvParameters);
+static void dma_filter_grayscale(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst);
+static void dma_filter_grayscale_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst);
+static void dma_filter_yuyv(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst);
+static void dma_filter_yuyv_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst);
+static void dma_filter_jpeg(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst);
+static void i2s_stop(bool* need_yield);
+
+static bool is_hs_mode()
+{
+    return s_state->config.xclk_freq_hz > 10000000;
+}
+
+static size_t i2s_bytes_per_sample(i2s_sampling_mode_t mode)
+{
+    switch(mode) {
+    case SM_0A00_0B00:
+        return 4;
+    case SM_0A0B_0B0C:
+        return 4;
+    case SM_0A0B_0C0D:
+        return 2;
+    default:
+        assert(0 && "invalid sampling mode");
+        return 0;
+    }
+}
+
+static int IRAM_ATTR _gpio_get_level(gpio_num_t gpio_num)
+{
+    if (gpio_num < 32) {
+        return (GPIO.in >> gpio_num) & 0x1;
+    } else {
+        return (GPIO.in1.data >> (gpio_num - 32)) & 0x1;
+    }
+}
+
+static void IRAM_ATTR vsync_intr_disable()
+{
+    gpio_set_intr_type(s_state->config.pin_vsync, GPIO_INTR_DISABLE);
+}
+
+static void vsync_intr_enable()
+{
+    gpio_set_intr_type(s_state->config.pin_vsync, GPIO_INTR_NEGEDGE);
+}
+
+static int skip_frame()
+{
+    if (s_state == NULL) {
+        return -1;
+    }
+    int64_t st_t = esp_timer_get_time();
+    while (_gpio_get_level(s_state->config.pin_vsync) == 0) {
+        if((esp_timer_get_time() - st_t) > 1000000LL){
+            goto timeout;
+        }
+    }
+    while (_gpio_get_level(s_state->config.pin_vsync) != 0) {
+        if((esp_timer_get_time() - st_t) > 1000000LL){
+            goto timeout;
+        }
+    }
+    while (_gpio_get_level(s_state->config.pin_vsync) == 0) {
+        if((esp_timer_get_time() - st_t) > 1000000LL){
+            goto timeout;
+        }
+    }
+    return 0;
+
+timeout:
+    ESP_LOGE(TAG, "Timeout waiting for VSYNC");
+    return -1;
+}
+
+static void camera_fb_deinit()
+{
+    camera_fb_int_t * _fb1 = s_state->fb, * _fb2 = NULL;
+    while(s_state->fb) {
+        _fb2 = s_state->fb;
+        s_state->fb = _fb2->next;
+        if(_fb2->next == _fb1) {
+            s_state->fb = NULL;
+        }
+        free(_fb2->buf);
+        free(_fb2);
+    }
+}
+
+static esp_err_t camera_fb_init(size_t count)
+{
+    if(!count) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    camera_fb_deinit();
+
+    ESP_LOGI(TAG, "Allocating %u frame buffers (%d KB total)", count, (s_state->fb_size * count) / 1024);
+
+    camera_fb_int_t * _fb = NULL, * _fb1 = NULL, * _fb2 = NULL;
+    for(size_t i = 0; i < count; i++) {
+        _fb2 = (camera_fb_int_t *)malloc(sizeof(camera_fb_int_t));
+        if(!_fb2) {
+            goto fail;
+        }
+        memset(_fb2, 0, sizeof(camera_fb_int_t));
+        _fb2->size = s_state->fb_size;
+        _fb2->buf = (uint8_t*) calloc(_fb2->size, 1);
+        if(!_fb2->buf) {
+            ESP_LOGI(TAG, "Allocating %d KB frame buffer in PSRAM", s_state->fb_size/1024);
+            _fb2->buf = (uint8_t*) heap_caps_calloc(_fb2->size, 1, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+        } else {
+            ESP_LOGI(TAG, "Allocating %d KB frame buffer in OnBoard RAM", s_state->fb_size/1024);
+        }
+        if(!_fb2->buf) {
+            free(_fb2);
+            ESP_LOGE(TAG, "Allocating %d KB frame buffer Failed", s_state->fb_size/1024);
+            goto fail;
+        }
+        memset(_fb2->buf, 0, _fb2->size);
+        _fb2->next = _fb;
+        _fb = _fb2;
+        if(!i) {
+            _fb1 = _fb2;
+        }
+    }
+    if(_fb1) {
+        _fb1->next = _fb;
+    }
+
+    s_state->fb = _fb;//load first buffer
+
+    return ESP_OK;
+
+fail:
+    while(_fb) {
+        _fb2 = _fb;
+        _fb = _fb->next;
+        free(_fb2->buf);
+        free(_fb2);
+    }
+    return ESP_ERR_NO_MEM;
+}
+
+static esp_err_t dma_desc_init()
+{
+    assert(s_state->width % 4 == 0);
+    size_t line_size = s_state->width * s_state->in_bytes_per_pixel *
+                       i2s_bytes_per_sample(s_state->sampling_mode);
+    ESP_LOGD(TAG, "Line width (for DMA): %d bytes", line_size);
+    size_t dma_per_line = 1;
+    size_t buf_size = line_size;
+    while (buf_size >= 4096) {
+        buf_size /= 2;
+        dma_per_line *= 2;
+    }
+    size_t dma_desc_count = dma_per_line * 4;
+    s_state->dma_buf_width = line_size;
+    s_state->dma_per_line = dma_per_line;
+    s_state->dma_desc_count = dma_desc_count;
+    ESP_LOGD(TAG, "DMA buffer size: %d, DMA buffers per line: %d", buf_size, dma_per_line);
+    ESP_LOGD(TAG, "DMA buffer count: %d", dma_desc_count);
+    ESP_LOGD(TAG, "DMA buffer total: %d bytes", buf_size * dma_desc_count);
+
+    s_state->dma_buf = (dma_elem_t**) malloc(sizeof(dma_elem_t*) * dma_desc_count);
+    if (s_state->dma_buf == NULL) {
+        return ESP_ERR_NO_MEM;
+    }
+    s_state->dma_desc = (lldesc_t*) malloc(sizeof(lldesc_t) * dma_desc_count);
+    if (s_state->dma_desc == NULL) {
+        return ESP_ERR_NO_MEM;
+    }
+    size_t dma_sample_count = 0;
+    for (int i = 0; i < dma_desc_count; ++i) {
+        ESP_LOGD(TAG, "Allocating DMA buffer #%d, size=%d", i, buf_size);
+        dma_elem_t* buf = (dma_elem_t*) malloc(buf_size);
+        if (buf == NULL) {
+            return ESP_ERR_NO_MEM;
+        }
+        s_state->dma_buf[i] = buf;
+        ESP_LOGV(TAG, "dma_buf[%d]=%p", i, buf);
+
+        lldesc_t* pd = &s_state->dma_desc[i];
+        pd->length = buf_size;
+        if (s_state->sampling_mode == SM_0A0B_0B0C &&
+                (i + 1) % dma_per_line == 0) {
+            pd->length -= 4;
+        }
+        dma_sample_count += pd->length / 4;
+        pd->size = pd->length;
+        pd->owner = 1;
+        pd->sosf = 1;
+        pd->buf = (uint8_t*) buf;
+        pd->offset = 0;
+        pd->empty = 0;
+        pd->eof = 1;
+        pd->qe.stqe_next = &s_state->dma_desc[(i + 1) % dma_desc_count];
+    }
+    s_state->dma_sample_count = dma_sample_count;
+    return ESP_OK;
+}
+
+static void dma_desc_deinit()
+{
+    if (s_state->dma_buf) {
+        for (int i = 0; i < s_state->dma_desc_count; ++i) {
+            free(s_state->dma_buf[i]);
+        }
+    }
+    free(s_state->dma_buf);
+    free(s_state->dma_desc);
+}
+
+static inline void IRAM_ATTR i2s_conf_reset()
+{
+    const uint32_t lc_conf_reset_flags = I2S_IN_RST_M | I2S_AHBM_RST_M
+                                         | I2S_AHBM_FIFO_RST_M;
+    I2S0.lc_conf.val |= lc_conf_reset_flags;
+    I2S0.lc_conf.val &= ~lc_conf_reset_flags;
+
+    const uint32_t conf_reset_flags = I2S_RX_RESET_M | I2S_RX_FIFO_RESET_M
+                                      | I2S_TX_RESET_M | I2S_TX_FIFO_RESET_M;
+    I2S0.conf.val |= conf_reset_flags;
+    I2S0.conf.val &= ~conf_reset_flags;
+    while (I2S0.state.rx_fifo_reset_back) {
+        ;
+    }
+}
+
+static void i2s_init()
+{
+    camera_config_t* config = &s_state->config;
+
+    // Configure input GPIOs
+    gpio_num_t pins[] = {
+        config->pin_d7,
+        config->pin_d6,
+        config->pin_d5,
+        config->pin_d4,
+        config->pin_d3,
+        config->pin_d2,
+        config->pin_d1,
+        config->pin_d0,
+        config->pin_vsync,
+        config->pin_href,
+        config->pin_pclk
+    };
+    gpio_config_t conf = {
+        .mode = GPIO_MODE_INPUT,
+        .pull_up_en = GPIO_PULLUP_ENABLE,
+        .pull_down_en = GPIO_PULLDOWN_DISABLE,
+        .intr_type = GPIO_INTR_DISABLE
+    };
+    for (int i = 0; i < sizeof(pins) / sizeof(gpio_num_t); ++i) {
+        if (rtc_gpio_is_valid_gpio(pins[i])) {
+            rtc_gpio_deinit(pins[i]);
+        }
+        conf.pin_bit_mask = 1LL << pins[i];
+        gpio_config(&conf);
+    }
+
+    // Route input GPIOs to I2S peripheral using GPIO matrix
+    gpio_matrix_in(config->pin_d0, I2S0I_DATA_IN0_IDX, false);
+    gpio_matrix_in(config->pin_d1, I2S0I_DATA_IN1_IDX, false);
+    gpio_matrix_in(config->pin_d2, I2S0I_DATA_IN2_IDX, false);
+    gpio_matrix_in(config->pin_d3, I2S0I_DATA_IN3_IDX, false);
+    gpio_matrix_in(config->pin_d4, I2S0I_DATA_IN4_IDX, false);
+    gpio_matrix_in(config->pin_d5, I2S0I_DATA_IN5_IDX, false);
+    gpio_matrix_in(config->pin_d6, I2S0I_DATA_IN6_IDX, false);
+    gpio_matrix_in(config->pin_d7, I2S0I_DATA_IN7_IDX, false);
+    gpio_matrix_in(config->pin_vsync, I2S0I_V_SYNC_IDX, false);
+    gpio_matrix_in(0x38, I2S0I_H_SYNC_IDX, false);
+    gpio_matrix_in(config->pin_href, I2S0I_H_ENABLE_IDX, false);
+    gpio_matrix_in(config->pin_pclk, I2S0I_WS_IN_IDX, false);
+
+    // Enable and configure I2S peripheral
+    periph_module_enable(PERIPH_I2S0_MODULE);
+    // Toggle some reset bits in LC_CONF register
+    // Toggle some reset bits in CONF register
+    i2s_conf_reset();
+    // Enable slave mode (sampling clock is external)
+    I2S0.conf.rx_slave_mod = 1;
+    // Enable parallel mode
+    I2S0.conf2.lcd_en = 1;
+    // Use HSYNC/VSYNC/HREF to control sampling
+    I2S0.conf2.camera_en = 1;
+    // Configure clock divider
+    I2S0.clkm_conf.clkm_div_a = 1;
+    I2S0.clkm_conf.clkm_div_b = 0;
+    I2S0.clkm_conf.clkm_div_num = 2;
+    // FIFO will sink data to DMA
+    I2S0.fifo_conf.dscr_en = 1;
+    // FIFO configuration
+    I2S0.fifo_conf.rx_fifo_mod = s_state->sampling_mode;
+    I2S0.fifo_conf.rx_fifo_mod_force_en = 1;
+    I2S0.conf_chan.rx_chan_mod = 1;
+    // Clear flags which are used in I2S serial mode
+    I2S0.sample_rate_conf.rx_bits_mod = 0;
+    I2S0.conf.rx_right_first = 0;
+    I2S0.conf.rx_msb_right = 0;
+    I2S0.conf.rx_msb_shift = 0;
+    I2S0.conf.rx_mono = 0;
+    I2S0.conf.rx_short_sync = 0;
+    I2S0.timing.val = 0;
+    I2S0.timing.rx_dsync_sw = 1;
+
+    // Allocate I2S interrupt, keep it disabled
+    ESP_ERROR_CHECK(esp_intr_alloc(ETS_I2S0_INTR_SOURCE,
+                   ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM,
+                   &i2s_isr, NULL, &s_state->i2s_intr_handle));
+}
+
+static void IRAM_ATTR i2s_start_bus()
+{
+    s_state->dma_desc_cur = 0;
+    s_state->dma_received_count = 0;
+    //s_state->dma_filtered_count = 0;
+    esp_intr_disable(s_state->i2s_intr_handle);
+    i2s_conf_reset();
+
+    I2S0.rx_eof_num = s_state->dma_sample_count;
+    I2S0.in_link.addr = (uint32_t) &s_state->dma_desc[0];
+    I2S0.in_link.start = 1;
+    I2S0.int_clr.val = I2S0.int_raw.val;
+    I2S0.int_ena.val = 0;
+    I2S0.int_ena.in_done = 1;
+
+    esp_intr_enable(s_state->i2s_intr_handle);
+    I2S0.conf.rx_start = 1;
+    if (s_state->config.pixel_format == PIXFORMAT_JPEG) {
+        vsync_intr_enable();
+    }
+}
+
+static int i2s_run()
+{
+    for (int i = 0; i < s_state->dma_desc_count; ++i) {
+        lldesc_t* d = &s_state->dma_desc[i];
+        ESP_LOGV(TAG, "DMA desc %2d: %u %u %u %u %u %u %p %p",
+                 i, d->length, d->size, d->offset, d->eof, d->sosf, d->owner, d->buf, d->qe.stqe_next);
+        memset(s_state->dma_buf[i], 0, d->length);
+    }
+
+    // wait for frame
+    camera_fb_int_t * fb = s_state->fb;
+    while(s_state->config.fb_count > 1) {
+        while(s_state->fb->ref && s_state->fb->next != fb) {
+            s_state->fb = s_state->fb->next;
+        }
+        if(s_state->fb->ref == 0) {
+            break;
+        }
+        vTaskDelay(2);
+    }
+
+    //todo: wait for vsync
+    ESP_LOGV(TAG, "Waiting for negative edge on VSYNC");
+
+    int64_t st_t = esp_timer_get_time();
+    while (_gpio_get_level(s_state->config.pin_vsync) != 0) {
+        if((esp_timer_get_time() - st_t) > 1000000LL){
+            ESP_LOGE(TAG, "Timeout waiting for VSYNC");
+            return -1;
+        }
+    }
+    ESP_LOGV(TAG, "Got VSYNC");
+    i2s_start_bus();
+    return 0;
+}
+
+static void IRAM_ATTR i2s_stop_bus()
+{
+    esp_intr_disable(s_state->i2s_intr_handle);
+    vsync_intr_disable();
+    i2s_conf_reset();
+    I2S0.conf.rx_start = 0;
+}
+
+static void IRAM_ATTR i2s_stop(bool* need_yield)
+{
+    if(s_state->config.fb_count == 1 && !s_state->fb->bad) {
+        i2s_stop_bus();
+    } else {
+        s_state->dma_received_count = 0;
+    }
+
+    size_t val = SIZE_MAX;
+    BaseType_t higher_priority_task_woken;
+    BaseType_t ret = xQueueSendFromISR(s_state->data_ready, &val, &higher_priority_task_woken);
+    if(need_yield && !*need_yield) {
+        *need_yield = (ret == pdTRUE && higher_priority_task_woken == pdTRUE);
+    }
+}
+
+static void IRAM_ATTR signal_dma_buf_received(bool* need_yield)
+{
+    size_t dma_desc_filled = s_state->dma_desc_cur;
+    s_state->dma_desc_cur = (dma_desc_filled + 1) % s_state->dma_desc_count;
+    s_state->dma_received_count++;
+    if(!s_state->fb->ref && s_state->fb->bad){
+        *need_yield = false;
+        return;
+    }
+    BaseType_t higher_priority_task_woken;
+    BaseType_t ret = xQueueSendFromISR(s_state->data_ready, &dma_desc_filled, &higher_priority_task_woken);
+    if (ret != pdTRUE) {
+        if(!s_state->fb->ref) {
+            s_state->fb->bad = 1;
+        }
+        //ESP_EARLY_LOGW(TAG, "qsf:%d", s_state->dma_received_count);
+        //ets_printf("qsf:%d\n", s_state->dma_received_count);
+        //ets_printf("qovf\n");
+    }
+    *need_yield = (ret == pdTRUE && higher_priority_task_woken == pdTRUE);
+}
+
+static void IRAM_ATTR i2s_isr(void* arg)
+{
+    I2S0.int_clr.val = I2S0.int_raw.val;
+    bool need_yield = false;
+    signal_dma_buf_received(&need_yield);
+    if (s_state->config.pixel_format != PIXFORMAT_JPEG
+     && s_state->dma_received_count == s_state->height * s_state->dma_per_line) {
+        i2s_stop(&need_yield);
+    }
+    if (need_yield) {
+        portYIELD_FROM_ISR();
+    }
+}
+
+static void IRAM_ATTR vsync_isr(void* arg)
+{
+    GPIO.status1_w1tc.val = GPIO.status1.val;
+    GPIO.status_w1tc = GPIO.status;
+    bool need_yield = false;
+    //if vsync is low and we have received some data, frame is done
+    if (_gpio_get_level(s_state->config.pin_vsync) == 0) {
+        if(s_state->dma_received_count > 0) {
+            signal_dma_buf_received(&need_yield);
+            //ets_printf("end_vsync\n");
+            if(s_state->dma_filtered_count > 1 || s_state->fb->bad || s_state->config.fb_count > 1) {
+                i2s_stop(&need_yield);
+            }
+            //ets_printf("vs\n");
+        }
+        if(s_state->config.fb_count > 1 || s_state->dma_filtered_count < 2) {
+            I2S0.conf.rx_start = 0;
+            I2S0.in_link.start = 0;
+            I2S0.int_clr.val = I2S0.int_raw.val;
+            i2s_conf_reset();
+            s_state->dma_desc_cur = (s_state->dma_desc_cur + 1) % s_state->dma_desc_count;
+            //I2S0.rx_eof_num = s_state->dma_sample_count;
+            I2S0.in_link.addr = (uint32_t) &s_state->dma_desc[s_state->dma_desc_cur];
+            I2S0.in_link.start = 1;
+            I2S0.conf.rx_start = 1;
+            s_state->dma_received_count = 0;
+        }
+    }
+    if (need_yield) {
+        portYIELD_FROM_ISR();
+    }
+}
+
+static void IRAM_ATTR camera_fb_done()
+{
+    camera_fb_int_t * fb = NULL, * fb2 = NULL;
+    BaseType_t taskAwoken = 0;
+
+    if(s_state->config.fb_count == 1) {
+        xSemaphoreGive(s_state->frame_ready);
+        return;
+    }
+
+    fb = s_state->fb;
+    if(!fb->ref && fb->len) {
+        //add reference
+        fb->ref = 1;
+
+        //check if the queue is full
+        if(xQueueIsQueueFullFromISR(s_state->fb_out) == pdTRUE) {
+            //pop frame buffer from the queue
+            if(xQueueReceiveFromISR(s_state->fb_out, &fb2, &taskAwoken) == pdTRUE) {
+                //free the popped buffer
+                fb2->ref = 0;
+                fb2->len = 0;
+                //push the new frame to the end of the queue
+                xQueueSendFromISR(s_state->fb_out, &fb, &taskAwoken);
+            } else {
+                //queue is full and we could not pop a frame from it
+            }
+        } else {
+            //push the new frame to the end of the queue
+            xQueueSendFromISR(s_state->fb_out, &fb, &taskAwoken);
+        }
+    } else {
+        //frame was referenced or empty
+    }
+
+    //return buffers to be filled
+    while(xQueueReceiveFromISR(s_state->fb_in, &fb2, &taskAwoken) == pdTRUE) {
+        fb2->ref = 0;
+        fb2->len = 0;
+    }
+
+    //advance frame buffer only if the current one has data
+    if(s_state->fb->len) {
+        s_state->fb = s_state->fb->next;
+    }
+    //try to find the next free frame buffer
+    while(s_state->fb->ref && s_state->fb->next != fb) {
+        s_state->fb = s_state->fb->next;
+    }
+    //is the found frame buffer free?
+    if(!s_state->fb->ref) {
+        //buffer found. make sure it's empty
+        s_state->fb->len = 0;
+        *((uint32_t *)s_state->fb->buf) = 0;
+    } else {
+        //stay at the previous buffer
+        s_state->fb = fb;
+    }
+}
+
+static void IRAM_ATTR dma_finish_frame()
+{
+    size_t buf_len = s_state->width * s_state->fb_bytes_per_pixel / s_state->dma_per_line;
+
+    if(!s_state->fb->ref) {
+        // is the frame bad?
+        if(s_state->fb->bad){
+            s_state->fb->bad = 0;
+            s_state->fb->len = 0;
+            *((uint32_t *)s_state->fb->buf) = 0;
+            if(s_state->config.fb_count == 1) {
+                i2s_start_bus();
+            }
+            //ets_printf("bad\n");
+        } else {
+            s_state->fb->len = s_state->dma_filtered_count * buf_len;
+            if(s_state->fb->len) {
+                //find the end marker for JPEG. Data after that can be discarded
+                if(s_state->fb->format == PIXFORMAT_JPEG){
+                    uint8_t * dptr = &s_state->fb->buf[s_state->fb->len - 1];
+                    while(dptr > s_state->fb->buf){
+                        if(dptr[0] == 0xFF && dptr[1] == 0xD9 && dptr[2] == 0x00 && dptr[3] == 0x00){
+                            dptr += 2;
+                            s_state->fb->len = dptr - s_state->fb->buf;
+                            if((s_state->fb->len & 0x1FF) == 0){
+                                s_state->fb->len += 1;
+                            }
+                            if((s_state->fb->len % 100) == 0){
+                                s_state->fb->len += 1;
+                            }
+                            break;
+                        }
+                        dptr--;
+                    }
+                }
+                //send out the frame
+                camera_fb_done();
+            } else if(s_state->config.fb_count == 1){
+                //frame was empty?
+                i2s_start_bus();
+            } else {
+                //ets_printf("empty\n");
+            }
+        }
+    } else if(s_state->fb->len) {
+        camera_fb_done();
+    }
+    s_state->dma_filtered_count = 0;
+}
+
+static void IRAM_ATTR dma_filter_buffer(size_t buf_idx)
+{
+    //no need to process the data if frame is in use or is bad
+    if(s_state->fb->ref || s_state->fb->bad) {
+        return;
+    }
+
+    //check if there is enough space in the frame buffer for the new data
+    size_t buf_len = s_state->width * s_state->fb_bytes_per_pixel / s_state->dma_per_line;
+    size_t fb_pos = s_state->dma_filtered_count * buf_len;
+    if(fb_pos > s_state->fb_size - buf_len) {
+        //size_t processed = s_state->dma_received_count * buf_len;
+        //ets_printf("[%s:%u] ovf pos: %u, processed: %u\n", __FUNCTION__, __LINE__, fb_pos, processed);
+        return;
+    }
+
+    //convert I2S DMA buffer to pixel data
+    (*s_state->dma_filter)(s_state->dma_buf[buf_idx], &s_state->dma_desc[buf_idx], s_state->fb->buf + fb_pos);
+
+    //first frame buffer
+    if(!s_state->dma_filtered_count) {
+        //check for correct JPEG header
+        if(s_state->sensor.pixformat == PIXFORMAT_JPEG) {
+            uint32_t sig = *((uint32_t *)s_state->fb->buf) & 0xFFFFFF;
+            if(sig != 0xffd8ff) {
+                ets_printf("bh 0x%08x\n", sig);
+                s_state->fb->bad = 1;
+                return;
+            }
+        }
+        //set the frame properties
+        s_state->fb->width = resolution[s_state->sensor.status.framesize].width;
+        s_state->fb->height = resolution[s_state->sensor.status.framesize].height;
+        s_state->fb->format = s_state->sensor.pixformat;
+
+        uint64_t us = (uint64_t)esp_timer_get_time();
+        s_state->fb->timestamp.tv_sec = us / 1000000UL;
+        s_state->fb->timestamp.tv_usec = us % 1000000UL;
+    }
+    s_state->dma_filtered_count++;
+}
+
+static void IRAM_ATTR dma_filter_task(void *pvParameters)
+{
+    s_state->dma_filtered_count = 0;
+    while (true) {
+        size_t buf_idx;
+        if(xQueueReceive(s_state->data_ready, &buf_idx, portMAX_DELAY) == pdTRUE) {
+            if (buf_idx == SIZE_MAX) {
+                //this is the end of the frame
+                dma_finish_frame();
+            } else {
+                dma_filter_buffer(buf_idx);
+            }
+        }
+    }
+}
+
+static void IRAM_ATTR dma_filter_jpeg(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst)
+{
+    size_t end = dma_desc->length / sizeof(dma_elem_t) / 4;
+    // manually unrolling 4 iterations of the loop here
+    for (size_t i = 0; i < end; ++i) {
+        dst[0] = src[0].sample1;
+        dst[1] = src[1].sample1;
+        dst[2] = src[2].sample1;
+        dst[3] = src[3].sample1;
+        src += 4;
+        dst += 4;
+    }
+}
+
+static void IRAM_ATTR dma_filter_grayscale(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst)
+{
+    size_t end = dma_desc->length / sizeof(dma_elem_t) / 4;
+    for (size_t i = 0; i < end; ++i) {
+        // manually unrolling 4 iterations of the loop here
+        dst[0] = src[0].sample1;
+        dst[1] = src[1].sample1;
+        dst[2] = src[2].sample1;
+        dst[3] = src[3].sample1;
+        src += 4;
+        dst += 4;
+    }
+}
+
+static void IRAM_ATTR dma_filter_grayscale_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst)
+{
+    size_t end = dma_desc->length / sizeof(dma_elem_t) / 8;
+    for (size_t i = 0; i < end; ++i) {
+        // manually unrolling 4 iterations of the loop here
+        dst[0] = src[0].sample1;
+        dst[1] = src[2].sample1;
+        dst[2] = src[4].sample1;
+        dst[3] = src[6].sample1;
+        src += 8;
+        dst += 4;
+    }
+    // the final sample of a line in SM_0A0B_0B0C sampling mode needs special handling
+    if ((dma_desc->length & 0x7) != 0) {
+        dst[0] = src[0].sample1;
+        dst[1] = src[2].sample1;
+    }
+}
+
+static void IRAM_ATTR dma_filter_yuyv(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst)
+{
+    size_t end = dma_desc->length / sizeof(dma_elem_t) / 4;
+    for (size_t i = 0; i < end; ++i) {
+        dst[0] = src[0].sample1;//y0
+        dst[1] = src[0].sample2;//u
+        dst[2] = src[1].sample1;//y1
+        dst[3] = src[1].sample2;//v
+
+        dst[4] = src[2].sample1;//y0
+        dst[5] = src[2].sample2;//u
+        dst[6] = src[3].sample1;//y1
+        dst[7] = src[3].sample2;//v
+        src += 4;
+        dst += 8;
+    }
+}
+
+static void IRAM_ATTR dma_filter_yuyv_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst)
+{
+    size_t end = dma_desc->length / sizeof(dma_elem_t) / 8;
+    for (size_t i = 0; i < end; ++i) {
+        dst[0] = src[0].sample1;//y0
+        dst[1] = src[1].sample1;//u
+        dst[2] = src[2].sample1;//y1
+        dst[3] = src[3].sample1;//v
+
+        dst[4] = src[4].sample1;//y0
+        dst[5] = src[5].sample1;//u
+        dst[6] = src[6].sample1;//y1
+        dst[7] = src[7].sample1;//v
+        src += 8;
+        dst += 8;
+    }
+    if ((dma_desc->length & 0x7) != 0) {
+        dst[0] = src[0].sample1;//y0
+        dst[1] = src[1].sample1;//u
+        dst[2] = src[2].sample1;//y1
+        dst[3] = src[2].sample2;//v
+    }
+}
+
+static void IRAM_ATTR dma_filter_rgb888(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst)
+{
+    size_t end = dma_desc->length / sizeof(dma_elem_t) / 4;
+    uint8_t lb, hb;
+    for (size_t i = 0; i < end; ++i) {
+        hb = src[0].sample1;
+        lb = src[0].sample2;
+        dst[0] = (lb & 0x1F) << 3;
+        dst[1] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
+        dst[2] = hb & 0xF8;
+
+        hb = src[1].sample1;
+        lb = src[1].sample2;
+        dst[3] = (lb & 0x1F) << 3;
+        dst[4] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
+        dst[5] = hb & 0xF8;
+
+        hb = src[2].sample1;
+        lb = src[2].sample2;
+        dst[6] = (lb & 0x1F) << 3;
+        dst[7] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
+        dst[8] = hb & 0xF8;
+
+        hb = src[3].sample1;
+        lb = src[3].sample2;
+        dst[9] = (lb & 0x1F) << 3;
+        dst[10] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
+        dst[11] = hb & 0xF8;
+        src += 4;
+        dst += 12;
+    }
+}
+
+static void IRAM_ATTR dma_filter_rgb888_highspeed(const dma_elem_t* src, lldesc_t* dma_desc, uint8_t* dst)
+{
+    size_t end = dma_desc->length / sizeof(dma_elem_t) / 8;
+    uint8_t lb, hb;
+    for (size_t i = 0; i < end; ++i) {
+        hb = src[0].sample1;
+        lb = src[1].sample1;
+        dst[0] = (lb & 0x1F) << 3;
+        dst[1] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
+        dst[2] = hb & 0xF8;
+
+        hb = src[2].sample1;
+        lb = src[3].sample1;
+        dst[3] = (lb & 0x1F) << 3;
+        dst[4] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
+        dst[5] = hb & 0xF8;
+
+        hb = src[4].sample1;
+        lb = src[5].sample1;
+        dst[6] = (lb & 0x1F) << 3;
+        dst[7] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
+        dst[8] = hb & 0xF8;
+
+        hb = src[6].sample1;
+        lb = src[7].sample1;
+        dst[9] = (lb & 0x1F) << 3;
+        dst[10] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
+        dst[11] = hb & 0xF8;
+
+        src += 8;
+        dst += 12;
+    }
+    if ((dma_desc->length & 0x7) != 0) {
+        hb = src[0].sample1;
+        lb = src[1].sample1;
+        dst[0] = (lb & 0x1F) << 3;
+        dst[1] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
+        dst[2] = hb & 0xF8;
+
+        hb = src[2].sample1;
+        lb = src[2].sample2;
+        dst[3] = (lb & 0x1F) << 3;
+        dst[4] = (hb & 0x07) << 5 | (lb & 0xE0) >> 3;
+        dst[5] = hb & 0xF8;
+    }
+}
+
+/*
+ * Public Methods
+ * */
+
+esp_err_t camera_probe(const camera_config_t* config, camera_model_t* out_camera_model)
+{
+    if (s_state != NULL) {
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    s_state = (camera_state_t*) calloc(sizeof(*s_state), 1);
+    if (!s_state) {
+        return ESP_ERR_NO_MEM;
+    }
+
+    ESP_LOGD(TAG, "Enabling XCLK output");
+    camera_enable_out_clock(config);
+
+    ESP_LOGD(TAG, "Initializing SSCB");
+    SCCB_Init(config->pin_sscb_sda, config->pin_sscb_scl);
+	
+    if(config->pin_pwdn >= 0) {
+        ESP_LOGD(TAG, "Resetting camera by power down line");
+        gpio_config_t conf = { 0 };
+        conf.pin_bit_mask = 1LL << config->pin_pwdn;
+        conf.mode = GPIO_MODE_OUTPUT;
+        gpio_config(&conf);
+
+        // carefull, logic is inverted compared to reset pin
+        gpio_set_level(config->pin_pwdn, 1);
+        vTaskDelay(10 / portTICK_PERIOD_MS);
+        gpio_set_level(config->pin_pwdn, 0);
+        vTaskDelay(10 / portTICK_PERIOD_MS);
+    }
+
+    if(config->pin_reset >= 0) {
+        ESP_LOGD(TAG, "Resetting camera");
+        gpio_config_t conf = { 0 };
+        conf.pin_bit_mask = 1LL << config->pin_reset;
+        conf.mode = GPIO_MODE_OUTPUT;
+        gpio_config(&conf);
+
+        gpio_set_level(config->pin_reset, 0);
+        vTaskDelay(10 / portTICK_PERIOD_MS);
+        gpio_set_level(config->pin_reset, 1);
+        vTaskDelay(10 / portTICK_PERIOD_MS);
+    }
+
+    ESP_LOGD(TAG, "Searching for camera address");
+    vTaskDelay(10 / portTICK_PERIOD_MS);
+    uint8_t slv_addr = SCCB_Probe();
+    if (slv_addr == 0) {
+        *out_camera_model = CAMERA_NONE;
+        camera_disable_out_clock();
+        return ESP_ERR_CAMERA_NOT_DETECTED;
+    }
+    
+    //slv_addr = 0x30;
+    ESP_LOGD(TAG, "Detected camera at address=0x%02x", slv_addr);
+    sensor_id_t* id = &s_state->sensor.id;
+
+#if CONFIG_OV2640_SUPPORT
+    if (slv_addr == 0x30) {
+        ESP_LOGD(TAG, "Resetting OV2640");
+        //camera might be OV2640. try to reset it
+        SCCB_Write(0x30, 0xFF, 0x01);//bank sensor
+        SCCB_Write(0x30, 0x12, 0x80);//reset
+        vTaskDelay(10 / portTICK_PERIOD_MS);
+        slv_addr = SCCB_Probe();
+    }
+#endif
+
+    s_state->sensor.slv_addr = slv_addr;
+    s_state->sensor.xclk_freq_hz = config->xclk_freq_hz;
+
+#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT)
+    if(s_state->sensor.slv_addr == 0x3c){
+        id->PID = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDH);
+        id->VER = SCCB_Read16(s_state->sensor.slv_addr, REG16_CHIDL);
+        vTaskDelay(10 / portTICK_PERIOD_MS);
+        ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x", id->PID, id->VER);
+    } else {
+#endif
+        id->PID = SCCB_Read(s_state->sensor.slv_addr, REG_PID);
+        id->VER = SCCB_Read(s_state->sensor.slv_addr, REG_VER);
+        id->MIDL = SCCB_Read(s_state->sensor.slv_addr, REG_MIDL);
+        id->MIDH = SCCB_Read(s_state->sensor.slv_addr, REG_MIDH);
+        vTaskDelay(10 / portTICK_PERIOD_MS);
+        ESP_LOGD(TAG, "Camera PID=0x%02x VER=0x%02x MIDL=0x%02x MIDH=0x%02x",
+                 id->PID, id->VER, id->MIDH, id->MIDL);
+
+#if (CONFIG_OV3660_SUPPORT || CONFIG_OV5640_SUPPORT)
+    }
+#endif
+
+
+    switch (id->PID) {
+#if CONFIG_OV2640_SUPPORT
+    case OV2640_PID:
+        *out_camera_model = CAMERA_OV2640;
+        ov2640_init(&s_state->sensor);
+        break;
+#endif
+#if CONFIG_OV7725_SUPPORT
+    case OV7725_PID:
+        *out_camera_model = CAMERA_OV7725;
+        ov7725_init(&s_state->sensor);
+        break;
+#endif
+#if CONFIG_OV3660_SUPPORT
+    case OV3660_PID:
+        *out_camera_model = CAMERA_OV3660;
+        ov3660_init(&s_state->sensor);
+        break;
+#endif
+#if CONFIG_OV5640_SUPPORT
+    case OV5640_PID:
+        *out_camera_model = CAMERA_OV5640;
+        ov5640_init(&s_state->sensor);
+        break;
+#endif
+    default:
+        id->PID = 0;
+        *out_camera_model = CAMERA_UNKNOWN;
+        camera_disable_out_clock();
+        ESP_LOGE(TAG, "Detected camera not supported.");
+        return ESP_ERR_CAMERA_NOT_SUPPORTED;
+    }
+
+    ESP_LOGD(TAG, "Doing SW reset of sensor");
+    s_state->sensor.reset(&s_state->sensor);
+
+    return ESP_OK;
+}
+
+esp_err_t camera_init(const camera_config_t* config)
+{
+    if (!s_state) {
+        return ESP_ERR_INVALID_STATE;
+    }
+    if (s_state->sensor.id.PID == 0) {
+        return ESP_ERR_CAMERA_NOT_SUPPORTED;
+    }
+    memcpy(&s_state->config, config, sizeof(*config));
+    esp_err_t err = ESP_OK;
+    framesize_t frame_size = (framesize_t) config->frame_size;
+    pixformat_t pix_format = (pixformat_t) config->pixel_format;
+
+    switch (s_state->sensor.id.PID) {
+#if CONFIG_OV2640_SUPPORT
+        case OV2640_PID:
+            if (frame_size > FRAMESIZE_UXGA) {
+                frame_size = FRAMESIZE_UXGA;
+            }
+            break;
+#endif
+#if CONFIG_OV7725_SUPPORT
+        case OV7725_PID:
+            if (frame_size > FRAMESIZE_VGA) {
+                frame_size = FRAMESIZE_VGA;
+            }
+            break;
+#endif
+#if CONFIG_OV3660_SUPPORT
+        case OV3660_PID:
+            if (frame_size > FRAMESIZE_QXGA) {
+                frame_size = FRAMESIZE_QXGA;
+            }
+            break;
+#endif
+#if CONFIG_OV5640_SUPPORT
+        case OV5640_PID:
+            if (frame_size > FRAMESIZE_QSXGA) {
+                frame_size = FRAMESIZE_QSXGA;
+            }
+            break;
+#endif
+        default:
+            return ESP_ERR_CAMERA_NOT_SUPPORTED;
+    }
+
+    s_state->width = resolution[frame_size].width;
+    s_state->height = resolution[frame_size].height;
+
+    if (pix_format == PIXFORMAT_GRAYSCALE) {
+        s_state->fb_size = s_state->width * s_state->height;
+        if (s_state->sensor.id.PID == OV3660_PID || s_state->sensor.id.PID == OV5640_PID) {
+            if (is_hs_mode()) {
+                s_state->sampling_mode = SM_0A00_0B00;
+                s_state->dma_filter = &dma_filter_yuyv_highspeed;
+            } else {
+                s_state->sampling_mode = SM_0A0B_0C0D;
+                s_state->dma_filter = &dma_filter_yuyv;
+            }
+            s_state->in_bytes_per_pixel = 1;       // camera sends Y8
+        } else {
+            if (is_hs_mode() && s_state->sensor.id.PID != OV7725_PID) {
+                s_state->sampling_mode = SM_0A00_0B00;
+                s_state->dma_filter = &dma_filter_grayscale_highspeed;
+            } else {
+                s_state->sampling_mode = SM_0A0B_0C0D;
+                s_state->dma_filter = &dma_filter_grayscale;
+            }
+            s_state->in_bytes_per_pixel = 2;       // camera sends YU/YV
+        }
+        s_state->fb_bytes_per_pixel = 1;       // frame buffer stores Y8
+    } else if (pix_format == PIXFORMAT_YUV422 || pix_format == PIXFORMAT_RGB565) {
+        s_state->fb_size = s_state->width * s_state->height * 2;
+        if (is_hs_mode() && s_state->sensor.id.PID != OV7725_PID) {
+            s_state->sampling_mode = SM_0A00_0B00;
+            s_state->dma_filter = &dma_filter_yuyv_highspeed;
+        } else {
+            s_state->sampling_mode = SM_0A0B_0C0D;
+            s_state->dma_filter = &dma_filter_yuyv;
+        }
+        s_state->in_bytes_per_pixel = 2;       // camera sends YU/YV
+        s_state->fb_bytes_per_pixel = 2;       // frame buffer stores YU/YV/RGB565
+    } else if (pix_format == PIXFORMAT_RGB888) {
+        s_state->fb_size = s_state->width * s_state->height * 3;
+        if (is_hs_mode()) {
+            s_state->sampling_mode = SM_0A00_0B00;
+            s_state->dma_filter = &dma_filter_rgb888_highspeed;
+        } else {
+            s_state->sampling_mode = SM_0A0B_0C0D;
+            s_state->dma_filter = &dma_filter_rgb888;
+        }
+        s_state->in_bytes_per_pixel = 2;       // camera sends RGB565
+        s_state->fb_bytes_per_pixel = 3;       // frame buffer stores RGB888
+    } else if (pix_format == PIXFORMAT_JPEG) {
+        if (s_state->sensor.id.PID != OV2640_PID && s_state->sensor.id.PID != OV3660_PID && s_state->sensor.id.PID != OV5640_PID) {
+            ESP_LOGE(TAG, "JPEG format is only supported for ov2640, ov3660 and ov5640");
+            err = ESP_ERR_NOT_SUPPORTED;
+            goto fail;
+        }
+        int qp = config->jpeg_quality;
+        int compression_ratio_bound = 1;
+        if (qp > 10) {
+            compression_ratio_bound = 16;
+        } else if (qp > 5) {
+            compression_ratio_bound = 10;
+        } else {
+            compression_ratio_bound = 4;
+        }
+        (*s_state->sensor.set_quality)(&s_state->sensor, qp);
+        s_state->in_bytes_per_pixel = 2;
+        s_state->fb_bytes_per_pixel = 2;
+        s_state->fb_size = (s_state->width * s_state->height * s_state->fb_bytes_per_pixel) / compression_ratio_bound;
+        s_state->dma_filter = &dma_filter_jpeg;
+        s_state->sampling_mode = SM_0A00_0B00;
+    } else {
+        ESP_LOGE(TAG, "Requested format is not supported");
+        err = ESP_ERR_NOT_SUPPORTED;
+        goto fail;
+    }
+
+    ESP_LOGD(TAG, "in_bpp: %d, fb_bpp: %d, fb_size: %d, mode: %d, width: %d height: %d",
+             s_state->in_bytes_per_pixel, s_state->fb_bytes_per_pixel,
+             s_state->fb_size, s_state->sampling_mode,
+             s_state->width, s_state->height);
+
+    i2s_init();
+
+    err = dma_desc_init();
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "Failed to initialize I2S and DMA");
+        goto fail;
+    }
+
+    //s_state->fb_size = 75 * 1024;
+    err = camera_fb_init(s_state->config.fb_count);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "Failed to allocate frame buffer");
+        goto fail;
+    }
+
+    s_state->data_ready = xQueueCreate(16, sizeof(size_t));
+    if (s_state->data_ready == NULL) {
+        ESP_LOGE(TAG, "Failed to dma queue");
+        err = ESP_ERR_NO_MEM;
+        goto fail;
+    }
+
+    if(s_state->config.fb_count == 1) {
+        s_state->frame_ready = xSemaphoreCreateBinary();
+        if (s_state->frame_ready == NULL) {
+            ESP_LOGE(TAG, "Failed to create semaphore");
+            err = ESP_ERR_NO_MEM;
+            goto fail;
+        }
+    } else {
+        s_state->fb_in = xQueueCreate(s_state->config.fb_count, sizeof(camera_fb_t *));
+        s_state->fb_out = xQueueCreate(1, sizeof(camera_fb_t *));
+        if (s_state->fb_in == NULL || s_state->fb_out == NULL) {
+            ESP_LOGE(TAG, "Failed to fb queues");
+            err = ESP_ERR_NO_MEM;
+            goto fail;
+        }
+    }
+
+    //ToDo: core affinity?
+#if CONFIG_CAMERA_CORE0
+    if (!xTaskCreatePinnedToCore(&dma_filter_task, "dma_filter", 4096, NULL, 10, &s_state->dma_filter_task, 0))
+#elif CONFIG_CAMERA_CORE1
+    if (!xTaskCreatePinnedToCore(&dma_filter_task, "dma_filter", 4096, NULL, 10, &s_state->dma_filter_task, 1))
+#else
+    if (!xTaskCreate(&dma_filter_task, "dma_filter", 4096, NULL, 10, &s_state->dma_filter_task))
+#endif
+    {
+        ESP_LOGE(TAG, "Failed to create DMA filter task");
+        err = ESP_ERR_NO_MEM;
+        goto fail;
+    }
+
+    vsync_intr_disable();
+    err = gpio_install_isr_service(ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "gpio_install_isr_service failed (%x)", err);
+        goto fail;
+    }
+    err = gpio_isr_handler_add(s_state->config.pin_vsync, &vsync_isr, NULL);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "vsync_isr_handler_add failed (%x)", err);
+        goto fail;
+    }
+
+    s_state->sensor.status.framesize = frame_size;
+    s_state->sensor.pixformat = pix_format;
+    ESP_LOGD(TAG, "Setting frame size to %dx%d", s_state->width, s_state->height);
+    if (s_state->sensor.set_framesize(&s_state->sensor, frame_size) != 0) {
+        ESP_LOGE(TAG, "Failed to set frame size");
+        err = ESP_ERR_CAMERA_FAILED_TO_SET_FRAME_SIZE;
+        goto fail;
+    }
+    s_state->sensor.set_pixformat(&s_state->sensor, pix_format);
+
+    if (s_state->sensor.id.PID == OV2640_PID) {
+        s_state->sensor.set_gainceiling(&s_state->sensor, GAINCEILING_2X);
+        s_state->sensor.set_bpc(&s_state->sensor, false);
+        s_state->sensor.set_wpc(&s_state->sensor, true);
+        s_state->sensor.set_lenc(&s_state->sensor, true);
+    }
+
+    if (skip_frame()) {
+        err = ESP_ERR_CAMERA_FAILED_TO_SET_OUT_FORMAT;
+        goto fail;
+    }
+    //todo: for some reason the first set of the quality does not work.
+    if (pix_format == PIXFORMAT_JPEG) {
+        (*s_state->sensor.set_quality)(&s_state->sensor, config->jpeg_quality);
+    }
+    s_state->sensor.init_status(&s_state->sensor);
+    return ESP_OK;
+
+fail:
+    esp_camera_deinit();
+    return err;
+}
+
+esp_err_t esp_camera_init(const camera_config_t* config)
+{
+    camera_model_t camera_model = CAMERA_NONE;
+    esp_err_t err = camera_probe(config, &camera_model);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "Camera probe failed with error 0x%x", err);
+        goto fail;
+    }
+    if (camera_model == CAMERA_OV7725) {
+        ESP_LOGI(TAG, "Detected OV7725 camera");
+        if(config->pixel_format == PIXFORMAT_JPEG) {
+            ESP_LOGE(TAG, "Camera does not support JPEG");
+            err = ESP_ERR_CAMERA_NOT_SUPPORTED;
+            goto fail;
+        }
+    } else if (camera_model == CAMERA_OV2640) {
+        ESP_LOGI(TAG, "Detected OV2640 camera");
+    } else if (camera_model == CAMERA_OV3660) {
+        ESP_LOGI(TAG, "Detected OV3660 camera");
+    } else if (camera_model == CAMERA_OV5640) {
+        ESP_LOGI(TAG, "Detected OV5640 camera");
+    } else {
+        ESP_LOGI(TAG, "Camera not supported");
+        err = ESP_ERR_CAMERA_NOT_SUPPORTED;
+        goto fail;
+    }
+    err = camera_init(config);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "Camera init failed with error 0x%x", err);
+        return err;
+    }
+    return ESP_OK;
+
+fail:
+    free(s_state);
+    s_state = NULL;
+    camera_disable_out_clock();
+    return err;
+}
+
+esp_err_t esp_camera_deinit()
+{
+    if (s_state == NULL) {
+        return ESP_ERR_INVALID_STATE;
+    }
+    if (s_state->dma_filter_task) {
+        vTaskDelete(s_state->dma_filter_task);
+    }
+    if (s_state->data_ready) {
+        vQueueDelete(s_state->data_ready);
+    }
+    if (s_state->fb_in) {
+        vQueueDelete(s_state->fb_in);
+    }
+    if (s_state->fb_out) {
+        vQueueDelete(s_state->fb_out);
+    }
+    if (s_state->frame_ready) {
+        vSemaphoreDelete(s_state->frame_ready);
+    }
+    gpio_isr_handler_remove(s_state->config.pin_vsync);
+    if (s_state->i2s_intr_handle) {
+        esp_intr_disable(s_state->i2s_intr_handle);
+        esp_intr_free(s_state->i2s_intr_handle);
+    }
+    dma_desc_deinit();
+    camera_fb_deinit();
+    free(s_state);
+    s_state = NULL;
+    camera_disable_out_clock();
+    periph_module_disable(PERIPH_I2S0_MODULE);
+    return ESP_OK;
+}
+
+#define FB_GET_TIMEOUT (4000 / portTICK_PERIOD_MS)
+
+camera_fb_t* esp_camera_fb_get()
+{
+    if (s_state == NULL) {
+        return NULL;
+    }
+    if(!I2S0.conf.rx_start) {
+        if(s_state->config.fb_count > 1) {
+            ESP_LOGD(TAG, "i2s_run");
+        }
+        if (i2s_run() != 0) {
+            return NULL;
+        }
+    }
+    bool need_yield = false;
+    if (s_state->config.fb_count == 1) {
+        if (xSemaphoreTake(s_state->frame_ready, FB_GET_TIMEOUT) != pdTRUE){
+            i2s_stop(&need_yield);
+            ESP_LOGE(TAG, "Failed to get the frame on time!");
+            return NULL;
+        }
+        return (camera_fb_t*)s_state->fb;
+    }
+    camera_fb_int_t * fb = NULL;
+    if(s_state->fb_out) {
+        if (xQueueReceive(s_state->fb_out, &fb, FB_GET_TIMEOUT) != pdTRUE) {
+            i2s_stop(&need_yield);
+            ESP_LOGE(TAG, "Failed to get the frame on time!");
+            return NULL;
+        }
+    }
+    return (camera_fb_t*)fb;
+}
+
+void esp_camera_fb_return(camera_fb_t * fb)
+{
+    if(fb == NULL || s_state == NULL || s_state->config.fb_count == 1 || s_state->fb_in == NULL) {
+        return;
+    }
+    xQueueSend(s_state->fb_in, &fb, portMAX_DELAY);
+}
+
+sensor_t * esp_camera_sensor_get()
+{
+    if (s_state == NULL) {
+        return NULL;
+    }
+    return &s_state->sensor;
+}
+
+esp_err_t esp_camera_save_to_nvs(const char *key) 
+{
+#if ESP_IDF_VERSION_MAJOR > 3
+    nvs_handle_t handle;
+#else
+    nvs_handle handle;
+#endif
+    esp_err_t ret = nvs_open(key,NVS_READWRITE,&handle);
+    
+    if (ret == ESP_OK) {
+        sensor_t *s = esp_camera_sensor_get();
+        if (s != NULL) {
+            ret = nvs_set_blob(handle,CAMERA_SENSOR_NVS_KEY,&s->status,sizeof(camera_status_t));
+            if (ret == ESP_OK) {
+                uint8_t pf = s->pixformat;
+                ret = nvs_set_u8(handle,CAMERA_PIXFORMAT_NVS_KEY,pf);
+            }
+            return ret;
+        } else {
+            return ESP_ERR_CAMERA_NOT_DETECTED; 
+        }
+        nvs_close(handle);
+        return ret;
+    } else {
+        return ret;
+    }
+}
+
+esp_err_t esp_camera_load_from_nvs(const char *key) 
+{
+#if ESP_IDF_VERSION_MAJOR > 3
+    nvs_handle_t handle;
+#else
+    nvs_handle handle;
+#endif
+  uint8_t pf;
+
+  esp_err_t ret = nvs_open(key,NVS_READWRITE,&handle);
+  
+  if (ret == ESP_OK) {
+      sensor_t *s = esp_camera_sensor_get();
+      camera_status_t st;
+      if (s != NULL) {
+        size_t size = sizeof(camera_status_t);
+        ret = nvs_get_blob(handle,CAMERA_SENSOR_NVS_KEY,&st,&size);
+        if (ret == ESP_OK) {
+            s->set_ae_level(s,st.ae_level);
+            s->set_aec2(s,st.aec2);
+            s->set_aec_value(s,st.aec_value);
+            s->set_agc_gain(s,st.agc_gain);
+            s->set_awb_gain(s,st.awb_gain);
+            s->set_bpc(s,st.bpc);
+            s->set_brightness(s,st.brightness);
+            s->set_colorbar(s,st.colorbar);
+            s->set_contrast(s,st.contrast);
+            s->set_dcw(s,st.dcw);
+            s->set_denoise(s,st.denoise);
+            s->set_exposure_ctrl(s,st.aec);
+            s->set_framesize(s,st.framesize);
+            s->set_gain_ctrl(s,st.agc);          
+            s->set_gainceiling(s,st.gainceiling);
+            s->set_hmirror(s,st.hmirror);
+            s->set_lenc(s,st.lenc);
+            s->set_quality(s,st.quality);
+            s->set_raw_gma(s,st.raw_gma);
+            s->set_saturation(s,st.saturation);
+            s->set_sharpness(s,st.sharpness);
+            s->set_special_effect(s,st.special_effect);
+            s->set_vflip(s,st.vflip);
+            s->set_wb_mode(s,st.wb_mode);
+            s->set_whitebal(s,st.awb);
+            s->set_wpc(s,st.wpc);
+        }  
+        ret = nvs_get_u8(handle,CAMERA_PIXFORMAT_NVS_KEY,&pf);
+        if (ret == ESP_OK) {
+          s->set_pixformat(s,pf);
+        }
+      } else {
+          return ESP_ERR_CAMERA_NOT_DETECTED;
+      }
+      nvs_close(handle);
+      return ret;
+  } else {
+      ESP_LOGW(TAG,"Error (%d) opening nvs key \"%s\"",ret,key);
+      return ret;
+  }
+}

+ 49 - 0
code/lib/driver/camera_common.h

@@ -0,0 +1,49 @@
+#pragma once
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_intr_alloc.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
+#include "freertos/task.h"
+#include "esp_camera.h"
+#include "sensor.h"
+
+#include "esp_system.h"
+#if ESP_IDF_VERSION_MAJOR >= 4 // IDF 4+
+#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
+#include "esp32/rom/lldesc.h"
+#else 
+#error Target CONFIG_IDF_TARGET is not supported
+#endif
+#else // ESP32 Before IDF 4.0
+#include "rom/lldesc.h"
+#endif
+
+typedef union {
+    struct {
+        uint8_t sample2;
+        uint8_t unused2;
+        uint8_t sample1;
+        uint8_t unused1;
+    };
+    uint32_t val;
+} dma_elem_t;
+
+typedef enum {
+    /* camera sends byte sequence: s1, s2, s3, s4, ...
+     * fifo receives: 00 s1 00 s2, 00 s2 00 s3, 00 s3 00 s4, ...
+     */
+    SM_0A0B_0B0C = 0,
+    /* camera sends byte sequence: s1, s2, s3, s4, ...
+     * fifo receives: 00 s1 00 s2, 00 s3 00 s4, ...
+     */
+    SM_0A0B_0C0D = 1,
+    /* camera sends byte sequence: s1, s2, s3, s4, ...
+     * fifo receives: 00 s1 00 00, 00 s2 00 00, 00 s3 00 00, ...
+     */
+    SM_0A00_0B00 = 3,
+} i2s_sampling_mode_t;
+

+ 200 - 0
code/lib/driver/esp_camera.h

@@ -0,0 +1,200 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+/*
+ * Example Use
+ *
+    static camera_config_t camera_example_config = {
+        .pin_pwdn       = PIN_PWDN,
+        .pin_reset      = PIN_RESET,
+        .pin_xclk       = PIN_XCLK,
+        .pin_sscb_sda   = PIN_SIOD,
+        .pin_sscb_scl   = PIN_SIOC,
+        .pin_d7         = PIN_D7,
+        .pin_d6         = PIN_D6,
+        .pin_d5         = PIN_D5,
+        .pin_d4         = PIN_D4,
+        .pin_d3         = PIN_D3,
+        .pin_d2         = PIN_D2,
+        .pin_d1         = PIN_D1,
+        .pin_d0         = PIN_D0,
+        .pin_vsync      = PIN_VSYNC,
+        .pin_href       = PIN_HREF,
+        .pin_pclk       = PIN_PCLK,
+
+        .xclk_freq_hz   = 20000000,
+        .ledc_timer     = LEDC_TIMER_0,
+        .ledc_channel   = LEDC_CHANNEL_0,
+        .pixel_format   = PIXFORMAT_JPEG,
+        .frame_size     = FRAMESIZE_SVGA,
+        .jpeg_quality   = 10,
+        .fb_count       = 2
+    };
+
+    esp_err_t camera_example_init(){
+        return esp_camera_init(&camera_example_config);
+    }
+
+    esp_err_t camera_example_capture(){
+        //capture a frame
+        camera_fb_t * fb = esp_camera_fb_get();
+        if (!fb) {
+            ESP_LOGE(TAG, "Frame buffer could not be acquired");
+            return ESP_FAIL;
+        }
+
+        //replace this with your own function
+        display_image(fb->width, fb->height, fb->pixformat, fb->buf, fb->len);
+
+        //return the frame buffer back to be reused
+        esp_camera_fb_return(fb);
+
+        return ESP_OK;
+    }
+*/
+
+#pragma once
+
+#ifndef ESPCAMERADEF
+#define ESPCAMERADEF
+
+#include "esp_err.h"
+#include "driver/ledc.h"
+#include "sensor.h"
+#include "sys/time.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Configuration structure for camera initialization
+ */
+typedef struct {
+    int pin_pwdn;                   /*!< GPIO pin for camera power down line */
+    int pin_reset;                  /*!< GPIO pin for camera reset line */
+    int pin_xclk;                   /*!< GPIO pin for camera XCLK line */
+    int pin_sscb_sda;               /*!< GPIO pin for camera SDA line */
+    int pin_sscb_scl;               /*!< GPIO pin for camera SCL line */
+    int pin_d7;                     /*!< GPIO pin for camera D7 line */
+    int pin_d6;                     /*!< GPIO pin for camera D6 line */
+    int pin_d5;                     /*!< GPIO pin for camera D5 line */
+    int pin_d4;                     /*!< GPIO pin for camera D4 line */
+    int pin_d3;                     /*!< GPIO pin for camera D3 line */
+    int pin_d2;                     /*!< GPIO pin for camera D2 line */
+    int pin_d1;                     /*!< GPIO pin for camera D1 line */
+    int pin_d0;                     /*!< GPIO pin for camera D0 line */
+    int pin_vsync;                  /*!< GPIO pin for camera VSYNC line */
+    int pin_href;                   /*!< GPIO pin for camera HREF line */
+    int pin_pclk;                   /*!< GPIO pin for camera PCLK line */
+
+    int xclk_freq_hz;               /*!< Frequency of XCLK signal, in Hz. Either 20KHz or 10KHz for OV2640 double FPS (Experimental) */
+
+    ledc_timer_t ledc_timer;        /*!< LEDC timer to be used for generating XCLK  */
+    ledc_channel_t ledc_channel;    /*!< LEDC channel to be used for generating XCLK  */
+
+    pixformat_t pixel_format;       /*!< Format of the pixel data: PIXFORMAT_ + YUV422|GRAYSCALE|RGB565|JPEG  */
+    framesize_t frame_size;         /*!< Size of the output image: FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA  */
+
+    int jpeg_quality;               /*!< Quality of JPEG output. 0-63 lower means higher quality  */
+    size_t fb_count;                /*!< Number of frame buffers to be allocated. If more than one, then each frame will be acquired (double speed)  */
+} camera_config_t;
+
+/**
+ * @brief Data structure of camera frame buffer
+ */
+typedef struct {
+    uint8_t * buf;              /*!< Pointer to the pixel data */
+    size_t len;                 /*!< Length of the buffer in bytes */
+    size_t width;               /*!< Width of the buffer in pixels */
+    size_t height;              /*!< Height of the buffer in pixels */
+    pixformat_t format;         /*!< Format of the pixel data */
+    struct timeval timestamp;   /*!< Timestamp since boot of the first DMA buffer of the frame */
+} camera_fb_t;
+
+#define ESP_ERR_CAMERA_BASE 0x20000
+#define ESP_ERR_CAMERA_NOT_DETECTED             (ESP_ERR_CAMERA_BASE + 1)
+#define ESP_ERR_CAMERA_FAILED_TO_SET_FRAME_SIZE (ESP_ERR_CAMERA_BASE + 2)
+#define ESP_ERR_CAMERA_FAILED_TO_SET_OUT_FORMAT (ESP_ERR_CAMERA_BASE + 3)
+#define ESP_ERR_CAMERA_NOT_SUPPORTED            (ESP_ERR_CAMERA_BASE + 4)
+
+/**
+ * @brief Initialize the camera driver
+ *
+ * @note call camera_probe before calling this function
+ *
+ * This function detects and configures camera over I2C interface,
+ * allocates framebuffer and DMA buffers,
+ * initializes parallel I2S input, and sets up DMA descriptors.
+ *
+ * Currently this function can only be called once and there is
+ * no way to de-initialize this module.
+ *
+ * @param config  Camera configuration parameters
+ *
+ * @return ESP_OK on success
+ */
+esp_err_t esp_camera_init(const camera_config_t* config);
+
+/**
+ * @brief Deinitialize the camera driver
+ *
+ * @return
+ *      - ESP_OK on success
+ *      - ESP_ERR_INVALID_STATE if the driver hasn't been initialized yet
+ */
+esp_err_t esp_camera_deinit();
+
+/**
+ * @brief Obtain pointer to a frame buffer.
+ *
+ * @return pointer to the frame buffer
+ */
+camera_fb_t* esp_camera_fb_get();
+
+/**
+ * @brief Return the frame buffer to be reused again.
+ *
+ * @param fb    Pointer to the frame buffer
+ */
+void esp_camera_fb_return(camera_fb_t * fb);
+
+/**
+ * @brief Get a pointer to the image sensor control structure
+ *
+ * @return pointer to the sensor
+ */
+sensor_t * esp_camera_sensor_get();
+
+/**
+ * @brief Save camera settings to non-volatile-storage (NVS)
+ * 
+ * @param key   A unique nvs key name for the camera settings 
+ */
+esp_err_t esp_camera_save_to_nvs(const char *key);
+
+/**
+ * @brief Load camera settings from non-volatile-storage (NVS)
+ * 
+ * @param key   A unique nvs key name for the camera settings 
+ */
+esp_err_t esp_camera_load_from_nvs(const char *key);
+
+#ifdef __cplusplus
+}
+#endif
+
+#include "img_converters.h"
+
+#endif
+

+ 260 - 0
code/lib/driver/sccb.c

@@ -0,0 +1,260 @@
+/*
+ * This file is part of the OpenMV project.
+ * Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
+ * This work is licensed under the MIT license, see the file LICENSE for details.
+ *
+ * SCCB (I2C like) driver.
+ *
+ */
+#include <stdbool.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include "sccb.h"
+#include <stdio.h>
+#include "sdkconfig.h"
+#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
+#include "esp32-hal-log.h"
+#else
+#include "esp_log.h"
+static const char* TAG = "sccb";
+#endif
+
+//#undef CONFIG_SCCB_HARDWARE_I2C
+
+#define LITTLETOBIG(x)          ((x<<8)|(x>>8))
+
+#ifdef CONFIG_SCCB_HARDWARE_I2C
+#include "driver/i2c.h"
+
+#define SCCB_FREQ               100000           /*!< I2C master frequency*/
+#define WRITE_BIT               I2C_MASTER_WRITE /*!< I2C master write */
+#define READ_BIT                I2C_MASTER_READ  /*!< I2C master read */
+#define ACK_CHECK_EN            0x1              /*!< I2C master will check ack from slave*/
+#define ACK_CHECK_DIS           0x0              /*!< I2C master will not check ack from slave */
+#define ACK_VAL                 0x0              /*!< I2C ack value */
+#define NACK_VAL                0x1              /*!< I2C nack value */
+#if CONFIG_SCCB_HARDWARE_I2C_PORT1
+const int SCCB_I2C_PORT         = 1;
+#else
+const int SCCB_I2C_PORT         = 0;
+#endif
+static uint8_t ESP_SLAVE_ADDR   = 0x3c;
+#else
+#include "twi.h"
+#endif
+
+int SCCB_Init(int pin_sda, int pin_scl)
+{
+    ESP_LOGI(TAG, "pin_sda %d pin_scl %d\n", pin_sda, pin_scl);
+#ifdef CONFIG_SCCB_HARDWARE_I2C
+    //log_i("SCCB_Init start");
+    i2c_config_t conf;
+    conf.mode = I2C_MODE_MASTER;
+    conf.sda_io_num = pin_sda;
+    conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
+    conf.scl_io_num = pin_scl;
+    conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
+    conf.master.clk_speed = SCCB_FREQ;
+
+    i2c_param_config(SCCB_I2C_PORT, &conf);
+    i2c_driver_install(SCCB_I2C_PORT, conf.mode, 0, 0, 0);
+#else
+    twi_init(pin_sda, pin_scl);
+#endif
+    return 0;
+}
+
+uint8_t SCCB_Probe()
+{
+#ifdef CONFIG_SCCB_HARDWARE_I2C
+    uint8_t slave_addr = 0x0;
+    while(slave_addr < 0x7f) {
+        i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+        i2c_master_start(cmd);
+        i2c_master_write_byte(cmd, ( slave_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN);
+        i2c_master_stop(cmd);
+        esp_err_t ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS);
+        i2c_cmd_link_delete(cmd);
+        if( ret == ESP_OK) {
+            ESP_SLAVE_ADDR = slave_addr;
+            return ESP_SLAVE_ADDR;
+        }
+        slave_addr++;
+    }
+    return ESP_SLAVE_ADDR;
+#else
+    uint8_t reg = 0x00;
+    uint8_t slv_addr = 0x00;
+
+    ESP_LOGI(TAG, "SCCB_Probe start");
+    for (uint8_t i = 0; i < 127; i++) {
+        if (twi_writeTo(i, &reg, 1, true) == 0) {
+            slv_addr = i;
+            break;
+        }
+
+        if (i!=126) {
+            vTaskDelay(10 / portTICK_PERIOD_MS); // Necessary for OV7725 camera (not for OV2640).
+        }
+    }
+    return slv_addr;
+#endif
+}
+
+uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg)
+{
+#ifdef CONFIG_SCCB_HARDWARE_I2C
+    uint8_t data=0;
+    esp_err_t ret = ESP_FAIL;
+    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+    i2c_master_start(cmd);
+    i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN);
+    i2c_master_write_byte(cmd, reg, ACK_CHECK_EN);
+    i2c_master_stop(cmd);
+    ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS);
+    i2c_cmd_link_delete(cmd);
+    if(ret != ESP_OK) return -1;
+    cmd = i2c_cmd_link_create();
+    i2c_master_start(cmd);
+    i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN);
+    i2c_master_read_byte(cmd, &data, NACK_VAL);
+    i2c_master_stop(cmd);
+    ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS);
+    i2c_cmd_link_delete(cmd);
+    if(ret != ESP_OK) {
+        ESP_LOGE(TAG, "SCCB_Read Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret);
+    }
+    return data;
+#else
+    uint8_t data=0;
+
+    int rc = twi_writeTo(slv_addr, &reg, 1, true);
+    if (rc != 0) {
+        data = 0xff;
+    } else {
+        rc = twi_readFrom(slv_addr, &data, 1, true);
+        if (rc != 0) {
+            data=0xFF;
+        }
+    }
+    if (rc != 0) {
+        ESP_LOGE(TAG, "SCCB_Read [%02x] failed rc=%d\n", reg, rc);
+    }
+    return data;
+#endif
+}
+
+uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data)
+{
+#ifdef CONFIG_SCCB_HARDWARE_I2C
+    esp_err_t ret = ESP_FAIL;
+    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+    i2c_master_start(cmd);
+    i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN);
+    i2c_master_write_byte(cmd, reg, ACK_CHECK_EN);
+    i2c_master_write_byte(cmd, data, ACK_CHECK_EN);
+    i2c_master_stop(cmd);
+    ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS);
+    i2c_cmd_link_delete(cmd);
+    if(ret != ESP_OK) {
+        ESP_LOGE(TAG, "SCCB_Write Failed addr:0x%02x, reg:0x%02x, data:0x%02x, ret:%d", slv_addr, reg, data, ret);
+    }
+    return ret == ESP_OK ? 0 : -1;
+#else
+    uint8_t ret=0;
+    uint8_t buf[] = {reg, data};
+
+    if(twi_writeTo(slv_addr, buf, 2, true) != 0) {
+        ret=0xFF;
+    }
+    if (ret != 0) {
+        ESP_LOGE(TAG, "SCCB_Write [%02x]=%02x failed\n", reg, data);
+    }
+    return ret;
+#endif
+}
+
+uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg)
+{
+#ifdef CONFIG_SCCB_HARDWARE_I2C
+    uint8_t data=0;
+    esp_err_t ret = ESP_FAIL;
+    uint16_t reg_htons = LITTLETOBIG(reg);
+    uint8_t *reg_u8 = (uint8_t *)&reg_htons;
+    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+    i2c_master_start(cmd);
+    i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN);
+    i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN);
+    i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN);
+    i2c_master_stop(cmd);
+    ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS);
+    i2c_cmd_link_delete(cmd);
+    if(ret != ESP_OK) return -1;
+    cmd = i2c_cmd_link_create();
+    i2c_master_start(cmd);
+    i2c_master_write_byte(cmd, ( slv_addr << 1 ) | READ_BIT, ACK_CHECK_EN);
+    i2c_master_read_byte(cmd, &data, NACK_VAL);
+    i2c_master_stop(cmd);
+    ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS);
+    i2c_cmd_link_delete(cmd);
+    if(ret != ESP_OK) {
+        ESP_LOGE(TAG, "W [%04x]=%02x fail\n", reg, data);
+    }
+    return data;
+#else
+    uint8_t data=0;
+    uint16_t reg_htons = LITTLETOBIG(reg);
+    uint8_t *reg_u8 = (uint8_t *)&reg_htons;
+    uint8_t buf[] = {reg_u8[0], reg_u8[1]};
+
+    int rc = twi_writeTo(slv_addr, buf, 2, true);
+    if (rc != 0) {
+        data = 0xff;
+    } else {
+        rc = twi_readFrom(slv_addr, &data, 1, true);
+        if (rc != 0) {
+            data=0xFF;
+        }
+    }
+    if (rc != 0) {
+        ESP_LOGE(TAG, "R [%04x] fail rc=%d\n", reg, rc);
+    }
+    return data;
+#endif
+}
+
+uint8_t SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data)
+{
+    static uint16_t i = 0;
+#ifdef CONFIG_SCCB_HARDWARE_I2C
+    esp_err_t ret = ESP_FAIL;
+    uint16_t reg_htons = LITTLETOBIG(reg);
+    uint8_t *reg_u8 = (uint8_t *)&reg_htons;
+    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
+    i2c_master_start(cmd);
+    i2c_master_write_byte(cmd, ( slv_addr << 1 ) | WRITE_BIT, ACK_CHECK_EN);
+    i2c_master_write_byte(cmd, reg_u8[0], ACK_CHECK_EN);
+    i2c_master_write_byte(cmd, reg_u8[1], ACK_CHECK_EN);
+    i2c_master_write_byte(cmd, data, ACK_CHECK_EN);
+    i2c_master_stop(cmd);
+    ret = i2c_master_cmd_begin(SCCB_I2C_PORT, cmd, 1000 / portTICK_RATE_MS);
+    i2c_cmd_link_delete(cmd);
+    if(ret != ESP_OK) {
+        ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++);
+    }
+    return ret == ESP_OK ? 0 : -1;
+#else
+    uint8_t ret=0;
+    uint16_t reg_htons = LITTLETOBIG(reg);
+    uint8_t *reg_u8 = (uint8_t *)&reg_htons;
+    uint8_t buf[] = {reg_u8[0], reg_u8[1], data};
+
+    if(twi_writeTo(slv_addr, buf, 3, true) != 0) {
+        ret = 0xFF;
+    }
+    if (ret != 0) {
+        ESP_LOGE(TAG, "W [%04x]=%02x %d fail\n", reg, data, i++);
+    }
+    return ret;
+#endif
+}

+ 18 - 0
code/lib/driver/sccb.h

@@ -0,0 +1,18 @@
+/*
+ * This file is part of the OpenMV project.
+ * Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
+ * This work is licensed under the MIT license, see the file LICENSE for details.
+ *
+ * SCCB (I2C like) driver.
+ *
+ */
+#ifndef __SCCB_H__
+#define __SCCB_H__
+#include <stdint.h>
+int SCCB_Init(int pin_sda, int pin_scl);
+uint8_t SCCB_Probe();
+uint8_t SCCB_Read(uint8_t slv_addr, uint8_t reg);
+uint8_t SCCB_Write(uint8_t slv_addr, uint8_t reg, uint8_t data);
+uint8_t SCCB_Read16(uint8_t slv_addr, uint16_t reg);
+uint8_t SCCB_Write16(uint8_t slv_addr, uint16_t reg, uint8_t data);
+#endif // __SCCB_H__

+ 28 - 0
code/lib/driver/sensor.c

@@ -0,0 +1,28 @@
+#include "sensor.h"
+
+const resolution_info_t resolution[FRAMESIZE_INVALID] = {
+    {   96,   96, ASPECT_RATIO_1X1   }, /* 96x96 */
+    {  160,  120, ASPECT_RATIO_4X3   }, /* QQVGA */
+    {  176,  144, ASPECT_RATIO_5X4   }, /* QCIF  */
+    {  240,  176, ASPECT_RATIO_4X3   }, /* HQVGA */
+    {  240,  240, ASPECT_RATIO_1X1   }, /* 240x240 */
+    {  320,  240, ASPECT_RATIO_4X3   }, /* QVGA  */
+    {  400,  296, ASPECT_RATIO_4X3   }, /* CIF   */
+    {  480,  320, ASPECT_RATIO_3X2   }, /* HVGA  */
+    {  640,  480, ASPECT_RATIO_4X3   }, /* VGA   */
+    {  800,  600, ASPECT_RATIO_4X3   }, /* SVGA  */
+    { 1024,  768, ASPECT_RATIO_4X3   }, /* XGA   */
+    { 1280,  720, ASPECT_RATIO_16X9  }, /* HD    */
+    { 1280, 1024, ASPECT_RATIO_5X4   }, /* SXGA  */
+    { 1600, 1200, ASPECT_RATIO_4X3   }, /* UXGA  */
+    // 3MP Sensors
+    { 1920, 1080, ASPECT_RATIO_16X9  }, /* FHD   */
+    {  720, 1280, ASPECT_RATIO_9X16  }, /* Portrait HD   */
+    {  864, 1536, ASPECT_RATIO_9X16  }, /* Portrait 3MP   */
+    { 2048, 1536, ASPECT_RATIO_4X3   }, /* QXGA  */
+    // 5MP Sensors
+    { 2560, 1440, ASPECT_RATIO_16X9  }, /* QHD    */
+    { 2560, 1600, ASPECT_RATIO_16X10 }, /* WQXGA  */
+    { 1088, 1920, ASPECT_RATIO_9X16  }, /* Portrait FHD   */
+    { 2560, 1920, ASPECT_RATIO_4X3   }, /* QSXGA  */
+};

+ 191 - 0
code/lib/driver/sensor.h

@@ -0,0 +1,191 @@
+/*
+ * This file is part of the OpenMV project.
+ * Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
+ * This work is licensed under the MIT license, see the file LICENSE for details.
+ *
+ * Sensor abstraction layer.
+ *
+ */
+#ifndef __SENSOR_H__
+#define __SENSOR_H__
+#include <stdint.h>
+#include <stdbool.h>
+
+#define OV9650_PID     (0x96)
+#define OV7725_PID     (0x77)
+#define OV2640_PID     (0x26)
+#define OV3660_PID     (0x36)
+#define OV5640_PID     (0x56)
+
+typedef enum {
+    PIXFORMAT_RGB565,    // 2BPP/RGB565
+    PIXFORMAT_YUV422,    // 2BPP/YUV422
+    PIXFORMAT_GRAYSCALE, // 1BPP/GRAYSCALE
+    PIXFORMAT_JPEG,      // JPEG/COMPRESSED
+    PIXFORMAT_RGB888,    // 3BPP/RGB888
+    PIXFORMAT_RAW,       // RAW
+    PIXFORMAT_RGB444,    // 3BP2P/RGB444
+    PIXFORMAT_RGB555,    // 3BP2P/RGB555
+} pixformat_t;
+
+typedef enum {
+    FRAMESIZE_96X96,    // 96x96
+    FRAMESIZE_QQVGA,    // 160x120
+    FRAMESIZE_QCIF,     // 176x144
+    FRAMESIZE_HQVGA,    // 240x176
+    FRAMESIZE_240X240,  // 240x240
+    FRAMESIZE_QVGA,     // 320x240
+    FRAMESIZE_CIF,      // 400x296
+    FRAMESIZE_HVGA,     // 480x320
+    FRAMESIZE_VGA,      // 640x480
+    FRAMESIZE_SVGA,     // 800x600
+    FRAMESIZE_XGA,      // 1024x768
+    FRAMESIZE_HD,       // 1280x720
+    FRAMESIZE_SXGA,     // 1280x1024
+    FRAMESIZE_UXGA,     // 1600x1200
+    // 3MP Sensors
+    FRAMESIZE_FHD,      // 1920x1080
+    FRAMESIZE_P_HD,     //  720x1280
+    FRAMESIZE_P_3MP,    //  864x1536
+    FRAMESIZE_QXGA,     // 2048x1536
+    // 5MP Sensors
+    FRAMESIZE_QHD,      // 2560x1440
+    FRAMESIZE_WQXGA,    // 2560x1600
+    FRAMESIZE_P_FHD,    // 1080x1920
+    FRAMESIZE_QSXGA,    // 2560x1920
+    FRAMESIZE_INVALID
+} framesize_t;
+
+typedef enum {
+    ASPECT_RATIO_4X3,
+    ASPECT_RATIO_3X2,
+    ASPECT_RATIO_16X10,
+    ASPECT_RATIO_5X3,
+    ASPECT_RATIO_16X9,
+    ASPECT_RATIO_21X9,
+    ASPECT_RATIO_5X4,
+    ASPECT_RATIO_1X1,
+    ASPECT_RATIO_9X16
+} aspect_ratio_t;
+
+typedef enum {
+    GAINCEILING_2X,
+    GAINCEILING_4X,
+    GAINCEILING_8X,
+    GAINCEILING_16X,
+    GAINCEILING_32X,
+    GAINCEILING_64X,
+    GAINCEILING_128X,
+} gainceiling_t;
+
+typedef struct {
+        uint16_t max_width;
+        uint16_t max_height;
+        uint16_t start_x;
+        uint16_t start_y;
+        uint16_t end_x;
+        uint16_t end_y;
+        uint16_t offset_x;
+        uint16_t offset_y;
+        uint16_t total_x;
+        uint16_t total_y;
+} ratio_settings_t;
+
+typedef struct {
+        const uint16_t width;
+        const uint16_t height;
+        const aspect_ratio_t aspect_ratio;
+} resolution_info_t;
+
+// Resolution table (in sensor.c)
+extern const resolution_info_t resolution[];
+
+typedef struct {
+    uint8_t MIDH;
+    uint8_t MIDL;
+    uint8_t PID;
+    uint8_t VER;
+} sensor_id_t;
+
+typedef struct {
+    framesize_t framesize;//0 - 10
+    bool scale;
+    bool binning;
+    uint8_t quality;//0 - 63
+    int8_t brightness;//-2 - 2
+    int8_t contrast;//-2 - 2
+    int8_t saturation;//-2 - 2
+    int8_t sharpness;//-2 - 2
+    uint8_t denoise;
+    uint8_t special_effect;//0 - 6
+    uint8_t wb_mode;//0 - 4
+    uint8_t awb;
+    uint8_t awb_gain;
+    uint8_t aec;
+    uint8_t aec2;
+    int8_t ae_level;//-2 - 2
+    uint16_t aec_value;//0 - 1200
+    uint8_t agc;
+    uint8_t agc_gain;//0 - 30
+    uint8_t gainceiling;//0 - 6
+    uint8_t bpc;
+    uint8_t wpc;
+    uint8_t raw_gma;
+    uint8_t lenc;
+    uint8_t hmirror;
+    uint8_t vflip;
+    uint8_t dcw;
+    uint8_t colorbar;
+} camera_status_t;
+
+typedef struct _sensor sensor_t;
+typedef struct _sensor {
+    sensor_id_t id;             // Sensor ID.
+    uint8_t  slv_addr;          // Sensor I2C slave address.
+    pixformat_t pixformat;
+    camera_status_t status;
+    int xclk_freq_hz;
+
+    // Sensor function pointers
+    int  (*init_status)         (sensor_t *sensor);
+    int  (*reset)               (sensor_t *sensor);
+    int  (*set_pixformat)       (sensor_t *sensor, pixformat_t pixformat);
+    int  (*set_framesize)       (sensor_t *sensor, framesize_t framesize);
+    int  (*set_contrast)        (sensor_t *sensor, int level);
+    int  (*set_brightness)      (sensor_t *sensor, int level);
+    int  (*set_saturation)      (sensor_t *sensor, int level);
+    int  (*set_sharpness)       (sensor_t *sensor, int level);
+    int  (*set_denoise)         (sensor_t *sensor, int level);
+    int  (*set_gainceiling)     (sensor_t *sensor, gainceiling_t gainceiling);
+    int  (*set_quality)         (sensor_t *sensor, int quality);
+    int  (*set_colorbar)        (sensor_t *sensor, int enable);
+    int  (*set_whitebal)        (sensor_t *sensor, int enable);
+    int  (*set_gain_ctrl)       (sensor_t *sensor, int enable);
+    int  (*set_exposure_ctrl)   (sensor_t *sensor, int enable);
+    int  (*set_hmirror)         (sensor_t *sensor, int enable);
+    int  (*set_vflip)           (sensor_t *sensor, int enable);
+
+    int  (*set_aec2)            (sensor_t *sensor, int enable);
+    int  (*set_awb_gain)        (sensor_t *sensor, int enable);
+    int  (*set_agc_gain)        (sensor_t *sensor, int gain);
+    int  (*set_aec_value)       (sensor_t *sensor, int gain);
+
+    int  (*set_special_effect)  (sensor_t *sensor, int effect);
+    int  (*set_wb_mode)         (sensor_t *sensor, int mode);
+    int  (*set_ae_level)        (sensor_t *sensor, int level);
+
+    int  (*set_dcw)             (sensor_t *sensor, int enable);
+    int  (*set_bpc)             (sensor_t *sensor, int enable);
+    int  (*set_wpc)             (sensor_t *sensor, int enable);
+
+    int  (*set_raw_gma)         (sensor_t *sensor, int enable);
+    int  (*set_lenc)            (sensor_t *sensor, int enable);
+
+    int  (*get_reg)             (sensor_t *sensor, int reg, int mask);
+    int  (*set_reg)             (sensor_t *sensor, int reg, int mask, int value);
+    int  (*set_res_raw)         (sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning);
+    int  (*set_pll)             (sensor_t *sensor, int bypass, int mul, int sys, int root, int pre, int seld5, int pclken, int pclk);
+    int  (*set_xclk)            (sensor_t *sensor, int timer, int xclk);
+} sensor_t;
+
+#endif /* __SENSOR_H__ */

+ 432 - 0
code/lib/driver/twi.c

@@ -0,0 +1,432 @@
+/*
+  si2c.c - Software I2C library for ESP31B
+
+  Copyright (c) 2015 Hristo Gochkov. All rights reserved.
+  This file is part of the ESP31B core for Arduino environment.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+#include <stdint.h>
+#include <stdbool.h>
+#include "twi.h"
+#include "soc/gpio_reg.h"
+#include "soc/gpio_struct.h"
+#include "soc/io_mux_reg.h"
+#include "driver/rtc_io.h"
+#include <stdio.h>
+
+
+#define LOW               0x0
+#define HIGH              0x1
+
+//GPIO FUNCTIONS
+#define INPUT             0x01
+#define OUTPUT            0x02
+#define PULLUP            0x04
+#define INPUT_PULLUP      0x05
+#define PULLDOWN          0x08
+#define INPUT_PULLDOWN    0x09
+#define OPEN_DRAIN        0x10
+#define OUTPUT_OPEN_DRAIN 0x12
+#define SPECIAL           0xF0
+#define FUNCTION_1        0x00
+#define FUNCTION_2        0x20
+#define FUNCTION_3        0x40
+#define FUNCTION_4        0x60
+#define FUNCTION_5        0x80
+#define FUNCTION_6        0xA0
+
+#define ESP_REG(addr) *((volatile uint32_t *)(addr))
+
+const uint8_t pin_to_mux[40] = { 0x44, 0x88, 0x40, 0x84, 0x48, 0x6c, 0x60, 0x64, 0x68, 0x54, 0x58, 0x5c, 0x34, 0x38, 0x30, 0x3c, 0x4c, 0x50, 0x70, 0x74, 0x78, 0x7c, 0x80, 0x8c, 0, 0x24, 0x28, 0x2c, 0, 0, 0, 0, 0x1c, 0x20, 0x14, 0x18, 0x04, 0x08, 0x0c, 0x10};
+
+static void pinMode(uint8_t pin, uint8_t mode)
+{
+    if(pin >= 40) {
+        return;
+    }
+
+    uint32_t rtc_reg = rtc_gpio_desc[pin].reg;
+
+    //RTC pins PULL settings
+    if(rtc_reg) {
+        //lock rtc
+        ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].mux);
+        if(mode & PULLUP) {
+            ESP_REG(rtc_reg) = (ESP_REG(rtc_reg) | rtc_gpio_desc[pin].pullup) & ~(rtc_gpio_desc[pin].pulldown);
+        } else if(mode & PULLDOWN) {
+            ESP_REG(rtc_reg) = (ESP_REG(rtc_reg) | rtc_gpio_desc[pin].pulldown) & ~(rtc_gpio_desc[pin].pullup);
+        } else {
+            ESP_REG(rtc_reg) = ESP_REG(rtc_reg) & ~(rtc_gpio_desc[pin].pullup | rtc_gpio_desc[pin].pulldown);
+        }
+        //unlock rtc
+    }
+
+    uint32_t pinFunction = 0, pinControl = 0;
+
+    //lock gpio
+    if(mode & INPUT) {
+        if(pin < 32) {
+            GPIO.enable_w1tc = BIT(pin);
+        } else {
+            GPIO.enable1_w1tc.val = BIT(pin - 32);
+        }
+    } else if(mode & OUTPUT) {
+        if(pin > 33) {
+            //unlock gpio
+            return;//pins above 33 can be only inputs
+        } else if(pin < 32) {
+            GPIO.enable_w1ts = BIT(pin);
+        } else {
+            GPIO.enable1_w1ts.val = BIT(pin - 32);
+        }
+    }
+
+    if(mode & PULLUP) {
+        pinFunction |= FUN_PU;
+    } else if(mode & PULLDOWN) {
+        pinFunction |= FUN_PD;
+    }
+
+    pinFunction |= ((uint32_t)2 << FUN_DRV_S);//what are the drivers?
+    pinFunction |= FUN_IE;//input enable but required for output as well?
+
+    if(mode & (INPUT | OUTPUT)) {
+        pinFunction |= ((uint32_t)2 << MCU_SEL_S);
+    } else if(mode == SPECIAL) {
+        pinFunction |= ((uint32_t)(((pin)==1||(pin)==3)?0:1) << MCU_SEL_S);
+    } else {
+        pinFunction |= ((uint32_t)(mode >> 5) << MCU_SEL_S);
+    }
+
+    ESP_REG(DR_REG_IO_MUX_BASE + pin_to_mux[pin]) = pinFunction;
+
+    if(mode & OPEN_DRAIN) {
+        pinControl = (1 << GPIO_PIN0_PAD_DRIVER_S);
+    }
+
+    GPIO.pin[pin].val = pinControl;
+    //unlock gpio
+}
+
+static void digitalWrite(uint8_t pin, uint8_t val)
+{
+    if(val) {
+        if(pin < 32) {
+            GPIO.out_w1ts = BIT(pin);
+        } else if(pin < 34) {
+            GPIO.out1_w1ts.val = BIT(pin - 32);
+        }
+    } else {
+        if(pin < 32) {
+            GPIO.out_w1tc = BIT(pin);
+        } else if(pin < 34) {
+            GPIO.out1_w1tc.val = BIT(pin - 32);
+        }
+    }
+}
+
+
+unsigned char twi_dcount = 18;
+static unsigned char twi_sda, twi_scl;
+
+
+static inline void SDA_LOW()
+{
+    //Enable SDA (becomes output and since GPO is 0 for the pin,
+    // it will pull the line low)
+    if (twi_sda < 32) {
+        GPIO.enable_w1ts = BIT(twi_sda);
+    } else {
+        GPIO.enable1_w1ts.val = BIT(twi_sda - 32);
+    }
+}
+
+static inline void SDA_HIGH()
+{
+    //Disable SDA (becomes input and since it has pullup it will go high)
+    if (twi_sda < 32) {
+        GPIO.enable_w1tc = BIT(twi_sda);
+    } else {
+        GPIO.enable1_w1tc.val = BIT(twi_sda - 32);
+    }
+}
+
+static inline uint32_t SDA_READ()
+{
+    if (twi_sda < 32) {
+        return (GPIO.in & BIT(twi_sda)) != 0;
+    } else {
+        return (GPIO.in1.val & BIT(twi_sda - 32)) != 0;
+    }
+}
+
+static void SCL_LOW()
+{
+    if (twi_scl < 32) {
+        GPIO.enable_w1ts = BIT(twi_scl);
+    } else {
+        GPIO.enable1_w1ts.val = BIT(twi_scl - 32);
+    }
+}
+
+static void SCL_HIGH()
+{
+    if (twi_scl < 32) {
+        GPIO.enable_w1tc = BIT(twi_scl);
+    } else {
+        GPIO.enable1_w1tc.val = BIT(twi_scl - 32);
+    }
+}
+
+static uint32_t SCL_READ()
+{
+    if (twi_scl < 32) {
+        return (GPIO.in & BIT(twi_scl)) != 0;
+    } else {
+        return (GPIO.in1.val & BIT(twi_scl - 32)) != 0;
+    }
+}
+
+
+#ifndef FCPU80
+#define FCPU80 80000000L
+#endif
+
+#if F_CPU == FCPU80
+#define TWI_CLOCK_STRETCH 800
+#else
+#define TWI_CLOCK_STRETCH 1600
+#endif
+
+void twi_setClock(unsigned int freq)
+{
+#if F_CPU == FCPU80
+    if(freq <= 100000) {
+        twi_dcount = 19;    //about 100KHz
+    } else if(freq <= 200000) {
+        twi_dcount = 8;    //about 200KHz
+    } else if(freq <= 300000) {
+        twi_dcount = 3;    //about 300KHz
+    } else if(freq <= 400000) {
+        twi_dcount = 1;    //about 400KHz
+    } else {
+        twi_dcount = 1;    //about 400KHz
+    }
+#else
+    if(freq <= 100000) {
+        twi_dcount = 32;    //about 100KHz
+    } else if(freq <= 200000) {
+        twi_dcount = 14;    //about 200KHz
+    } else if(freq <= 300000) {
+        twi_dcount = 8;    //about 300KHz
+    } else if(freq <= 400000) {
+        twi_dcount = 5;    //about 400KHz
+    } else if(freq <= 500000) {
+        twi_dcount = 3;    //about 500KHz
+    } else if(freq <= 600000) {
+        twi_dcount = 2;    //about 600KHz
+    } else {
+        twi_dcount = 1;    //about 700KHz
+    }
+#endif
+}
+
+void twi_init(unsigned char sda, unsigned char scl)
+{
+    twi_sda = sda;
+    twi_scl = scl;
+    pinMode(twi_sda, OUTPUT);
+    pinMode(twi_scl, OUTPUT);
+
+    digitalWrite(twi_sda, 0);
+    digitalWrite(twi_scl, 0);
+
+    pinMode(twi_sda, INPUT_PULLUP);
+    pinMode(twi_scl, INPUT_PULLUP);
+    twi_setClock(100000);
+}
+
+void twi_stop(void)
+{
+    pinMode(twi_sda, INPUT);
+    pinMode(twi_scl, INPUT);
+}
+
+static void twi_delay(unsigned char v)
+{
+    unsigned int i;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
+    unsigned int reg;
+    for(i=0; i<v; i++) {
+        reg = REG_READ(GPIO_IN_REG);
+    }
+#pragma GCC diagnostic pop
+}
+
+static bool twi_write_start(void)
+{
+    SCL_HIGH();
+    SDA_HIGH();
+    if (SDA_READ() == 0) {
+        return false;
+    }
+    twi_delay(twi_dcount);
+    SDA_LOW();
+    twi_delay(twi_dcount);
+    return true;
+}
+
+static bool twi_write_stop(void)
+{
+    unsigned int i = 0;
+    SCL_LOW();
+    SDA_LOW();
+    twi_delay(twi_dcount);
+    SCL_HIGH();
+    while (SCL_READ() == 0 && (i++) < TWI_CLOCK_STRETCH);// Clock stretching (up to 100us)
+    twi_delay(twi_dcount);
+    SDA_HIGH();
+    twi_delay(twi_dcount);
+
+    return true;
+}
+
+bool do_log = false;
+static bool twi_write_bit(bool bit)
+{
+    unsigned int i = 0;
+    SCL_LOW();
+    if (bit) {
+        SDA_HIGH();
+        if (do_log) {
+            twi_delay(twi_dcount+1);
+        }
+    } else {
+        SDA_LOW();
+        if (do_log) {}
+    }
+    twi_delay(twi_dcount+1);
+    SCL_HIGH();
+    while (SCL_READ() == 0 && (i++) < TWI_CLOCK_STRETCH);// Clock stretching (up to 100us)
+    twi_delay(twi_dcount);
+    return true;
+}
+
+static bool twi_read_bit(void)
+{
+    unsigned int i = 0;
+    SCL_LOW();
+    SDA_HIGH();
+    twi_delay(twi_dcount+2);
+    SCL_HIGH();
+    while (SCL_READ() == 0 && (i++) < TWI_CLOCK_STRETCH);// Clock stretching (up to 100us)
+    bool bit = SDA_READ();
+    twi_delay(twi_dcount);
+    return bit;
+}
+
+static bool twi_write_byte(unsigned char byte)
+{
+
+    if (byte == 0x43) {
+        // printf("TWB %02x ", (uint32_t) byte);
+        // do_log = true;
+    }
+    unsigned char bit;
+    for (bit = 0; bit < 8; bit++) {
+        twi_write_bit((byte & 0x80) != 0);
+        byte <<= 1;
+    }
+    if (do_log) {
+        printf("\n");
+        do_log = false;
+    }
+    return !twi_read_bit();//NACK/ACK
+}
+
+static unsigned char twi_read_byte(bool nack)
+{
+    unsigned char byte = 0;
+    unsigned char bit;
+    for (bit = 0; bit < 8; bit++) {
+        byte = (byte << 1) | twi_read_bit();
+    }
+    twi_write_bit(nack);
+    return byte;
+}
+
+unsigned char twi_writeTo(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop)
+{
+    unsigned int i;
+    if(!twi_write_start()) {
+        return 4;    //line busy
+    }
+    if(!twi_write_byte(((address << 1) | 0) & 0xFF)) {
+        if (sendStop) {
+            twi_write_stop();
+        }
+        return 2; //received NACK on transmit of address
+    }
+    for(i=0; i<len; i++) {
+        if(!twi_write_byte(buf[i])) {
+            if (sendStop) {
+                twi_write_stop();
+            }
+            return 3;//received NACK on transmit of data
+        }
+    }
+    if(sendStop) {
+        twi_write_stop();
+    }
+    i = 0;
+    while(SDA_READ() == 0 && (i++) < 10) {
+        SCL_LOW();
+        twi_delay(twi_dcount);
+        SCL_HIGH();
+        twi_delay(twi_dcount);
+    }
+    return 0;
+}
+
+unsigned char twi_readFrom(unsigned char address, unsigned char* buf, unsigned int len, unsigned char sendStop)
+{
+    unsigned int i;
+    if(!twi_write_start()) {
+        return 4;    //line busy
+    }
+    if(!twi_write_byte(((address << 1) | 1) & 0xFF)) {
+        if (sendStop) {
+            twi_write_stop();
+        }
+        return 2;//received NACK on transmit of address
+    }
+    for(i=0; i<(len-1); i++) {
+        buf[i] = twi_read_byte(false);
+    }
+    buf[len-1] = twi_read_byte(true);
+    if(sendStop) {
+        twi_write_stop();
+    }
+    i = 0;
+    while(SDA_READ() == 0 && (i++) < 10) {
+        SCL_LOW();
+        twi_delay(twi_dcount);
+        SCL_HIGH();
+        twi_delay(twi_dcount);
+    }
+    return 0;
+}

+ 38 - 0
code/lib/driver/twi.h

@@ -0,0 +1,38 @@
+/*
+  twi.h - Software I2C library for ESP31B
+
+  Copyright (c) 2015 Hristo Gochkov. All rights reserved.
+  This file is part of the ESP31B core for Arduino environment.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+#ifndef SI2C_h
+#define SI2C_h
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void twi_init(unsigned char sda, unsigned char scl);
+void twi_stop(void);
+void twi_setClock(unsigned int freq);
+uint8_t twi_writeTo(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop);
+uint8_t twi_readFrom(unsigned char address, unsigned char * buf, unsigned int len, unsigned char sendStop);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

+ 64 - 0
code/lib/driver/xclk.c

@@ -0,0 +1,64 @@
+#include "driver/gpio.h"
+#include "driver/ledc.h"
+#include "esp_err.h"
+#include "esp_log.h"
+#include "esp_system.h"
+#include "xclk.h"
+
+#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
+#include "esp32-hal-log.h"
+#else
+#include "esp_log.h"
+static const char* TAG = "camera_xclk";
+#endif
+
+esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz)
+{
+    ledc_timer_config_t timer_conf;
+    timer_conf.duty_resolution = 2;
+    timer_conf.freq_hz = xclk_freq_hz;
+    timer_conf.speed_mode = LEDC_HIGH_SPEED_MODE;
+
+#if ESP_IDF_VERSION_MAJOR >= 4
+    timer_conf.clk_cfg = LEDC_AUTO_CLK;
+#endif
+//    timer_conf.clk_cfg = LEDC_USE_APB_CLK;
+
+    timer_conf.timer_num = (ledc_timer_t)ledc_timer;
+    esp_err_t err = ledc_timer_config(&timer_conf);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "ledc_timer_config failed for freq %d, rc=%x", xclk_freq_hz, err);
+    }
+    return err;
+}
+
+esp_err_t camera_enable_out_clock(camera_config_t* config)
+{
+    periph_module_enable(PERIPH_LEDC_MODULE);
+
+    esp_err_t err = xclk_timer_conf(config->ledc_timer, config->xclk_freq_hz);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "ledc_timer_config failed, rc=%x", err);
+        return err;
+    }
+
+    ledc_channel_config_t ch_conf;
+    ch_conf.gpio_num = config->pin_xclk;
+    ch_conf.speed_mode = LEDC_HIGH_SPEED_MODE;
+    ch_conf.channel = config->ledc_channel;
+    ch_conf.intr_type = LEDC_INTR_DISABLE;
+    ch_conf.timer_sel = config->ledc_timer;
+    ch_conf.duty = 2;
+    ch_conf.hpoint = 0;
+    err = ledc_channel_config(&ch_conf);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "ledc_channel_config failed, rc=%x", err);
+        return err;
+    }
+    return ESP_OK;
+}
+
+void camera_disable_out_clock()
+{
+    periph_module_disable(PERIPH_LEDC_MODULE);
+}

+ 7 - 0
code/lib/driver/xclk.h

@@ -0,0 +1,7 @@
+#pragma once
+
+#include "camera_common.h"
+
+esp_err_t camera_enable_out_clock();
+
+void camera_disable_out_clock();

+ 254 - 0
code/lib/jomjol_controlcamera/ClassControllCamera.cpp

@@ -0,0 +1,254 @@
+#include "ClassControllCamera.h"
+
+#include <stdio.h>
+#include "driver/gpio.h"
+#include "esp_timer.h"
+#include "esp_log.h"
+
+#include "Helper.h"
+#include "CFindTemplate.h"
+
+#include "camera_define.h"
+
+CCamera Camera;
+
+
+#define FLASH_GPIO GPIO_NUM_4
+
+typedef struct {
+        httpd_req_t *req;
+        size_t len;
+} jpg_chunking_t;
+
+static size_t jpg_encode_stream(void * arg, size_t index, const void* data, size_t len){
+    jpg_chunking_t *j = (jpg_chunking_t *)arg;
+    if(!index){
+        j->len = 0;
+    }
+    if(httpd_resp_send_chunk(j->req, (const char *)data, len) != ESP_OK){
+        return 0;
+    }
+    j->len += len;
+    return len;
+}
+
+
+void CCamera::SetQualitySize(int qual, framesize_t resol)
+{
+    sensor_t * s = esp_camera_sensor_get();   
+    s->set_quality(s, qual);    
+    s->set_framesize(s, resol); 
+    ActualResolution = resol;
+    ActualQuality = qual;
+}
+
+
+esp_err_t CCamera::CaptureToFile(std::string nm, int delay)
+{
+//    nm =  "/sdcard/josef_zw.bmp";
+    string ftype;
+
+    if (delay > 0) 
+    {
+        LightOnOff(true);
+        const TickType_t xDelay = delay / portTICK_PERIOD_MS;
+        vTaskDelay( xDelay );
+    }
+
+    camera_fb_t * fb = esp_camera_fb_get();
+    if (!fb) {
+        ESP_LOGE(TAGCAMERACLASS, "Camera Capture Failed");
+        return ESP_FAIL;
+    }
+    printf("w %d, h %d, size %d\n", fb->width, fb->height, fb->len);
+
+    nm = FormatFileName(nm);
+    printf("Save Camera to : %s\n", nm.c_str());
+    ftype = toUpper(getFileType(nm));
+    printf("Filetype: %s\n", ftype.c_str());
+
+    uint8_t * buf = NULL;
+    size_t buf_len = 0;   
+    bool converted = false; 
+
+    if (ftype.compare("BMP") == 0)
+    {
+        frame2bmp(fb, &buf, &buf_len);
+        converted = true;
+    }
+    if (ftype.compare("JPG") == 0)
+    {
+        if(fb->format != PIXFORMAT_JPEG){
+            bool jpeg_converted = frame2jpg(fb, ActualQuality, &buf, &buf_len);
+            converted = true;
+            if(!jpeg_converted){
+                ESP_LOGE(TAGCAMERACLASS, "JPEG compression failed");
+            }
+        } else {
+            buf_len = fb->len;
+            buf = fb->buf;
+        }
+    }
+
+    FILE * fp = fopen(nm.c_str(), "wb");
+    if (fp == NULL)  /* If an error occurs during the file creation */
+    {
+        fprintf(stderr, "fopen() failed for '%s'\n", nm.c_str());
+    }
+    else
+    {
+        fwrite(buf, sizeof(uint8_t), buf_len, fp); 
+        fclose(fp);
+    }    
+    if (converted)
+        free(buf);
+
+    esp_camera_fb_return(fb);
+
+    if (delay > 0) 
+    {
+        LightOnOff(false);
+    }
+
+    return ESP_OK;    
+}
+
+
+esp_err_t CCamera::CaptureToHTTP(httpd_req_t *req, int delay)
+{
+    camera_fb_t * fb = NULL;
+    esp_err_t res = ESP_OK;
+    size_t fb_len = 0;
+    int64_t fr_start = esp_timer_get_time();
+
+    fb = esp_camera_fb_get();
+    if (!fb) {
+        ESP_LOGE(TAGCAMERACLASS, "Camera capture failed");
+        httpd_resp_send_500(req);
+        return ESP_FAIL;
+    }
+    res = httpd_resp_set_type(req, "image/jpeg");
+    if(res == ESP_OK){
+        res = httpd_resp_set_hdr(req, "Content-Disposition", "inline; filename=capture.jpg");
+    }
+
+    if(res == ESP_OK){
+        if(fb->format == PIXFORMAT_JPEG){
+            fb_len = fb->len;
+            res = httpd_resp_send(req, (const char *)fb->buf, fb->len);
+        } else {
+            jpg_chunking_t jchunk = {req, 0};
+            res = frame2jpg_cb(fb, 80, jpg_encode_stream, &jchunk)?ESP_OK:ESP_FAIL;
+            httpd_resp_send_chunk(req, NULL, 0);
+            fb_len = jchunk.len;
+        }
+    }
+    esp_camera_fb_return(fb);
+    int64_t fr_end = esp_timer_get_time();
+    
+    ESP_LOGI(TAGCAMERACLASS, "JPG: %uKB %ums", (uint32_t)(fb_len/1024), (uint32_t)((fr_end - fr_start)/1000));
+    return res;
+}
+
+void CCamera::LightOnOff(bool status)
+{
+	// Init the GPIO
+    gpio_pad_select_gpio(FLASH_GPIO);
+    /* Set the GPIO as a push/pull output */
+    gpio_set_direction(FLASH_GPIO, GPIO_MODE_OUTPUT);  
+
+    if (status)  
+        gpio_set_level(FLASH_GPIO, 1);
+    else
+        gpio_set_level(FLASH_GPIO, 0);      
+}
+
+void CCamera::GetCameraParameter(httpd_req_t *req, int &qual, framesize_t &resol)
+{
+    char _query[100];
+    char _qual[10];
+    char _size[10];
+
+    resol = ActualResolution;
+    qual = ActualQuality;
+
+
+    if (httpd_req_get_url_query_str(req, _query, 100) == ESP_OK)
+    {
+        printf("Query: "); printf(_query); printf("\n");
+        if (httpd_query_key_value(_query, "size", _size, 10) == ESP_OK)
+        {
+            printf("Size: "); printf(_size); printf("\n");            
+            if (strcmp(_size, "QVGA") == 0)
+                resol = FRAMESIZE_QVGA;       // 320x240
+            if (strcmp(_size, "VGA") == 0)
+                resol = FRAMESIZE_VGA;      // 640x480
+            if (strcmp(_size, "SVGA") == 0)
+                resol = FRAMESIZE_SVGA;     // 800x600
+            if (strcmp(_size, "XGA") == 0)
+                resol = FRAMESIZE_XGA;      // 1024x768
+            if (strcmp(_size, "SXGA") == 0)
+                resol = FRAMESIZE_SXGA;     // 1280x1024
+            if (strcmp(_size, "UXGA") == 0)
+                resol = FRAMESIZE_UXGA;     // 1600x1200                
+        }
+        if (httpd_query_key_value(_query, "quality", _qual, 10) == ESP_OK)
+        {
+            printf("Quality: "); printf(_qual); printf("\n");
+            qual = atoi(_qual);
+                
+            if (qual > 63)
+                qual = 63;
+            if (qual < 0)
+                qual = 0;
+        }
+    };
+}
+
+framesize_t CCamera::TextToFramesize(const char * _size)
+{
+    if (strcmp(_size, "QVGA") == 0)
+        return FRAMESIZE_QVGA;       // 320x240
+    if (strcmp(_size, "VGA") == 0)
+        return FRAMESIZE_VGA;      // 640x480
+    if (strcmp(_size, "SVGA") == 0)
+        return FRAMESIZE_SVGA;     // 800x600
+    if (strcmp(_size, "XGA") == 0)
+        return FRAMESIZE_XGA;      // 1024x768
+    if (strcmp(_size, "SXGA") == 0)
+        return FRAMESIZE_SXGA;     // 1280x1024
+    if (strcmp(_size, "UXGA") == 0)
+        return FRAMESIZE_UXGA;     // 1600x1200   
+    return ActualResolution;
+}
+
+
+CCamera::CCamera()
+{
+    printf("CreateClassCamera\n");
+}
+
+esp_err_t CCamera::InitCam()
+{
+    printf("Init Flash\n");
+    //power up the camera if PWDN pin is defined
+    if(PWDN_GPIO_NUM != -1){
+        // Init the GPIO
+        gpio_pad_select_gpio(PWDN_GPIO_NUM);
+        /* Set the GPIO as a push/pull output */
+        gpio_set_direction(PWDN_GPIO_NUM, GPIO_MODE_OUTPUT);
+        gpio_set_level(PWDN_GPIO_NUM, 0);
+    }
+
+    printf("Init Camera\n");
+    ActualQuality = camera_config.jpeg_quality;
+    ActualResolution = camera_config.frame_size;
+    //initialize the camera
+    esp_err_t err = esp_camera_init(&camera_config);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAGCAMERACLASS, "Camera Init Failed");
+        return err;
+    }
+
+    return ESP_OK;
+}

+ 46 - 0
code/lib/jomjol_controlcamera/ClassControllCamera.h

@@ -0,0 +1,46 @@
+#ifndef CLASSCONTROLLCAMERA_H
+#define CLASSCONTROLLCAMERA_H
+
+#include <string>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "freertos/event_groups.h"
+
+#include "esp_camera.h"
+#include <string>
+#include "esp_http_server.h"
+
+
+#define CAMERA_MODEL_AI_THINKER
+
+
+static const char *TAGCAMERACLASS = "server_part_camera";
+
+
+class CCamera {
+    protected:
+        int ActualQuality;
+        framesize_t ActualResolution;
+
+    public:
+        CCamera();
+        esp_err_t InitCam();
+
+        void LightOnOff(bool status);
+        esp_err_t CaptureToHTTP(httpd_req_t *req, int delay = 0);
+        void SetQualitySize(int qual, framesize_t resol);
+        void GetCameraParameter(httpd_req_t *req, int &qual, framesize_t &resol);
+
+        framesize_t TextToFramesize(const char * text);
+
+
+        esp_err_t CaptureToFile(std::string nm, int delay = 0);
+        
+};
+
+
+extern CCamera Camera;
+
+
+#endif

+ 97 - 0
code/lib/jomjol_controlcamera/camera_define.h

@@ -0,0 +1,97 @@
+#define CAMERA_MODEL_AI_THINKER
+
+
+#if defined(CAMERA_MODEL_WROVER_KIT)
+#define PWDN_GPIO_NUM    -1
+#define RESET_GPIO_NUM   -1
+#define XCLK_GPIO_NUM    21
+#define SIOD_GPIO_NUM    26
+#define SIOC_GPIO_NUM    27
+
+#define Y9_GPIO_NUM      35
+#define Y8_GPIO_NUM      34
+#define Y7_GPIO_NUM      39
+#define Y6_GPIO_NUM      36
+#define Y5_GPIO_NUM      19
+#define Y4_GPIO_NUM      18
+#define Y3_GPIO_NUM       5
+#define Y2_GPIO_NUM       4
+#define VSYNC_GPIO_NUM   25
+#define HREF_GPIO_NUM    23
+#define PCLK_GPIO_NUM    22
+
+#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
+#define PWDN_GPIO_NUM     -1
+#define RESET_GPIO_NUM    15
+#define XCLK_GPIO_NUM     27
+#define SIOD_GPIO_NUM     25
+#define SIOC_GPIO_NUM     23
+
+#define Y9_GPIO_NUM       19
+#define Y8_GPIO_NUM       36
+#define Y7_GPIO_NUM       18
+#define Y6_GPIO_NUM       39
+#define Y5_GPIO_NUM        5
+#define Y4_GPIO_NUM       34
+#define Y3_GPIO_NUM       35
+#define Y2_GPIO_NUM       32
+#define VSYNC_GPIO_NUM    22
+#define HREF_GPIO_NUM     26
+#define PCLK_GPIO_NUM     21
+
+#elif defined(CAMERA_MODEL_AI_THINKER)
+#define PWDN_GPIO_NUM     GPIO_NUM_32
+#define RESET_GPIO_NUM    -1
+#define XCLK_GPIO_NUM      GPIO_NUM_0
+#define SIOD_GPIO_NUM     GPIO_NUM_26
+#define SIOC_GPIO_NUM     GPIO_NUM_27
+
+#define Y9_GPIO_NUM       GPIO_NUM_35
+#define Y8_GPIO_NUM       GPIO_NUM_34
+#define Y7_GPIO_NUM       GPIO_NUM_39
+#define Y6_GPIO_NUM       GPIO_NUM_36
+#define Y5_GPIO_NUM       GPIO_NUM_21
+#define Y4_GPIO_NUM       GPIO_NUM_19
+#define Y3_GPIO_NUM       GPIO_NUM_18
+#define Y2_GPIO_NUM        GPIO_NUM_5
+#define VSYNC_GPIO_NUM    GPIO_NUM_25
+#define HREF_GPIO_NUM     GPIO_NUM_23
+#define PCLK_GPIO_NUM     GPIO_NUM_22
+
+#else
+#error "Camera model not selected"
+#endif
+
+
+
+static camera_config_t camera_config = {
+    .pin_pwdn  = PWDN_GPIO_NUM,
+    .pin_reset = RESET_GPIO_NUM,
+    .pin_xclk = XCLK_GPIO_NUM,
+    .pin_sscb_sda = SIOD_GPIO_NUM,
+    .pin_sscb_scl = SIOC_GPIO_NUM,
+
+    .pin_d7 = Y9_GPIO_NUM,
+    .pin_d6 = Y8_GPIO_NUM,
+    .pin_d5 = Y7_GPIO_NUM,
+    .pin_d4 = Y6_GPIO_NUM,
+    .pin_d3 = Y5_GPIO_NUM,
+    .pin_d2 = Y4_GPIO_NUM,
+    .pin_d1 = Y3_GPIO_NUM,
+    .pin_d0 = Y2_GPIO_NUM,
+    .pin_vsync = VSYNC_GPIO_NUM,
+    .pin_href = HREF_GPIO_NUM,
+    .pin_pclk = PCLK_GPIO_NUM,
+
+    //XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental)
+    .xclk_freq_hz = 20000000,
+    .ledc_timer = LEDC_TIMER_0,
+    .ledc_channel = LEDC_CHANNEL_0,
+
+    .pixel_format = PIXFORMAT_JPEG,//YUV422,GRAYSCALE,RGB565,JPEG
+//    .pixel_format = PIXFORMAT_RGB888,//YUV422,GRAYSCALE,RGB565,JPEG
+    .frame_size = FRAMESIZE_UXGA,//QQVGA-QXGA Do not use sizes above QVGA when not JPEG
+
+    .jpeg_quality = 5, //0-63 lower number means higher quality
+    .fb_count = 1 //if more than one, i2s runs in continuous mode. Use only with JPEG
+};

+ 200 - 0
code/lib/jomjol_controlcamera/esp_camera.h

@@ -0,0 +1,200 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+/*
+ * Example Use
+ *
+    static camera_config_t camera_example_config = {
+        .pin_pwdn       = PIN_PWDN,
+        .pin_reset      = PIN_RESET,
+        .pin_xclk       = PIN_XCLK,
+        .pin_sscb_sda   = PIN_SIOD,
+        .pin_sscb_scl   = PIN_SIOC,
+        .pin_d7         = PIN_D7,
+        .pin_d6         = PIN_D6,
+        .pin_d5         = PIN_D5,
+        .pin_d4         = PIN_D4,
+        .pin_d3         = PIN_D3,
+        .pin_d2         = PIN_D2,
+        .pin_d1         = PIN_D1,
+        .pin_d0         = PIN_D0,
+        .pin_vsync      = PIN_VSYNC,
+        .pin_href       = PIN_HREF,
+        .pin_pclk       = PIN_PCLK,
+
+        .xclk_freq_hz   = 20000000,
+        .ledc_timer     = LEDC_TIMER_0,
+        .ledc_channel   = LEDC_CHANNEL_0,
+        .pixel_format   = PIXFORMAT_JPEG,
+        .frame_size     = FRAMESIZE_SVGA,
+        .jpeg_quality   = 10,
+        .fb_count       = 2
+    };
+
+    esp_err_t camera_example_init(){
+        return esp_camera_init(&camera_example_config);
+    }
+
+    esp_err_t camera_example_capture(){
+        //capture a frame
+        camera_fb_t * fb = esp_camera_fb_get();
+        if (!fb) {
+            ESP_LOGE(TAG, "Frame buffer could not be acquired");
+            return ESP_FAIL;
+        }
+
+        //replace this with your own function
+        display_image(fb->width, fb->height, fb->pixformat, fb->buf, fb->len);
+
+        //return the frame buffer back to be reused
+        esp_camera_fb_return(fb);
+
+        return ESP_OK;
+    }
+*/
+
+#pragma once
+
+#ifndef ESPCAMERADEF
+#define ESPCAMERADEF
+
+#include "esp_err.h"
+#include "driver/ledc.h"
+#include "sensor.h"
+#include "sys/time.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Configuration structure for camera initialization
+ */
+typedef struct {
+    int pin_pwdn;                   /*!< GPIO pin for camera power down line */
+    int pin_reset;                  /*!< GPIO pin for camera reset line */
+    int pin_xclk;                   /*!< GPIO pin for camera XCLK line */
+    int pin_sscb_sda;               /*!< GPIO pin for camera SDA line */
+    int pin_sscb_scl;               /*!< GPIO pin for camera SCL line */
+    int pin_d7;                     /*!< GPIO pin for camera D7 line */
+    int pin_d6;                     /*!< GPIO pin for camera D6 line */
+    int pin_d5;                     /*!< GPIO pin for camera D5 line */
+    int pin_d4;                     /*!< GPIO pin for camera D4 line */
+    int pin_d3;                     /*!< GPIO pin for camera D3 line */
+    int pin_d2;                     /*!< GPIO pin for camera D2 line */
+    int pin_d1;                     /*!< GPIO pin for camera D1 line */
+    int pin_d0;                     /*!< GPIO pin for camera D0 line */
+    int pin_vsync;                  /*!< GPIO pin for camera VSYNC line */
+    int pin_href;                   /*!< GPIO pin for camera HREF line */
+    int pin_pclk;                   /*!< GPIO pin for camera PCLK line */
+
+    int xclk_freq_hz;               /*!< Frequency of XCLK signal, in Hz. Either 20KHz or 10KHz for OV2640 double FPS (Experimental) */
+
+    ledc_timer_t ledc_timer;        /*!< LEDC timer to be used for generating XCLK  */
+    ledc_channel_t ledc_channel;    /*!< LEDC channel to be used for generating XCLK  */
+
+    pixformat_t pixel_format;       /*!< Format of the pixel data: PIXFORMAT_ + YUV422|GRAYSCALE|RGB565|JPEG  */
+    framesize_t frame_size;         /*!< Size of the output image: FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA  */
+
+    int jpeg_quality;               /*!< Quality of JPEG output. 0-63 lower means higher quality  */
+    size_t fb_count;                /*!< Number of frame buffers to be allocated. If more than one, then each frame will be acquired (double speed)  */
+} camera_config_t;
+
+/**
+ * @brief Data structure of camera frame buffer
+ */
+typedef struct {
+    uint8_t * buf;              /*!< Pointer to the pixel data */
+    size_t len;                 /*!< Length of the buffer in bytes */
+    size_t width;               /*!< Width of the buffer in pixels */
+    size_t height;              /*!< Height of the buffer in pixels */
+    pixformat_t format;         /*!< Format of the pixel data */
+    struct timeval timestamp;   /*!< Timestamp since boot of the first DMA buffer of the frame */
+} camera_fb_t;
+
+#define ESP_ERR_CAMERA_BASE 0x20000
+#define ESP_ERR_CAMERA_NOT_DETECTED             (ESP_ERR_CAMERA_BASE + 1)
+#define ESP_ERR_CAMERA_FAILED_TO_SET_FRAME_SIZE (ESP_ERR_CAMERA_BASE + 2)
+#define ESP_ERR_CAMERA_FAILED_TO_SET_OUT_FORMAT (ESP_ERR_CAMERA_BASE + 3)
+#define ESP_ERR_CAMERA_NOT_SUPPORTED            (ESP_ERR_CAMERA_BASE + 4)
+
+/**
+ * @brief Initialize the camera driver
+ *
+ * @note call camera_probe before calling this function
+ *
+ * This function detects and configures camera over I2C interface,
+ * allocates framebuffer and DMA buffers,
+ * initializes parallel I2S input, and sets up DMA descriptors.
+ *
+ * Currently this function can only be called once and there is
+ * no way to de-initialize this module.
+ *
+ * @param config  Camera configuration parameters
+ *
+ * @return ESP_OK on success
+ */
+esp_err_t esp_camera_init(const camera_config_t* config);
+
+/**
+ * @brief Deinitialize the camera driver
+ *
+ * @return
+ *      - ESP_OK on success
+ *      - ESP_ERR_INVALID_STATE if the driver hasn't been initialized yet
+ */
+esp_err_t esp_camera_deinit();
+
+/**
+ * @brief Obtain pointer to a frame buffer.
+ *
+ * @return pointer to the frame buffer
+ */
+camera_fb_t* esp_camera_fb_get();
+
+/**
+ * @brief Return the frame buffer to be reused again.
+ *
+ * @param fb    Pointer to the frame buffer
+ */
+void esp_camera_fb_return(camera_fb_t * fb);
+
+/**
+ * @brief Get a pointer to the image sensor control structure
+ *
+ * @return pointer to the sensor
+ */
+sensor_t * esp_camera_sensor_get();
+
+/**
+ * @brief Save camera settings to non-volatile-storage (NVS)
+ * 
+ * @param key   A unique nvs key name for the camera settings 
+ */
+esp_err_t esp_camera_save_to_nvs(const char *key);
+
+/**
+ * @brief Load camera settings from non-volatile-storage (NVS)
+ * 
+ * @param key   A unique nvs key name for the camera settings 
+ */
+esp_err_t esp_camera_load_from_nvs(const char *key);
+
+#ifdef __cplusplus
+}
+#endif
+
+#include "img_converters.h"
+
+#endif
+

+ 126 - 0
code/lib/jomjol_controlcamera/img_converters.h

@@ -0,0 +1,126 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef _IMG_CONVERTERS_H_
+#define _IMG_CONVERTERS_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include "esp_camera.h"
+
+typedef size_t (* jpg_out_cb)(void * arg, size_t index, const void* data, size_t len);
+
+/**
+ * @brief Convert image buffer to JPEG
+ *
+ * @param src       Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format
+ * @param src_len   Length in bytes of the source buffer
+ * @param width     Width in pixels of the source image
+ * @param height    Height in pixels of the source image
+ * @param format    Format of the source image
+ * @param quality   JPEG quality of the resulting image
+ * @param cp        Callback to be called to write the bytes of the output JPEG
+ * @param arg       Pointer to be passed to the callback
+ *
+ * @return true on success
+ */
+bool fmt2jpg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, jpg_out_cb cb, void * arg);
+
+/**
+ * @brief Convert camera frame buffer to JPEG
+ *
+ * @param fb        Source camera frame buffer
+ * @param quality   JPEG quality of the resulting image
+ * @param cp        Callback to be called to write the bytes of the output JPEG
+ * @param arg       Pointer to be passed to the callback
+ *
+ * @return true on success
+ */
+bool frame2jpg_cb(camera_fb_t * fb, uint8_t quality, jpg_out_cb cb, void * arg);
+
+/**
+ * @brief Convert image buffer to JPEG buffer
+ *
+ * @param src       Source buffer in RGB565, RGB888, YUYV or GRAYSCALE format
+ * @param src_len   Length in bytes of the source buffer
+ * @param width     Width in pixels of the source image
+ * @param height    Height in pixels of the source image
+ * @param format    Format of the source image
+ * @param quality   JPEG quality of the resulting image
+ * @param out       Pointer to be populated with the address of the resulting buffer
+ * @param out_len   Pointer to be populated with the length of the output buffer
+ *
+ * @return true on success
+ */
+bool fmt2jpg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t quality, uint8_t ** out, size_t * out_len);
+
+/**
+ * @brief Convert camera frame buffer to JPEG buffer
+ *
+ * @param fb        Source camera frame buffer
+ * @param quality   JPEG quality of the resulting image
+ * @param out       Pointer to be populated with the address of the resulting buffer
+ * @param out_len   Pointer to be populated with the length of the output buffer
+ *
+ * @return true on success
+ */
+bool frame2jpg(camera_fb_t * fb, uint8_t quality, uint8_t ** out, size_t * out_len);
+
+/**
+ * @brief Convert image buffer to BMP buffer
+ *
+ * @param src       Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format
+ * @param src_len   Length in bytes of the source buffer
+ * @param width     Width in pixels of the source image
+ * @param height    Height in pixels of the source image
+ * @param format    Format of the source image
+ * @param out       Pointer to be populated with the address of the resulting buffer
+ * @param out_len   Pointer to be populated with the length of the output buffer
+ *
+ * @return true on success
+ */
+bool fmt2bmp(uint8_t *src, size_t src_len, uint16_t width, uint16_t height, pixformat_t format, uint8_t ** out, size_t * out_len);
+
+/**
+ * @brief Convert camera frame buffer to BMP buffer
+ *
+ * @param fb        Source camera frame buffer
+ * @param out       Pointer to be populated with the address of the resulting buffer
+ * @param out_len   Pointer to be populated with the length of the output buffer
+ *
+ * @return true on success
+ */
+bool frame2bmp(camera_fb_t * fb, uint8_t ** out, size_t * out_len);
+
+/**
+ * @brief Convert image buffer to RGB888 buffer (used for face detection)
+ *
+ * @param src       Source buffer in JPEG, RGB565, RGB888, YUYV or GRAYSCALE format
+ * @param src_len   Length in bytes of the source buffer
+ * @param format    Format of the source image
+ * @param rgb_buf   Pointer to the output buffer (width * height * 3)
+ *
+ * @return true on success
+ */
+bool fmt2rgb888(const uint8_t *src_buf, size_t src_len, pixformat_t format, uint8_t * rgb_buf);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IMG_CONVERTERS_H_ */

+ 191 - 0
code/lib/jomjol_controlcamera/sensor.h

@@ -0,0 +1,191 @@
+/*
+ * This file is part of the OpenMV project.
+ * Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
+ * This work is licensed under the MIT license, see the file LICENSE for details.
+ *
+ * Sensor abstraction layer.
+ *
+ */
+#ifndef __SENSOR_H__
+#define __SENSOR_H__
+#include <stdint.h>
+#include <stdbool.h>
+
+#define OV9650_PID     (0x96)
+#define OV7725_PID     (0x77)
+#define OV2640_PID     (0x26)
+#define OV3660_PID     (0x36)
+#define OV5640_PID     (0x56)
+
+typedef enum {
+    PIXFORMAT_RGB565,    // 2BPP/RGB565
+    PIXFORMAT_YUV422,    // 2BPP/YUV422
+    PIXFORMAT_GRAYSCALE, // 1BPP/GRAYSCALE
+    PIXFORMAT_JPEG,      // JPEG/COMPRESSED
+    PIXFORMAT_RGB888,    // 3BPP/RGB888
+    PIXFORMAT_RAW,       // RAW
+    PIXFORMAT_RGB444,    // 3BP2P/RGB444
+    PIXFORMAT_RGB555,    // 3BP2P/RGB555
+} pixformat_t;
+
+typedef enum {
+    FRAMESIZE_96X96,    // 96x96
+    FRAMESIZE_QQVGA,    // 160x120
+    FRAMESIZE_QCIF,     // 176x144
+    FRAMESIZE_HQVGA,    // 240x176
+    FRAMESIZE_240X240,  // 240x240
+    FRAMESIZE_QVGA,     // 320x240
+    FRAMESIZE_CIF,      // 400x296
+    FRAMESIZE_HVGA,     // 480x320
+    FRAMESIZE_VGA,      // 640x480
+    FRAMESIZE_SVGA,     // 800x600
+    FRAMESIZE_XGA,      // 1024x768
+    FRAMESIZE_HD,       // 1280x720
+    FRAMESIZE_SXGA,     // 1280x1024
+    FRAMESIZE_UXGA,     // 1600x1200
+    // 3MP Sensors
+    FRAMESIZE_FHD,      // 1920x1080
+    FRAMESIZE_P_HD,     //  720x1280
+    FRAMESIZE_P_3MP,    //  864x1536
+    FRAMESIZE_QXGA,     // 2048x1536
+    // 5MP Sensors
+    FRAMESIZE_QHD,      // 2560x1440
+    FRAMESIZE_WQXGA,    // 2560x1600
+    FRAMESIZE_P_FHD,    // 1080x1920
+    FRAMESIZE_QSXGA,    // 2560x1920
+    FRAMESIZE_INVALID
+} framesize_t;
+
+typedef enum {
+    ASPECT_RATIO_4X3,
+    ASPECT_RATIO_3X2,
+    ASPECT_RATIO_16X10,
+    ASPECT_RATIO_5X3,
+    ASPECT_RATIO_16X9,
+    ASPECT_RATIO_21X9,
+    ASPECT_RATIO_5X4,
+    ASPECT_RATIO_1X1,
+    ASPECT_RATIO_9X16
+} aspect_ratio_t;
+
+typedef enum {
+    GAINCEILING_2X,
+    GAINCEILING_4X,
+    GAINCEILING_8X,
+    GAINCEILING_16X,
+    GAINCEILING_32X,
+    GAINCEILING_64X,
+    GAINCEILING_128X,
+} gainceiling_t;
+
+typedef struct {
+        uint16_t max_width;
+        uint16_t max_height;
+        uint16_t start_x;
+        uint16_t start_y;
+        uint16_t end_x;
+        uint16_t end_y;
+        uint16_t offset_x;
+        uint16_t offset_y;
+        uint16_t total_x;
+        uint16_t total_y;
+} ratio_settings_t;
+
+typedef struct {
+        const uint16_t width;
+        const uint16_t height;
+        const aspect_ratio_t aspect_ratio;
+} resolution_info_t;
+
+// Resolution table (in sensor.c)
+extern const resolution_info_t resolution[];
+
+typedef struct {
+    uint8_t MIDH;
+    uint8_t MIDL;
+    uint8_t PID;
+    uint8_t VER;
+} sensor_id_t;
+
+typedef struct {
+    framesize_t framesize;//0 - 10
+    bool scale;
+    bool binning;
+    uint8_t quality;//0 - 63
+    int8_t brightness;//-2 - 2
+    int8_t contrast;//-2 - 2
+    int8_t saturation;//-2 - 2
+    int8_t sharpness;//-2 - 2
+    uint8_t denoise;
+    uint8_t special_effect;//0 - 6
+    uint8_t wb_mode;//0 - 4
+    uint8_t awb;
+    uint8_t awb_gain;
+    uint8_t aec;
+    uint8_t aec2;
+    int8_t ae_level;//-2 - 2
+    uint16_t aec_value;//0 - 1200
+    uint8_t agc;
+    uint8_t agc_gain;//0 - 30
+    uint8_t gainceiling;//0 - 6
+    uint8_t bpc;
+    uint8_t wpc;
+    uint8_t raw_gma;
+    uint8_t lenc;
+    uint8_t hmirror;
+    uint8_t vflip;
+    uint8_t dcw;
+    uint8_t colorbar;
+} camera_status_t;
+
+typedef struct _sensor sensor_t;
+typedef struct _sensor {
+    sensor_id_t id;             // Sensor ID.
+    uint8_t  slv_addr;          // Sensor I2C slave address.
+    pixformat_t pixformat;
+    camera_status_t status;
+    int xclk_freq_hz;
+
+    // Sensor function pointers
+    int  (*init_status)         (sensor_t *sensor);
+    int  (*reset)               (sensor_t *sensor);
+    int  (*set_pixformat)       (sensor_t *sensor, pixformat_t pixformat);
+    int  (*set_framesize)       (sensor_t *sensor, framesize_t framesize);
+    int  (*set_contrast)        (sensor_t *sensor, int level);
+    int  (*set_brightness)      (sensor_t *sensor, int level);
+    int  (*set_saturation)      (sensor_t *sensor, int level);
+    int  (*set_sharpness)       (sensor_t *sensor, int level);
+    int  (*set_denoise)         (sensor_t *sensor, int level);
+    int  (*set_gainceiling)     (sensor_t *sensor, gainceiling_t gainceiling);
+    int  (*set_quality)         (sensor_t *sensor, int quality);
+    int  (*set_colorbar)        (sensor_t *sensor, int enable);
+    int  (*set_whitebal)        (sensor_t *sensor, int enable);
+    int  (*set_gain_ctrl)       (sensor_t *sensor, int enable);
+    int  (*set_exposure_ctrl)   (sensor_t *sensor, int enable);
+    int  (*set_hmirror)         (sensor_t *sensor, int enable);
+    int  (*set_vflip)           (sensor_t *sensor, int enable);
+
+    int  (*set_aec2)            (sensor_t *sensor, int enable);
+    int  (*set_awb_gain)        (sensor_t *sensor, int enable);
+    int  (*set_agc_gain)        (sensor_t *sensor, int gain);
+    int  (*set_aec_value)       (sensor_t *sensor, int gain);
+
+    int  (*set_special_effect)  (sensor_t *sensor, int effect);
+    int  (*set_wb_mode)         (sensor_t *sensor, int mode);
+    int  (*set_ae_level)        (sensor_t *sensor, int level);
+
+    int  (*set_dcw)             (sensor_t *sensor, int enable);
+    int  (*set_bpc)             (sensor_t *sensor, int enable);
+    int  (*set_wpc)             (sensor_t *sensor, int enable);
+
+    int  (*set_raw_gma)         (sensor_t *sensor, int enable);
+    int  (*set_lenc)            (sensor_t *sensor, int enable);
+
+    int  (*get_reg)             (sensor_t *sensor, int reg, int mask);
+    int  (*set_reg)             (sensor_t *sensor, int reg, int mask, int value);
+    int  (*set_res_raw)         (sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning);
+    int  (*set_pll)             (sensor_t *sensor, int bypass, int mul, int sys, int root, int pre, int seld5, int pclken, int pclk);
+    int  (*set_xclk)            (sensor_t *sensor, int timer, int xclk);
+} sensor_t;
+
+#endif /* __SENSOR_H__ */

+ 175 - 0
code/lib/jomjol_controlcamera/server_camera.cpp

@@ -0,0 +1,175 @@
+#include "server_camera.h"
+
+#include <string>
+#include "string.h"
+
+#include "esp_camera.h"
+#include "ClassControllCamera.h"
+
+#include "ClassLogFile.h"
+
+#define SCRATCH_BUFSIZE2  8192 
+char scratch2[SCRATCH_BUFSIZE2];
+
+
+esp_err_t handler_lightOn(httpd_req_t *req)
+{
+    LogFile.WriteToFile("handler_lightOn");
+    printf("handler_lightOn uri:\n"); printf(req->uri); printf("\n");
+    Camera.LightOnOff(true);
+    const char* resp_str = (const char*) req->user_ctx;
+    httpd_resp_send(req, resp_str, strlen(resp_str));      
+    return ESP_OK;
+};
+
+esp_err_t handler_lightOff(httpd_req_t *req)
+{
+    LogFile.WriteToFile("handler_lightOff");
+    printf("handler_lightOff uri:\n"); printf(req->uri); printf("\n");
+    Camera.LightOnOff(false);
+    const char* resp_str = (const char*) req->user_ctx;
+    httpd_resp_send(req, resp_str, strlen(resp_str));       
+    return ESP_OK;
+};
+
+esp_err_t handler_capture(httpd_req_t *req)
+{
+    LogFile.WriteToFile("handler_capture");
+    int quality;
+    framesize_t res;
+
+    Camera.GetCameraParameter(req, quality, res);
+    printf("Size: %d", res); printf(" Quality: %d\n", quality);
+    Camera.SetQualitySize(quality, res);
+
+    esp_err_t ressult;
+    ressult = Camera.CaptureToHTTP(req);
+    return ressult;
+};
+
+
+esp_err_t handler_capture_with_ligth(httpd_req_t *req)
+{
+    LogFile.WriteToFile("handler_capture_with_ligth");
+    char _query[100];
+    char _delay[10];
+
+    int quality;
+    framesize_t res;    
+    int delay = 2500;
+
+    if (httpd_req_get_url_query_str(req, _query, 100) == ESP_OK)
+    {
+        printf("Query: "); printf(_query); printf("\n");
+        if (httpd_query_key_value(_query, "delay", _delay, 10) == ESP_OK)
+        {
+            printf("Delay: "); printf(_delay); printf("\n");            
+            delay = atoi(_delay);
+
+            if (delay < 0)
+                delay = 0;
+        }
+    };
+
+    Camera.GetCameraParameter(req, quality, res);
+    printf("Size: %d", res); printf(" Quality: %d\n", quality);
+    Camera.SetQualitySize(quality, res);
+
+    Camera.LightOnOff(true);
+    const TickType_t xDelay = delay / portTICK_PERIOD_MS;
+    vTaskDelay( xDelay );
+
+    esp_err_t ressult;
+    ressult = Camera.CaptureToHTTP(req);  
+
+    Camera.LightOnOff(false);
+   
+    return ressult;
+};
+
+
+
+esp_err_t handler_capture_save_to_file(httpd_req_t *req)
+{
+    LogFile.WriteToFile("handler_capture_save_to_file");
+    char _query[100];
+    char _delay[10];
+    int delay = 0;
+    char filename[100];
+    std::string fn = "/sdcard/";
+
+
+    int quality;
+    framesize_t res;    
+
+    if (httpd_req_get_url_query_str(req, _query, 100) == ESP_OK)
+    {
+        printf("Query: "); printf(_query); printf("\n");
+        if (httpd_query_key_value(_query, "filename", filename, 100) == ESP_OK)
+        {
+            fn.append(filename);
+            printf("Filename: "); printf(fn.c_str()); printf("\n");            
+        }
+        else
+            fn.append("noname.jpg");
+
+        if (httpd_query_key_value(_query, "delay", _delay, 10) == ESP_OK)
+        {
+            printf("Delay: "); printf(_delay); printf("\n");            
+            delay = atoi(_delay);
+
+            if (delay < 0)
+                delay = 0;
+        }
+
+    }
+    else
+        fn.append("noname.jpg");
+
+    Camera.GetCameraParameter(req, quality, res);
+    printf("Size: %d", res); printf(" Quality: %d\n", quality);
+    Camera.SetQualitySize(quality, res);
+
+    esp_err_t ressult;
+    ressult = Camera.CaptureToFile(fn, delay);  
+
+    const char* resp_str = (const char*) fn.c_str();
+    httpd_resp_send(req, resp_str, strlen(resp_str));  
+  
+    return ressult;
+};
+
+
+
+void register_server_camera_uri(httpd_handle_t server)
+{
+    ESP_LOGI(TAGPARTCAMERA, "server_part_camera - Registering URI handlers");
+    
+    httpd_uri_t camuri = { };
+    camuri.method    = HTTP_GET;
+
+    camuri.uri       = "/lighton";
+    camuri.handler   = handler_lightOn;
+    camuri.user_ctx  = (void*) "Light On";    
+    httpd_register_uri_handler(server, &camuri);
+
+    camuri.uri       = "/lightoff";
+    camuri.handler   = handler_lightOff;
+    camuri.user_ctx  = (void*) "Light Off"; 
+    httpd_register_uri_handler(server, &camuri);    
+
+    camuri.uri       = "/capture";
+    camuri.handler   = handler_capture;
+    camuri.user_ctx  = NULL; 
+    httpd_register_uri_handler(server, &camuri);      
+
+    camuri.uri       = "/capture_with_flashlight";
+    camuri.handler   = handler_capture_with_ligth;
+    camuri.user_ctx  = NULL; 
+    httpd_register_uri_handler(server, &camuri);  
+
+    camuri.uri       = "/save";
+    camuri.handler   = handler_capture_save_to_file;
+    camuri.user_ctx  = NULL; 
+    httpd_register_uri_handler(server, &camuri);    
+}

+ 14 - 0
code/lib/jomjol_controlcamera/server_camera.h

@@ -0,0 +1,14 @@
+#ifndef JOMJOL_CONTROLCAMERA_H
+#define JOMJOL_CONTROLCAMERA_H
+
+#include <esp_log.h>
+
+#include <esp_http_server.h>
+
+//#include "ClassControllCamera.h"
+
+static const char *TAGPARTCAMERA = "server_camera";
+
+void register_server_camera_uri(httpd_handle_t server);
+
+#endif

+ 539 - 0
code/lib/jomjol_fileserver_ota/server_file.cpp

@@ -0,0 +1,539 @@
+/* HTTP File Server Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+
+#include "server_file.h"
+
+
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <sys/param.h>
+#include <sys/unistd.h>
+#include <sys/stat.h>
+#include <dirent.h>
+
+#include "esp_err.h"
+#include "esp_log.h"
+
+#include "esp_vfs.h"
+#include "esp_spiffs.h"
+#include "esp_http_server.h"
+
+#include "ClassLogFile.h"
+
+#include "server_help.h"
+
+/* Max length a file path can have on storage */
+// #define FILE_PATH_MAX (ESP_VFS_PATH_MAX + CONFIG_SPIFFS_OBJ_NAME_LEN)
+#define FILE_PATH_MAX (255)
+
+/* Max size of an individual file. Make sure this
+ * value is same as that set in upload_script.html */
+#define MAX_FILE_SIZE   (2000*1024) // 200 KB
+#define MAX_FILE_SIZE_STR "2000KB"
+
+/* Scratch buffer size */
+#define SCRATCH_BUFSIZE  8192 
+
+struct file_server_data {
+    /* Base path of file storage */
+    char base_path[ESP_VFS_PATH_MAX + 1];
+
+    /* Scratch buffer for temporary storage during file transfer */
+    char scratch[SCRATCH_BUFSIZE];
+};
+
+static const char *TAG = "file_server";
+
+/* Handler to redirect incoming GET request for /index.html to /
+ * This can be overridden by uploading file with same name */
+static esp_err_t index_html_get_handler(httpd_req_t *req)
+{
+    httpd_resp_set_status(req, "307 Temporary Redirect");
+    httpd_resp_set_hdr(req, "Location", "/");
+    httpd_resp_send(req, NULL, 0);  // Response body can be empty
+    return ESP_OK;
+}
+
+/* Handler to respond with an icon file embedded in flash.
+ * Browsers expect to GET website icon at URI /favicon.ico.
+ * This can be overridden by uploading file with same name */
+static esp_err_t favicon_get_handler(httpd_req_t *req)
+{
+    extern const unsigned char favicon_ico_start[] asm("_binary_favicon_ico_start");
+    extern const unsigned char favicon_ico_end[]   asm("_binary_favicon_ico_end");
+    const size_t favicon_ico_size = (favicon_ico_end - favicon_ico_start);
+    httpd_resp_set_type(req, "image/x-icon");
+    httpd_resp_send(req, (const char *)favicon_ico_start, favicon_ico_size);
+    /* Respond with an empty chunk to signal HTTP response completion */
+    httpd_resp_send_chunk(req, NULL, 0);       
+    return ESP_OK;
+}
+
+/* Send HTTP response with a run-time generated html consisting of
+ * a list of all files and folders under the requested path.
+ * In case of SPIFFS this returns empty list when path is any
+ * string other than '/', since SPIFFS doesn't support directories */
+static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath)
+{
+    char entrypath[FILE_PATH_MAX];
+    char entrysize[16];
+    const char *entrytype;
+
+    struct dirent *entry;
+    struct stat entry_stat;
+
+    char dirpath_corrected[FILE_PATH_MAX];
+    strcpy(dirpath_corrected, dirpath);
+
+    file_server_data * server_data = (file_server_data *) req->user_ctx;
+    if ((strlen(dirpath_corrected)-1) > strlen(server_data->base_path))      // if dirpath is not mountpoint, the last "\" needs to be removed
+        dirpath_corrected[strlen(dirpath_corrected)-1] = '\0';
+
+    DIR *dir = opendir(dirpath_corrected);
+
+    const size_t dirpath_len = strlen(dirpath);
+    printf("Dirpath: <%s>, Pathlength: %d\n", dirpath, dirpath_len);    
+
+    /* Retrieve the base path of file storage to construct the full path */
+    strlcpy(entrypath, dirpath, sizeof(entrypath));
+    printf("entrypath: <%s>\n", entrypath);    
+
+    if (!dir) {
+        ESP_LOGE(TAG, "Failed to stat dir : %s", dirpath);
+        /* Respond with 404 Not Found */
+        httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Directory does not exist");
+        return ESP_FAIL;
+    }
+
+    /* Send HTML file header */
+    httpd_resp_sendstr_chunk(req, "<!DOCTYPE html><html><body>");
+
+    /* Get handle to embedded file upload script */
+    extern const unsigned char upload_script_start[] asm("_binary_upload_script_html_start");
+    extern const unsigned char upload_script_end[]   asm("_binary_upload_script_html_end");
+    const size_t upload_script_size = (upload_script_end - upload_script_start);
+
+    /* Add file upload form and script which on execution sends a POST request to /upload */
+    httpd_resp_send_chunk(req, (const char *)upload_script_start, upload_script_size);
+
+
+/////////////////////////////////////////////////
+/*
+    FILE *fd = fopen("/sdcard/html/file_server_upload_script.html", "r");
+    char *chunk = ((struct file_server_data *)req->user_ctx)->scratch;
+    size_t chunksize;
+    do {
+        chunksize = fread(chunk, 1, SCRATCH_BUFSIZE, fd);
+        if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
+            fclose(fd);
+            ESP_LOGE(TAG, "File sending failed!");
+            return ESP_FAIL;
+        }
+    } while (chunksize != 0);
+    fclose(fd);
+    ESP_LOGI(TAG, "File sending complete");
+    httpd_resp_send_chunk(req, NULL, 0);
+*/
+///////////////////////////////
+
+
+    /* Send file-list table definition and column labels */
+    httpd_resp_sendstr_chunk(req,
+        "<table class=\"fixed\" border=\"1\">"
+        "<col width=\"800px\" /><col width=\"300px\" /><col width=\"300px\" /><col width=\"100px\" />"
+        "<thead><tr><th>Name</th><th>Type</th><th>Size (Bytes)</th><th>Delete</th></tr></thead>"
+        "<tbody>");
+
+    /* Iterate over all files / folders and fetch their names and sizes */
+    while ((entry = readdir(dir)) != NULL) {
+        entrytype = (entry->d_type == DT_DIR ? "directory" : "file");
+
+        strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len);
+        printf("Entrypath: %s\n", entrypath);
+        if (stat(entrypath, &entry_stat) == -1) {
+            ESP_LOGE(TAG, "Failed to stat %s : %s", entrytype, entry->d_name);
+            continue;
+        }
+        sprintf(entrysize, "%ld", entry_stat.st_size);
+        ESP_LOGI(TAG, "Found %s : %s (%s bytes)", entrytype, entry->d_name, entrysize);
+
+        /* Send chunk of HTML file containing table entries with file name and size */
+        httpd_resp_sendstr_chunk(req, "<tr><td><a href=\"");
+        httpd_resp_sendstr_chunk(req, req->uri);
+        httpd_resp_sendstr_chunk(req, entry->d_name);
+        if (entry->d_type == DT_DIR) {
+            httpd_resp_sendstr_chunk(req, "/");
+        }
+        httpd_resp_sendstr_chunk(req, "\">");
+        httpd_resp_sendstr_chunk(req, entry->d_name);
+        httpd_resp_sendstr_chunk(req, "</a></td><td>");
+        httpd_resp_sendstr_chunk(req, entrytype);
+        httpd_resp_sendstr_chunk(req, "</td><td>");
+        httpd_resp_sendstr_chunk(req, entrysize);
+        httpd_resp_sendstr_chunk(req, "</td><td>");
+        httpd_resp_sendstr_chunk(req, "<form method=\"post\" action=\"/delete");
+        httpd_resp_sendstr_chunk(req, req->uri + strlen("/fileserver"));
+        httpd_resp_sendstr_chunk(req, entry->d_name);
+        httpd_resp_sendstr_chunk(req, "\"><button type=\"submit\">Delete</button></form>");
+        httpd_resp_sendstr_chunk(req, "</td></tr>\n");
+    }
+    closedir(dir);
+
+    /* Finish the file list table */
+    httpd_resp_sendstr_chunk(req, "</tbody></table>");
+
+    /* Send remaining chunk of HTML file to complete it */
+    httpd_resp_sendstr_chunk(req, "</body></html>");
+
+    /* Send empty chunk to signal HTTP response completion */
+    httpd_resp_sendstr_chunk(req, NULL);
+    return ESP_OK;
+}
+
+#define IS_FILE_EXT(filename, ext) \
+    (strcasecmp(&filename[strlen(filename) - sizeof(ext) + 1], ext) == 0)
+
+
+/* Handler to download a file kept on the server */
+static esp_err_t download_get_handler(httpd_req_t *req)
+{
+    LogFile.WriteToFile("download_get_handler");
+    char filepath[FILE_PATH_MAX];
+    FILE *fd = NULL;
+    struct stat file_stat;
+    printf("uri: %s\n", req->uri);
+
+    const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
+                                             req->uri  + sizeof("/fileserver") - 1, sizeof(filepath));    
+
+    printf("1 uri: %s, filename: %s, filepath: %s\n", req->uri, filename, filepath);
+
+//    filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
+//                                             req->uri, sizeof(filepath));
+
+    if (!filename) {
+        ESP_LOGE(TAG, "Filename is too long");
+        /* Respond with 500 Internal Server Error */
+        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long");
+        return ESP_FAIL;
+    }
+
+    /* If name has trailing '/', respond with directory contents */
+    if (filename[strlen(filename) - 1] == '/') {
+        return http_resp_dir_html(req, filepath);
+    }
+
+    if (stat(filepath, &file_stat) == -1) {
+        /* If file not present on SPIFFS check if URI
+         * corresponds to one of the hardcoded paths */
+        ESP_LOGE(TAG, "Failed to stat file : %s", filepath);
+        /* Respond with 404 Not Found */
+        httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist");
+        return ESP_FAIL;
+    }
+
+    fd = fopen(filepath, "r");
+    if (!fd) {
+        ESP_LOGE(TAG, "Failed to read existing file : %s", filepath);
+        /* Respond with 500 Internal Server Error */
+        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file");
+        return ESP_FAIL;
+    }
+
+    ESP_LOGI(TAG, "Sending file : %s (%ld bytes)...", filename, file_stat.st_size);
+    set_content_type_from_file(req, filename);
+
+    /* Retrieve the pointer to scratch buffer for temporary storage */
+    char *chunk = ((struct file_server_data *)req->user_ctx)->scratch;
+    size_t chunksize;
+    do {
+        /* Read file in chunks into the scratch buffer */
+        chunksize = fread(chunk, 1, SCRATCH_BUFSIZE, fd);
+
+        /* Send the buffer contents as HTTP response chunk */
+        if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
+            fclose(fd);
+            ESP_LOGE(TAG, "File sending failed!");
+            /* Abort sending file */
+            httpd_resp_sendstr_chunk(req, NULL);
+            /* Respond with 500 Internal Server Error */
+            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
+            return ESP_FAIL;
+        }
+
+        /* Keep looping till the whole file is sent */
+    } while (chunksize != 0);
+
+    /* Close file after sending complete */
+    fclose(fd);
+    ESP_LOGI(TAG, "File sending complete");
+
+    /* Respond with an empty chunk to signal HTTP response completion */
+    httpd_resp_send_chunk(req, NULL, 0);
+    return ESP_OK;
+}
+
+/* Handler to upload a file onto the server */
+static esp_err_t upload_post_handler(httpd_req_t *req)
+{
+    LogFile.WriteToFile("upload_post_handler");
+    char filepath[FILE_PATH_MAX];
+    FILE *fd = NULL;
+    struct stat file_stat;
+
+    /* Skip leading "/upload" from URI to get filename */
+    /* Note sizeof() counts NULL termination hence the -1 */
+    const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
+                                             req->uri + sizeof("/upload") - 1, sizeof(filepath));
+    if (!filename) {
+        /* Respond with 500 Internal Server Error */
+        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long");
+        return ESP_FAIL;
+    }
+
+    /* Filename cannot have a trailing '/' */
+    if (filename[strlen(filename) - 1] == '/') {
+        ESP_LOGE(TAG, "Invalid filename : %s", filename);
+        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Invalid filename");
+        return ESP_FAIL;
+    }
+
+    if (stat(filepath, &file_stat) == 0) {
+        ESP_LOGE(TAG, "File already exists : %s", filepath);
+        /* Respond with 400 Bad Request */
+        httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File already exists");
+        return ESP_FAIL;
+    }
+
+    /* File cannot be larger than a limit */
+    if (req->content_len > MAX_FILE_SIZE) {
+        ESP_LOGE(TAG, "File too large : %d bytes", req->content_len);
+        /* Respond with 400 Bad Request */
+        httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
+                            "File size must be less than "
+                            MAX_FILE_SIZE_STR "!");
+        /* Return failure to close underlying connection else the
+         * incoming file content will keep the socket busy */
+        return ESP_FAIL;
+    }
+
+    fd = fopen(filepath, "w");
+    if (!fd) {
+        ESP_LOGE(TAG, "Failed to create file : %s", filepath);
+        /* Respond with 500 Internal Server Error */
+        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create file");
+        return ESP_FAIL;
+    }
+
+    ESP_LOGI(TAG, "Receiving file : %s...", filename);
+
+    /* Retrieve the pointer to scratch buffer for temporary storage */
+    char *buf = ((struct file_server_data *)req->user_ctx)->scratch;
+    int received;
+
+    /* Content length of the request gives
+     * the size of the file being uploaded */
+    int remaining = req->content_len;
+
+    while (remaining > 0) {
+
+        ESP_LOGI(TAG, "Remaining size : %d", remaining);
+        /* Receive the file part by part into a buffer */
+        if ((received = httpd_req_recv(req, buf, MIN(remaining, SCRATCH_BUFSIZE))) <= 0) {
+            if (received == HTTPD_SOCK_ERR_TIMEOUT) {
+                /* Retry if timeout occurred */
+                continue;
+            }
+
+            /* In case of unrecoverable error,
+             * close and delete the unfinished file*/
+            fclose(fd);
+            unlink(filepath);
+
+            ESP_LOGE(TAG, "File reception failed!");
+            /* Respond with 500 Internal Server Error */
+            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file");
+            return ESP_FAIL;
+        }
+
+        /* Write buffer content to file on storage */
+        if (received && (received != fwrite(buf, 1, received, fd))) {
+            /* Couldn't write everything to file!
+             * Storage may be full? */
+            fclose(fd);
+            unlink(filepath);
+
+            ESP_LOGE(TAG, "File write failed!");
+            /* Respond with 500 Internal Server Error */
+            httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to write file to storage");
+            return ESP_FAIL;
+        }
+
+        /* Keep track of remaining size of
+         * the file left to be uploaded */
+        remaining -= received;
+    }
+
+    /* Close file upon upload completion */
+    fclose(fd);
+    ESP_LOGI(TAG, "File reception complete");
+
+    std::string directory = std::string(filepath);
+	size_t zw = directory.find("/");
+	size_t found = zw;
+	while (zw != std::string::npos)
+	{
+		zw = directory.find("/", found+1);  
+		if (zw != std::string::npos)
+			found = zw;
+	}
+
+    int start_fn = strlen(((struct file_server_data *)req->user_ctx)->base_path);
+    printf("Directory: %s, start_fn: %d, found: %d\n", directory.c_str(), start_fn, found);
+	directory = directory.substr(start_fn, found - start_fn + 1);
+    printf("Directory danach: %s\n", directory.c_str());    
+
+    directory = "/fileserver" + directory;
+    printf("Directory danach: %s\n", directory.c_str());   
+
+    /* Redirect onto root to see the updated file list */
+    httpd_resp_set_status(req, "303 See Other");
+    httpd_resp_set_hdr(req, "Location", directory.c_str());
+
+    /* Redirect onto root to see the updated file list */
+    httpd_resp_set_status(req, "303 See Other");
+    httpd_resp_set_hdr(req, "Location", directory.c_str());
+    httpd_resp_sendstr(req, "File uploaded successfully");
+    return ESP_OK;
+}
+
+/* Handler to delete a file from the server */
+static esp_err_t delete_post_handler(httpd_req_t *req)
+{
+    LogFile.WriteToFile("delete_post_handler");
+    char filepath[FILE_PATH_MAX];
+    struct stat file_stat;
+
+
+    /* Skip leading "/delete" from URI to get filename */
+    /* Note sizeof() counts NULL termination hence the -1 */
+    const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
+                                             req->uri  + sizeof("/delete") - 1, sizeof(filepath));
+    if (!filename) {
+        /* Respond with 500 Internal Server Error */
+        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long");
+        return ESP_FAIL;
+    }
+
+    /* Filename cannot have a trailing '/' */
+    if (filename[strlen(filename) - 1] == '/') {
+        ESP_LOGE(TAG, "Invalid filename : %s", filename);
+        httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Invalid filename");
+        return ESP_FAIL;
+    }
+
+    if (stat(filepath, &file_stat) == -1) {
+        ESP_LOGE(TAG, "File does not exist : %s", filename);
+        /* Respond with 400 Bad Request */
+        httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File does not exist");
+        return ESP_FAIL;
+    }
+
+    ESP_LOGI(TAG, "Deleting file : %s", filename);
+    /* Delete file */
+    unlink(filepath);
+
+    std::string directory = std::string(filepath);
+	size_t zw = directory.find("/");
+	size_t found = zw;
+	while (zw != std::string::npos)
+	{
+		zw = directory.find("/", found+1);  
+		if (zw != std::string::npos)
+			found = zw;
+	}
+
+    int start_fn = strlen(((struct file_server_data *)req->user_ctx)->base_path);
+    printf("Directory: %s, start_fn: %d, found: %d\n", directory.c_str(), start_fn, found);
+	directory = directory.substr(start_fn, found - start_fn + 1);
+    printf("Directory danach: %s\n", directory.c_str());    
+
+    directory = "/fileserver" + directory;
+    printf("Directory danach: %s\n", directory.c_str());   
+
+    /* Redirect onto root to see the updated file list */
+    httpd_resp_set_status(req, "303 See Other");
+    httpd_resp_set_hdr(req, "Location", directory.c_str());
+    httpd_resp_sendstr(req, "File deleted successfully");
+    return ESP_OK;
+}
+
+
+void register_server_file_uri(httpd_handle_t server, const char *base_path)
+{
+    static struct file_server_data *server_data = NULL;
+
+    /* Validate file storage base path */
+    if (!base_path) {
+//    if (!base_path || strcmp(base_path, "/spiffs") != 0) {
+        ESP_LOGE(TAG, "File server base_path not set");
+//        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (server_data) {
+        ESP_LOGE(TAG, "File server already started");
+//        return ESP_ERR_INVALID_STATE;
+    }
+
+    /* Allocate memory for server data */
+    server_data = (file_server_data *) calloc(1, sizeof(struct file_server_data));
+    if (!server_data) {
+        ESP_LOGE(TAG, "Failed to allocate memory for server data");
+//        return ESP_ERR_NO_MEM;
+    }
+    strlcpy(server_data->base_path, base_path,
+            sizeof(server_data->base_path));
+
+
+
+    /* URI handler for getting uploaded files */
+//    char zw[sizeof(serverprefix)+1];
+//    strcpy(zw, serverprefix);
+//    zw[strlen(serverprefix)] = '*';
+//    zw[strlen(serverprefix)+1] = '\0';    
+//    printf("zw: %s\n", zw);
+    httpd_uri_t file_download = {
+        .uri       = "/fileserver*",  // Match all URIs of type /path/to/file
+        .method    = HTTP_GET,
+        .handler   = download_get_handler,
+        .user_ctx  = server_data    // Pass server data as context
+    };
+    httpd_register_uri_handler(server, &file_download);
+
+    /* URI handler for uploading files to server */
+    httpd_uri_t file_upload = {
+        .uri       = "/upload/*",   // Match all URIs of type /upload/path/to/file
+        .method    = HTTP_POST,
+        .handler   = upload_post_handler,
+        .user_ctx  = server_data    // Pass server data as context
+    };
+    httpd_register_uri_handler(server, &file_upload);
+
+    /* URI handler for deleting files from server */
+    httpd_uri_t file_delete = {
+        .uri       = "/delete/*",   // Match all URIs of type /delete/path/to/file
+        .method    = HTTP_POST,
+        .handler   = delete_post_handler,
+        .user_ctx  = server_data    // Pass server data as context
+    };
+    httpd_register_uri_handler(server, &file_delete);
+
+}

+ 3 - 0
code/lib/jomjol_fileserver_ota/server_file.h

@@ -0,0 +1,3 @@
+#include <esp_http_server.h>
+
+void register_server_file_uri(httpd_handle_t server, const char *base_path);

+ 408 - 0
code/lib/jomjol_fileserver_ota/server_ota.cpp

@@ -0,0 +1,408 @@
+#include "server_ota.h"
+
+#include <string>
+#include "string.h"
+
+#include <esp_int_wdt.h>
+#include <esp_task_wdt.h>
+
+
+#include <string.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_system.h"
+#include "esp_event.h"
+#include "esp_event_loop.h"
+#include "esp_log.h"
+#include "esp_ota_ops.h"
+#include "esp_http_client.h"
+#include "esp_flash_partitions.h"
+#include "esp_partition.h"
+#include "nvs.h"
+#include "nvs_flash.h"
+#include "driver/gpio.h"
+#include "protocol_examples_common.h"
+#include "errno.h"
+
+#include <sys/stat.h>
+
+#include "server_tflite.h"
+
+#include "ClassLogFile.h"
+
+
+
+#define BUFFSIZE 1024
+#define HASH_LEN 32 /* SHA-256 digest length */
+
+
+/*an ota data write buffer ready to write to the flash*/
+static char ota_write_data[BUFFSIZE + 1] = { 0 };
+
+
+#define OTA_URL_SIZE 256
+
+
+static void infinite_loop(void)
+{
+    int i = 0;
+    ESP_LOGI(TAGPARTOTA, "When a new firmware is available on the server, press the reset button to download it");
+    while(1) {
+        ESP_LOGI(TAGPARTOTA, "Waiting for a new firmware ... %d", ++i);
+        vTaskDelay(2000 / portTICK_PERIOD_MS);
+    }
+}
+
+
+
+static bool ota_example_task(std::string fn)
+{
+    esp_err_t err;
+    /* update handle : set by esp_ota_begin(), must be freed via esp_ota_end() */
+    esp_ota_handle_t update_handle = 0 ;
+    const esp_partition_t *update_partition = NULL;
+
+    ESP_LOGI(TAGPARTOTA, "Starting OTA example");
+
+    const esp_partition_t *configured = esp_ota_get_boot_partition();
+    const esp_partition_t *running = esp_ota_get_running_partition();
+
+    if (configured != running) {
+        ESP_LOGW(TAGPARTOTA, "Configured OTA boot partition at offset 0x%08x, but running from offset 0x%08x",
+                 configured->address, running->address);
+        ESP_LOGW(TAGPARTOTA, "(This can happen if either the OTA boot data or preferred boot image become corrupted somehow.)");
+    }
+    ESP_LOGI(TAGPARTOTA, "Running partition type %d subtype %d (offset 0x%08x)",
+             running->type, running->subtype, running->address);
+
+
+    update_partition = esp_ota_get_next_update_partition(NULL);
+    ESP_LOGI(TAGPARTOTA, "Writing to partition subtype %d at offset 0x%x",
+             update_partition->subtype, update_partition->address);
+//    assert(update_partition != NULL);
+
+    int binary_file_length = 0;
+
+    // deal with all receive packet 
+    bool image_header_was_checked = false;
+
+    int data_read;     
+
+    FILE* f = fopen(fn.c_str(), "rb");     // vorher  nur "r"
+    data_read = fread(ota_write_data, 1, BUFFSIZE, f);
+
+    while (data_read > 0) {
+        if (data_read < 0) {
+            ESP_LOGE(TAGPARTOTA, "Error: SSL data read error");
+            return false;
+        } else if (data_read > 0) {
+            if (image_header_was_checked == false) {
+                esp_app_desc_t new_app_info;
+                if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
+                    // check current version with downloading
+                    memcpy(&new_app_info, &ota_write_data[sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t)], sizeof(esp_app_desc_t));
+                    ESP_LOGI(TAGPARTOTA, "New firmware version: %s", new_app_info.version);
+
+                    esp_app_desc_t running_app_info;
+                    if (esp_ota_get_partition_description(running, &running_app_info) == ESP_OK) {
+                        ESP_LOGI(TAGPARTOTA, "Running firmware version: %s", running_app_info.version);
+                    }
+
+                    const esp_partition_t* last_invalid_app = esp_ota_get_last_invalid_partition();
+                    esp_app_desc_t invalid_app_info;
+                    if (esp_ota_get_partition_description(last_invalid_app, &invalid_app_info) == ESP_OK) {
+                        ESP_LOGI(TAGPARTOTA, "Last invalid firmware version: %s", invalid_app_info.version);
+                    }
+
+                    // check current version with last invalid partition
+                    if (last_invalid_app != NULL) {
+                        if (memcmp(invalid_app_info.version, new_app_info.version, sizeof(new_app_info.version)) == 0) {
+                            ESP_LOGW(TAGPARTOTA, "New version is the same as invalid version.");
+                            ESP_LOGW(TAGPARTOTA, "Previously, there was an attempt to launch the firmware with %s version, but it failed.", invalid_app_info.version);
+                            ESP_LOGW(TAGPARTOTA, "The firmware has been rolled back to the previous version.");
+                            infinite_loop();
+                        }
+                    }
+
+/*
+                    if (memcmp(new_app_info.version, running_app_info.version, sizeof(new_app_info.version)) == 0) {
+                        ESP_LOGW(TAGPARTOTA, "Current running version is the same as a new. We will not continue the update.");
+                        infinite_loop();
+                    }
+*/
+                    image_header_was_checked = true;
+
+                    err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
+                    if (err != ESP_OK) {
+                        ESP_LOGE(TAGPARTOTA, "esp_ota_begin failed (%s)", esp_err_to_name(err));
+                        return false;
+                    }
+                    ESP_LOGI(TAGPARTOTA, "esp_ota_begin succeeded");
+                } else {
+                    ESP_LOGE(TAGPARTOTA, "received package is not fit len");
+                    return false;
+                }
+            }            
+            err = esp_ota_write( update_handle, (const void *)ota_write_data, data_read);
+            if (err != ESP_OK) {
+                return false;
+            }
+            binary_file_length += data_read;
+            ESP_LOGD(TAGPARTOTA, "Written image length %d", binary_file_length);
+        } else if (data_read == 0) {
+           //
+           // * As esp_http_client_read never returns negative error code, we rely on
+           // * `errno` to check for underlying transport connectivity closure if any
+           //
+            if (errno == ECONNRESET || errno == ENOTCONN) {
+                ESP_LOGE(TAGPARTOTA, "Connection closed, errno = %d", errno);
+                break;
+            }
+        }
+        data_read = fread(ota_write_data, 1, BUFFSIZE, f);
+    }
+    fclose(f);  
+
+    ESP_LOGI(TAGPARTOTA, "Total Write binary data length: %d", binary_file_length);
+
+    err = esp_ota_end(update_handle);
+    if (err != ESP_OK) {
+        if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
+            ESP_LOGE(TAGPARTOTA, "Image validation failed, image is corrupted");
+        }
+        ESP_LOGE(TAGPARTOTA, "esp_ota_end failed (%s)!", esp_err_to_name(err));
+        return false;
+    }
+
+    err = esp_ota_set_boot_partition(update_partition);
+    if (err != ESP_OK) {
+        ESP_LOGE(TAGPARTOTA, "esp_ota_set_boot_partition failed (%s)!", esp_err_to_name(err));
+
+    }
+//    ESP_LOGI(TAGPARTOTA, "Prepare to restart system!");
+//    esp_restart();
+
+    return true ;
+}
+
+
+static void print_sha256 (const uint8_t *image_hash, const char *label)
+{
+    char hash_print[HASH_LEN * 2 + 1];
+    hash_print[HASH_LEN * 2] = 0;
+    for (int i = 0; i < HASH_LEN; ++i) {
+        sprintf(&hash_print[i * 2], "%02x", image_hash[i]);
+    }
+    ESP_LOGI(TAGPARTOTA, "%s: %s", label, hash_print);
+}
+
+
+static bool diagnostic(void)
+{
+/*
+    gpio_config_t io_conf;
+    io_conf.intr_type    = (gpio_int_type_t) GPIO_PIN_INTR_DISABLE;
+    io_conf.mode         = GPIO_MODE_INPUT;
+    io_conf.pin_bit_mask = (1ULL << CONFIG_EXAMPLE_GPIO_DIAGNOSTIC);
+    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+    io_conf.pull_up_en   = GPIO_PULLUP_ENABLE;
+    gpio_config(&io_conf);
+
+    ESP_LOGI(TAGPARTOTA, "Diagnostics (5 sec)...");
+    vTaskDelay(5000 / portTICK_PERIOD_MS);
+
+    bool diagnostic_is_ok = gpio_get_level(CONFIG_EXAMPLE_GPIO_DIAGNOSTIC);
+
+    gpio_reset_pin(CONFIG_EXAMPLE_GPIO_DIAGNOSTIC);
+
+    return diagnostic_is_ok;
+*/
+    return true;
+}
+
+void CheckOTAUpdate(void)
+{
+    ESP_LOGI(TAGPARTOTA, "Start CheckOTAUpdateCheck ...");
+    printf("Start CheckOTAUpdateCheck ...\n");
+
+    uint8_t sha_256[HASH_LEN] = { 0 };
+    esp_partition_t partition;
+
+    // get sha256 digest for the partition table
+    partition.address   = ESP_PARTITION_TABLE_OFFSET;
+    partition.size      = ESP_PARTITION_TABLE_MAX_LEN;
+    partition.type      = ESP_PARTITION_TYPE_DATA;
+    esp_partition_get_sha256(&partition, sha_256);
+    print_sha256(sha_256, "SHA-256 for the partition table: ");
+
+    // get sha256 digest for bootloader
+    partition.address   = ESP_BOOTLOADER_OFFSET;
+    partition.size      = ESP_PARTITION_TABLE_OFFSET;
+    partition.type      = ESP_PARTITION_TYPE_APP;
+    esp_partition_get_sha256(&partition, sha_256);
+    print_sha256(sha_256, "SHA-256 for bootloader: ");
+
+    // get sha256 digest for running partition
+    esp_partition_get_sha256(esp_ota_get_running_partition(), sha_256);
+    print_sha256(sha_256, "SHA-256 for current firmware: ");
+
+    const esp_partition_t *running = esp_ota_get_running_partition();
+    esp_ota_img_states_t ota_state;
+    esp_err_t res_stat_partition = esp_ota_get_state_partition(running, &ota_state);
+    switch (res_stat_partition)
+    {
+        case ESP_OK:
+            printf("CheckOTAUpdate Partition: ESP_OK\n");
+            if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
+                if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
+                    // run diagnostic function ...
+                    bool diagnostic_is_ok = diagnostic();
+                    if (diagnostic_is_ok) {
+                        ESP_LOGI(TAGPARTOTA, "Diagnostics completed successfully! Continuing execution ...");
+                        printf("Diagnostics completed successfully! Continuing execution ...\n");
+                        esp_ota_mark_app_valid_cancel_rollback();
+                    } else {
+                        ESP_LOGE(TAGPARTOTA, "Diagnostics failed! Start rollback to the previous version ...");
+                        printf("Diagnostics failed! Start rollback to the previous version ...\n");
+                        esp_ota_mark_app_invalid_rollback_and_reboot();
+                    }
+                }
+            }            
+            break;
+        case ESP_ERR_INVALID_ARG:
+            printf("CheckOTAUpdate Partition: ESP_ERR_INVALID_ARG\n");
+            break;
+        case ESP_ERR_NOT_SUPPORTED:
+            printf("CheckOTAUpdate Partition: ESP_ERR_NOT_SUPPORTED\n");
+            break;
+        case ESP_ERR_NOT_FOUND:
+            printf("CheckOTAUpdate Partition: ESP_ERR_NOT_FOUND\n");
+            break;
+    }
+    if (esp_ota_get_state_partition(running, &ota_state) == ESP_OK) {
+        if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
+            // run diagnostic function ...
+            bool diagnostic_is_ok = diagnostic();
+            if (diagnostic_is_ok) {
+                ESP_LOGI(TAGPARTOTA, "Diagnostics completed successfully! Continuing execution ...");
+                printf("Diagnostics completed successfully! Continuing execution ...\n");
+                esp_ota_mark_app_valid_cancel_rollback();
+            } else {
+                ESP_LOGE(TAGPARTOTA, "Diagnostics failed! Start rollback to the previous version ...");
+                printf("Diagnostics failed! Start rollback to the previous version ...\n");
+                esp_ota_mark_app_invalid_rollback_and_reboot();
+            }
+        }
+    }
+}
+
+
+
+esp_err_t handler_ota_update(httpd_req_t *req)
+{
+    LogFile.WriteToFile("handler_ota_update");    
+    char _query[100];
+    char _filename[30];
+    std::string fn = "/sdcard/firmware/";
+    bool _file_del = false;
+
+    if (httpd_req_get_url_query_str(req, _query, 100) == ESP_OK)
+    {
+        printf("Query: "); printf(_query); printf("\n");
+        if (httpd_query_key_value(_query, "file", _filename, 30) == ESP_OK)
+        {
+            fn.append(_filename);
+            printf("File: "); printf(fn.c_str()); printf("\n");            
+        }
+        if (httpd_query_key_value(_query, "delete", _filename, 30) == ESP_OK)
+        {
+            fn.append(_filename);
+            _file_del = true;
+            printf("Delete Default File: "); printf(fn.c_str()); printf("\n");            
+        }
+
+    };
+
+    if (_file_del)
+    {
+        struct stat file_stat;
+        if (stat(fn.c_str(), &file_stat) != -1) {
+            printf("Deleting file : %s", fn.c_str());
+            /* Delete file */
+            unlink(fn.c_str());
+        }
+        /* Respond with an empty chunk to signal HTTP response completion */
+        httpd_resp_send_chunk(req, NULL, 0);
+        return ESP_OK;
+    }
+
+    const char* resp_str;    
+
+    if (ota_example_task(fn))
+    {
+        resp_str = "Firmware Update Successfull!<br><br>You can restart now.";
+    }
+    else
+    {
+        resp_str = "Error during Firmware Update!!!<br><br>Please check output of console.";
+    }
+
+    httpd_resp_send(req, resp_str, strlen(resp_str));  
+    
+    return ESP_OK;
+};
+
+void hard_restart() {
+  esp_task_wdt_init(1,true);
+  esp_task_wdt_add(NULL);
+  while(true);
+}
+
+void task_reboot(void *pvParameter)
+{
+
+
+    while(1)
+    {
+        vTaskDelay(5000 / portTICK_PERIOD_MS);
+        esp_restart();
+        hard_restart();
+    }
+
+    vTaskDelete(NULL); //Delete this task if it exits from the loop above
+}
+
+
+esp_err_t handler_reboot(httpd_req_t *req)
+{
+    LogFile.WriteToFile("handler_reboot");
+    ESP_LOGI(TAGPARTOTA, "!!! System will restart within 5 sec!!!");
+
+    const char* resp_str = "!!! System will restart within 5 sec!!!";
+    httpd_resp_send(req, resp_str, strlen(resp_str)); 
+    
+    KillTFliteTasks();
+
+    xTaskCreate(&task_reboot, "reboot", configMINIMAL_STACK_SIZE * 64, NULL, 10, NULL);
+
+    return ESP_OK;
+}
+
+void register_server_ota_sdcard_uri(httpd_handle_t server)
+{
+    ESP_LOGI(TAGPARTOTA, "server_ota - Registering URI handlers");
+    
+    httpd_uri_t camuri = { };
+    camuri.method    = HTTP_GET;
+    camuri.uri       = "/ota";
+    camuri.handler   = handler_ota_update;
+    camuri.user_ctx  = (void*) "Do OTA";    
+    httpd_register_uri_handler(server, &camuri);
+
+    camuri.method    = HTTP_GET;
+    camuri.uri       = "/reboot";
+    camuri.handler   = handler_reboot;
+    camuri.user_ctx  = (void*) "Reboot";    
+    httpd_register_uri_handler(server, &camuri);
+
+}

+ 10 - 0
code/lib/jomjol_fileserver_ota/server_ota.h

@@ -0,0 +1,10 @@
+#include <esp_log.h>
+
+#include <esp_http_server.h>
+
+//#include "ClassControllCamera.h"
+
+static const char *TAGPARTOTA = "server_ota";
+
+void register_server_ota_sdcard_uri(httpd_handle_t server);
+void CheckOTAUpdate();

+ 110 - 0
code/lib/jomjol_flowcontroll/ClassFlow.cpp

@@ -0,0 +1,110 @@
+#include "ClassFlow.h"
+#include <fstream>
+#include <string>
+#include <iostream>
+#include <string.h>
+
+
+
+void ClassFlow::SetInitialParameter(void)
+{
+	ListFlowControll = NULL;
+}
+
+std::vector<string> ClassFlow::ZerlegeZeile(std::string input)
+{
+	std::vector<string> Output;
+	std::string delimiter = " =,";
+
+	input = trim(input, delimiter);
+	size_t pos = findDelimiterPos(input, delimiter);
+	std::string token;
+	while (pos != std::string::npos) {
+		token = input.substr(0, pos);
+		token = trim(token, delimiter);
+		Output.push_back(token);
+		input.erase(0, pos + 1);
+		input = trim(input, delimiter);
+		pos = findDelimiterPos(input, delimiter);
+	}
+	Output.push_back(input);
+
+	return Output;
+
+}
+
+bool ClassFlow::isNewParagraph(string input)
+{
+	if (input[0] == '[')
+		return true;
+	return false;
+}
+
+bool ClassFlow::GetNextParagraph(FILE* pfile, string& aktparamgraph)
+{
+	while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph));
+
+	if (this->isNewParagraph(aktparamgraph))
+		return true;
+	return false;
+}
+
+
+ClassFlow::ClassFlow(void)
+{
+	SetInitialParameter();
+	ListFlowControll = NULL;
+}
+
+ClassFlow::ClassFlow(std::vector<ClassFlow*> * lfc)
+{
+	SetInitialParameter();	
+	ListFlowControll = lfc;
+}
+
+bool ClassFlow::ReadParameter(FILE* pfile, string &aktparamgraph)
+{
+	return false;
+}
+
+bool ClassFlow::doFlow(string time)
+{
+	return false;
+}
+
+string ClassFlow::getReadout()
+{
+	return string();
+}
+
+bool ClassFlow::getNextLine(FILE* pfile, string *rt)
+{
+	char zw[1024];
+	if (pfile == NULL)
+	{
+		*rt = "";
+		return false;
+	}
+	fgets(zw, 1024, pfile);
+	printf("%s", zw);
+	if ((strlen(zw) == 0) && feof(pfile))
+	{
+		*rt = "";
+		return false;
+	}
+	*rt = zw;
+	*rt = trim(*rt);
+	while (zw[0] == '#' || (rt->size() == 0))			// Kommentarzeilen und Leerzeilen überspringen
+	{
+		fgets(zw, 1024, pfile);
+		printf("%s", zw);		
+		if (feof(pfile))
+		{
+			*rt = "";
+			return false;
+		}
+		*rt = zw;
+		*rt = trim(*rt);
+	}
+	return true;
+}

+ 40 - 0
code/lib/jomjol_flowcontroll/ClassFlow.h

@@ -0,0 +1,40 @@
+#pragma once
+
+#include <fstream>
+#include <string>
+#include <vector>
+
+#include "Helper.h"
+#include "CFindTemplate.h"
+
+using namespace std;
+
+struct HTMLInfo
+{
+	float val;
+	std::string filename;
+};
+
+
+class ClassFlow
+{
+protected:
+	std::vector<string> ZerlegeZeile(string input);
+	bool isNewParagraph(string input);
+	bool GetNextParagraph(FILE* pfile, string& aktparamgraph);
+	bool getNextLine(FILE* pfile, string* rt);
+
+	std::vector<ClassFlow*>* ListFlowControll;
+
+	virtual void SetInitialParameter(void);
+
+public:
+	ClassFlow(void);
+	ClassFlow(std::vector<ClassFlow*> * lfc);
+	virtual bool ReadParameter(FILE* pfile, string &aktparamgraph);
+	virtual bool doFlow(string time);
+	virtual string getReadout();
+	virtual string name(){return "ClassFlow";};
+
+};
+

+ 108 - 0
code/lib/jomjol_flowcontroll/ClassFlowAlignment.cpp

@@ -0,0 +1,108 @@
+#include "ClassFlowAlignment.h"
+
+ClassFlowAlignment::ClassFlowAlignment()
+{
+    initalrotate = 0;
+    anz_ref = 0;
+    suchex = 40;
+    suchey = 40;
+    namerawimage =  "/sdcard/img_tmp/raw.jpg";
+    ListFlowControll = NULL;
+}
+
+ClassFlowAlignment::ClassFlowAlignment(std::vector<ClassFlow*>* lfc)
+{
+    initalrotate = 0;
+    anz_ref = 0;
+    suchex = 40;
+    suchey = 40;
+    namerawimage =  "/sdcard/img_tmp/raw.jpg";    
+    ListFlowControll = lfc;
+}
+
+bool ClassFlowAlignment::ReadParameter(FILE* pfile, string& aktparamgraph)
+{
+    std::vector<string> zerlegt;
+
+    aktparamgraph = trim(aktparamgraph);
+
+    if (aktparamgraph.size() == 0)
+        if (!this->GetNextParagraph(pfile, aktparamgraph))
+            return false;
+
+    if (aktparamgraph.compare("[Alignment]") != 0)       // Paragraph passt nich zu MakeImage
+        return false;
+
+    while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
+    {
+        zerlegt = this->ZerlegeZeile(aktparamgraph);
+        if ((zerlegt[0] == "InitalRotate") && (zerlegt.size() > 1))
+        {
+            this->initalrotate = std::stod(zerlegt[1]);
+        }
+        if ((zerlegt[0] == "SearchFieldX") && (zerlegt.size() > 1))
+        {
+            this->suchex = std::stod(zerlegt[1]);
+        }   
+        if ((zerlegt[0] == "SearchFieldY") && (zerlegt.size() > 1))
+        {
+            this->suchey = std::stod(zerlegt[1]);
+        }               
+        if ((zerlegt.size() == 3) && (anz_ref < 2))
+        {
+            this->reffilename[anz_ref] = FormatFileName("/sdcard" + zerlegt[0]);
+            this->ref_x[anz_ref] = std::stod(zerlegt[1]);
+            this->ref_y[anz_ref] = std::stod(zerlegt[2]);
+            anz_ref++;
+        }
+    }
+    return true;
+
+}
+
+bool ClassFlowAlignment::doFlow(string time)
+{
+    string input = namerawimage;
+    string output = "/sdcard/img_tmp/rot.jpg";
+    string output3 = "/sdcard/img_tmp/rot_roi.jpg";
+    string output2 = "/sdcard/img_tmp/alg.jpg";
+    string output4 = "/sdcard/img_tmp/alg_roi.jpg";
+
+    input = FormatFileName(input);
+    output = FormatFileName(output);
+    output2 = FormatFileName(output2);
+
+    CRotate *rt;
+
+    if (this->initalrotate != 0)
+    {
+        rt = new CRotate(input);
+        rt->Rotate(this->initalrotate);
+        rt->SaveToFile(output);
+        delete rt;
+    }
+    else
+    {
+        CopyFile(input, output);
+    }
+
+    CAlignAndCutImage *caic;
+    caic = new CAlignAndCutImage(output);
+    caic->Align(this->reffilename[0], this->ref_x[0], this->ref_y[0], this->reffilename[1], this->ref_x[1], this->ref_y[1], suchex, suchey, output3);
+    caic->SaveToFile(output2);
+
+    printf("Startwriting Output4:%s\n", output4.c_str());
+    if (output4.length() > 0)
+    {
+        caic->drawRect(ref_x[0], ref_y[0], caic->t0_dx, caic->t0_dy, 255, 0, 0, 2);
+        caic->drawRect(ref_x[1], ref_y[1], caic->t1_dx, caic->t1_dy, 255, 0, 0, 2);
+        caic->SaveToFile(output4);
+        printf("Write output4: %s\n", output4.c_str());
+    }
+
+    delete caic;
+
+    // Align mit Templates
+    return true;
+}
+

+ 28 - 0
code/lib/jomjol_flowcontroll/ClassFlowAlignment.h

@@ -0,0 +1,28 @@
+#pragma once
+
+#include "ClassFlow.h"
+#include "Helper.h"
+
+#include <string>
+
+using namespace std;
+
+class ClassFlowAlignment :
+    public ClassFlow
+{
+protected:
+    float initalrotate;
+    string reffilename[2];
+    int ref_x[2], ref_y[2];
+    int anz_ref;
+    int suchex, suchey;
+    string namerawimage;
+
+public:
+    ClassFlowAlignment();
+    ClassFlowAlignment(std::vector<ClassFlow*>* lfc);
+    bool ReadParameter(FILE* pfile, string& aktparamgraph);
+    bool doFlow(string time);
+    string name(){return "ClassFlowAlignment";};
+};
+

+ 257 - 0
code/lib/jomjol_flowcontroll/ClassFlowAnalog.cpp

@@ -0,0 +1,257 @@
+#include "ClassFlowAnalog.h"
+
+#include <math.h>
+#include <iomanip>
+#include <sstream>
+
+// #define OHNETFLITE
+
+#ifndef OHNETFLITE
+#include "CTfLiteClass.h"
+#endif
+
+ClassFlowAnalog::ClassFlowAnalog()
+{
+    isLogImage = false;
+    string cnnmodelfile = "";
+    modelxsize = 1;
+    modelysize = 1;
+    ListFlowControll = NULL;
+}
+
+ClassFlowAnalog::ClassFlowAnalog(std::vector<ClassFlow*>* lfc)
+{
+    isLogImage = false;
+    string cnnmodelfile = "";
+    modelxsize = 1;
+    modelysize = 1;
+    ListFlowControll = NULL;
+    ListFlowControll = lfc;
+}
+
+
+
+string ClassFlowAnalog::getReadout()
+{
+    int prev = -1;
+    string result = "";
+    for (int i = ROI.size() - 1; i >= 0; --i)
+    {
+        prev = ZeigerEval(ROI[i]->result, prev);
+        result = std::to_string(prev) + result;
+    }
+    return result;
+}
+
+int ClassFlowAnalog::ZeigerEval(float zahl, int ziffer_vorgaenger)
+{
+    int ergebnis_nachkomma = ((int) floor(zahl * 10)) % 10;
+    int ergebnis_vorkomma = ((int) floor(zahl)) % 10;
+    int ergebnis, ergebnis_rating;
+
+    if (ziffer_vorgaenger == -1)
+        return ergebnis_vorkomma % 10;
+
+    ergebnis_rating = ergebnis_nachkomma - ziffer_vorgaenger;
+    if (ergebnis_nachkomma >= 5)
+        ergebnis_rating-=5;
+    else
+        ergebnis_rating+=5;
+    ergebnis = (int) round(zahl);
+    if (ergebnis_rating < 0)
+        ergebnis-=1;
+    if (ergebnis == -1)
+        ergebnis+=10;
+
+    ergebnis = ergebnis % 10;
+    return ergebnis;
+}
+
+bool ClassFlowAnalog::ReadParameter(FILE* pfile, string& aktparamgraph)
+{
+    std::vector<string> zerlegt;
+
+    aktparamgraph = trim(aktparamgraph);
+
+    if (aktparamgraph.size() == 0)
+        if (!this->GetNextParagraph(pfile, aktparamgraph))
+            return false;
+
+
+    if (aktparamgraph.compare("[Analog]") != 0)       // Paragraph passt nich zu MakeImage
+        return false;
+
+    while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
+    {
+        zerlegt = this->ZerlegeZeile(aktparamgraph);
+        if ((zerlegt[0] == "LogImageLocation") && (zerlegt.size() > 1))
+        {
+            this->isLogImage = true;
+            this->LogImageLocation = zerlegt[1];
+        }
+        if ((zerlegt[0] == "Model") && (zerlegt.size() > 1))
+        {
+            this->cnnmodelfile = zerlegt[1];
+        }
+        if ((zerlegt[0] == "ModelInputSize") && (zerlegt.size() > 2))
+        {
+            this->modelxsize = std::stoi(zerlegt[1]);
+            this->modelysize = std::stoi(zerlegt[2]);
+        }
+        if (zerlegt.size() >= 5)
+        {
+            roianalog* neuroi = new roianalog;
+            neuroi->name = zerlegt[0];
+            neuroi->posx = std::stoi(zerlegt[1]);
+            neuroi->posy = std::stoi(zerlegt[2]);
+            neuroi->deltax = std::stoi(zerlegt[3]);
+            neuroi->deltay = std::stoi(zerlegt[4]);
+            ROI.push_back(neuroi);
+        }
+    }
+    return true;
+}
+
+
+bool ClassFlowAnalog::doFlow(string time)
+{
+    doAlignAndCut(time);
+    doNeuralNetwork(time);
+
+    return true;
+}
+
+bool ClassFlowAnalog::doAlignAndCut(string time)
+{
+    string input = "/sdcard/img_tmp/alg.jpg";
+    string input_roi = "/sdcard/img_tmp/alg_roi.jpg";
+    string ioresize = "/sdcard/img_tmp/resize.bmp";
+    string output;
+    string nm;
+    input = FormatFileName(input);
+    input_roi = FormatFileName(input_roi);   
+
+    CResizeImage *rs;
+    CImageBasis *img_roi = NULL;
+    CAlignAndCutImage *caic = new CAlignAndCutImage(input);
+
+    if (input_roi.length() > 0)
+        img_roi = new CImageBasis(input_roi);    
+
+    for (int i = 0; i < ROI.size(); ++i)
+    {
+        printf("Analog %d - Align&Cut\n", i);
+        output = "/sdcard/img_tmp/" + ROI[i]->name + ".jpg";
+        output = FormatFileName(output);
+        caic->CutAndSave(output, ROI[i]->posx, ROI[i]->posy, ROI[i]->deltax, ROI[i]->deltay);
+
+        rs = new CResizeImage(output);
+        rs->Resize(modelxsize, modelysize);
+        ioresize = "/sdcard/img_tmp/ra" + std::to_string(i) + ".bmp";
+        ioresize = FormatFileName(ioresize);
+        rs->SaveToFile(ioresize);
+        delete rs;
+
+        if (img_roi)
+        {
+            int r = 0;
+            int g = 255;
+            int b = 0;
+            img_roi->drawRect(ROI[i]->posx, ROI[i]->posy, ROI[i]->deltax, ROI[i]->deltay, r, g, b, 1);
+            img_roi->drawCircle((int) (ROI[i]->posx + ROI[i]->deltax/2), (int)  (ROI[i]->posy + ROI[i]->deltay/2), (int) (ROI[i]->deltax/2), r, g, b, 2);
+            img_roi->drawLine((int) (ROI[i]->posx + ROI[i]->deltax/2), (int) ROI[i]->posy, (int) (ROI[i]->posx + ROI[i]->deltax/2), (int) (ROI[i]->posy + ROI[i]->deltay), r, g, b, 2);
+            img_roi->drawLine((int) ROI[i]->posx, (int) (ROI[i]->posy + ROI[i]->deltay/2), (int) ROI[i]->posx + ROI[i]->deltax, (int) (ROI[i]->posy + ROI[i]->deltay/2), r, g, b, 2);
+        }
+    }
+    delete caic;
+
+
+    if (img_roi)
+    {
+        img_roi->SaveToFile(input_roi);
+        delete img_roi;
+    }
+
+
+    return true;
+} 
+
+bool ClassFlowAnalog::doNeuralNetwork(string time)
+{
+    string input = "/sdcard/img_tmp/alg.jpg";
+    string ioresize = "/sdcard/img_tmp/resize.bmp";
+    string output;
+    string nm;
+    input = FormatFileName(input);
+
+#ifndef OHNETFLITE
+    CTfLiteClass *tflite = new CTfLiteClass;  
+    string zwcnn = "/sdcard" + cnnmodelfile;
+    zwcnn = FormatFileName(zwcnn);
+    printf(zwcnn.c_str());printf("\n");
+    tflite->LoadModel(zwcnn); 
+    tflite->MakeAllocate();
+#endif
+
+    for (int i = 0; i < ROI.size(); ++i)
+    {
+        printf("Analog %d - TfLite\n", i);
+        ioresize = "/sdcard/img_tmp/ra" + std::to_string(i) + ".bmp";
+        ioresize = FormatFileName(ioresize);
+
+
+        float f1, f2;
+        f1 = 0; f2 = 0;
+
+#ifndef OHNETFLITE
+        tflite->LoadInputImage(ioresize);
+        tflite->Invoke();
+
+        f1 = tflite->GetOutputValue(0);
+        f2 = tflite->GetOutputValue(1);
+#endif
+
+        float result = fmod(atan2(f1, f2) / (M_PI * 2) + 2, 1);
+//        printf("Result sin, cos, ziffer: %f, %f, %f\n", f1, f2, result);  
+        ROI[i]->result = result * 10;
+
+        printf("Result Analog%i: %f\n", i, ROI[i]->result);           
+
+        if (isLogImage)
+        {
+            std::stringstream stream;
+            stream << std::fixed << std::setprecision(1) << ROI[i]->result;
+            std::string s = stream.str();
+//            std::snprintf(&s[0], s.size(), "%.2f", pi);
+            nm = "/sdcard" + LogImageLocation + "/" + s + "_" + ROI[i]->name + "_" + time + ".jpg";
+            nm = FormatFileName(nm);
+            output = "/sdcard/img_tmp/" + ROI[i]->name + ".jpg";
+            output = FormatFileName(output);
+            printf("Analog - save to file: %s\n", nm.c_str());
+            CopyFile(output, nm);
+        }        
+    }
+#ifndef OHNETFLITE
+        delete tflite;
+#endif    
+
+    return true;
+}
+
+
+std::vector<HTMLInfo*> ClassFlowAnalog::GetHTMLInfo()
+{
+    std::vector<HTMLInfo*> result;
+
+    for (int i = 0; i < ROI.size(); ++i)
+    {
+        HTMLInfo *zw = new HTMLInfo;
+        zw->filename = ROI[i]->name + ".jpg";
+        zw->val = ROI[i]->result;
+        result.push_back(zw);
+    }
+
+    return result;
+}
+
+

+ 36 - 0
code/lib/jomjol_flowcontroll/ClassFlowAnalog.h

@@ -0,0 +1,36 @@
+#pragma once
+#include "ClassFlow.h"
+// #include "CTfLiteClass.h"
+
+struct roianalog {
+    int posx, posy, deltax, deltay;
+    float result;
+    string name;
+};
+
+
+class ClassFlowAnalog :
+    public ClassFlow
+{
+protected:
+    string LogImageLocation;
+    bool isLogImage;
+    std::vector<roianalog*> ROI;
+    string cnnmodelfile;
+    int modelxsize, modelysize;
+    int ZeigerEval(float zahl, int ziffer_vorgaenger);
+
+public:
+    ClassFlowAnalog();
+    ClassFlowAnalog(std::vector<ClassFlow*>* lfc);
+    bool ReadParameter(FILE* pfile, string& aktparamgraph);
+    bool doFlow(string time);
+    string getReadout();    
+
+    bool doNeuralNetwork(string time); 
+    bool doAlignAndCut(string time);
+   	std::vector<HTMLInfo*> GetHTMLInfo();    
+
+    string name(){return "ClassFlowAnalog";};
+};
+

+ 212 - 0
code/lib/jomjol_flowcontroll/ClassFlowControll.cpp

@@ -0,0 +1,212 @@
+#include "ClassFlowControll.h"
+
+#include "Helper.h"
+
+std::vector<HTMLInfo*> ClassFlowControll::GetAllDigital()
+{
+    for (int i = 0; i < FlowControll.size(); ++i)
+        if (FlowControll[i]->name().compare("ClassFlowDigit") == 0)
+            return ((ClassFlowDigit*) (FlowControll[i]))->GetHTMLInfo();
+
+    std::vector<HTMLInfo*> empty;
+    return empty;
+}
+
+std::vector<HTMLInfo*> ClassFlowControll::GetAllAnalog()
+{
+    for (int i = 0; i < FlowControll.size(); ++i)
+        if (FlowControll[i]->name().compare("ClassFlowAnalog") == 0)
+            return ((ClassFlowAnalog*) (FlowControll[i]))->GetHTMLInfo();
+
+    std::vector<HTMLInfo*> empty;
+    return empty;
+}
+
+
+void ClassFlowControll::SetInitialParameter(void)
+{
+    AutoStart = false;
+    AutoIntervall = 10;
+}
+
+bool ClassFlowControll::isAutoStart(long &_intervall)
+{
+    _intervall = AutoIntervall * 60 * 1000; // AutoIntervall: Minuten -> ms
+    return AutoStart;
+}
+
+ClassFlow* ClassFlowControll::CreateClassFlow(std::string _type)
+{
+    ClassFlow* cfc = NULL;
+
+    _type = trim(_type);
+
+    if (_type.compare("[MakeImage]") == 0)
+        cfc = new ClassFlowMakeImage(&FlowControll);
+    if (_type.compare("[Alignment]") == 0)
+        cfc = new ClassFlowAlignment(&FlowControll);
+    if (_type.compare("[Analog]") == 0)
+        cfc = new ClassFlowAnalog(&FlowControll);
+    if (_type.compare("[Digits]") == 0)
+        cfc = new ClassFlowDigit(&FlowControll);
+    if (_type.compare("[PostProcessing]") == 0)
+    {
+        cfc = new ClassFlowPostProcessing(&FlowControll); 
+        flowpostprocessing = (ClassFlowPostProcessing*) cfc;
+    }
+
+    if (cfc)                            // Wird nur angehangen, falls es nicht [AutoTimer] ist, denn dieses ist für FlowControll
+        FlowControll.push_back(cfc);
+
+    if (_type.compare("[AutoTimer]") == 0)
+        cfc = this;    
+
+    return cfc;
+}
+
+void ClassFlowControll::InitFlow(std::string config)
+{
+    int aktFlow;
+    bool handeled;
+    string line;
+
+    flowpostprocessing = NULL;
+
+    ClassFlow* cfc;
+    FILE* pFile;
+    config = FormatFileName(config);
+    pFile = fopen(config.c_str(), "r");
+
+    line = "";
+    handeled = true;
+
+
+    char zw[1024];
+    if (pFile != NULL)
+    {
+        fgets(zw, 1024, pFile);
+        printf("%s", zw);
+        line = std::string(zw);
+    }
+
+    while ((line.size() > 0) && !(feof(pFile)))
+    {
+        cfc = CreateClassFlow(line);
+        if (cfc)
+        {
+            cfc->ReadParameter(pFile, line);
+        }
+        else
+        {
+            fgets(zw, 1024, pFile);
+            printf("%s", zw);
+            line = std::string(zw);
+        }
+    }
+
+    fclose(pFile);
+
+}
+
+bool ClassFlowControll::doFlow(string time)
+{
+    bool result = true;
+    for (int i = 0; i < FlowControll.size(); ++i)
+        result = result && FlowControll[i]->doFlow(time);
+    return result;
+}
+
+string ClassFlowControll::getReadout(bool _rawvalue = false)
+{
+    if (flowpostprocessing)
+        return flowpostprocessing->getReadout();
+
+    string zw = "";
+    string result = "";
+
+    for (int i = 0; i < FlowControll.size(); ++i)
+    {
+        zw = FlowControll[i]->getReadout();
+        if (zw.length() > 0)
+        {
+            if (result.length() == 0)
+                result = zw;
+            else
+                result = result + "\t" + zw;
+        }
+    }
+
+    return result;
+}
+
+string ClassFlowControll::GetPrevalue()	
+{
+    if (flowpostprocessing)
+    {
+        return flowpostprocessing->GetPreValue();   
+    }
+
+    return std::string();    
+}
+
+std::string ClassFlowControll::UpdatePrevalue(std::string _newvalue)
+{
+    float zw;
+    char* p;
+
+    _newvalue = trim(_newvalue);
+//    printf("Input UpdatePreValue: %s\n", _newvalue.c_str());
+
+    if (_newvalue.compare("0.0") == 0)
+    {
+        zw = 0;
+    }
+    else
+    {
+        zw = strtof(_newvalue.c_str(), &p);
+        if (zw == 0)
+            return "- Error in String to Value Conversion!!! Must be of format value=123.456";
+    }
+    
+
+    if (flowpostprocessing)
+    {
+        flowpostprocessing->SavePreValue(zw);
+        return _newvalue;    
+    }
+
+    return std::string();
+}
+
+bool ClassFlowControll::ReadParameter(FILE* pfile, string& aktparamgraph)
+{
+    std::vector<string> zerlegt;
+
+    aktparamgraph = trim(aktparamgraph);
+
+    if (aktparamgraph.size() == 0)
+        if (!this->GetNextParagraph(pfile, aktparamgraph))
+            return false;
+
+
+    if (aktparamgraph.compare("[AutoTimer]") != 0)       // Paragraph passt nich zu MakeImage
+        return false;
+
+    while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
+    {
+        zerlegt = this->ZerlegeZeile(aktparamgraph);
+        if ((zerlegt[0] == "AutoStart") && (zerlegt.size() > 1))
+        {
+            if (toUpper(zerlegt[1]) == "TRUE")
+            {
+                AutoStart = true;
+            }
+        }
+        if ((zerlegt[0] == "Intervall") && (zerlegt.size() > 1))
+        {
+            AutoIntervall = std::stof(zerlegt[1]);
+        }
+    }
+    return true;
+}
+

+ 41 - 0
code/lib/jomjol_flowcontroll/ClassFlowControll.h

@@ -0,0 +1,41 @@
+#pragma once
+
+#include <string>
+
+#include "ClassFlow.h"
+#include "ClassFlowMakeImage.h"
+#include "ClassFlowAlignment.h"
+#include "ClassFlowDigit.h"
+#include "ClassFlowAnalog.h"
+#include "ClassFlowPostProcessing.h"
+
+
+class ClassFlowControll :
+    public ClassFlow
+{
+protected:
+	std::vector<ClassFlow*> FlowControll;
+	ClassFlowPostProcessing* flowpostprocessing;
+	ClassFlow* CreateClassFlow(std::string _type);
+
+	bool AutoStart;
+	float AutoIntervall;
+	void SetInitialParameter(void);	
+
+
+public:
+	void InitFlow(std::string config);
+	bool doFlow(string time);
+	string getReadout(bool _rawvalue);
+	string UpdatePrevalue(std::string _newvalue);
+	string GetPrevalue();	
+	bool ReadParameter(FILE* pfile, string& aktparamgraph);	
+
+	bool isAutoStart(long &_intervall);
+
+	std::vector<HTMLInfo*> GetAllDigital();
+	std::vector<HTMLInfo*> GetAllAnalog();	
+
+	string name(){return "ClassFlowControll";};
+};
+

+ 210 - 0
code/lib/jomjol_flowcontroll/ClassFlowDigit.cpp

@@ -0,0 +1,210 @@
+#include "ClassFlowDigit.h"
+
+//#include "CFindTemplate.h"
+//#include "CTfLiteClass.h"
+
+// #define OHNETFLITE
+
+#ifndef OHNETFLITE
+#include "CTfLiteClass.h"
+#endif
+
+// #include "bitmap_image.hpp"
+
+ClassFlowDigit::ClassFlowDigit()
+{
+    isLogImage = false;
+    string cnnmodelfile = "";
+    modelxsize = 1;
+    modelysize = 1;
+    ListFlowControll = NULL;
+}
+
+ClassFlowDigit::ClassFlowDigit(std::vector<ClassFlow*>* lfc)
+{
+    isLogImage = false;
+    string cnnmodelfile = "";
+    modelxsize = 1;
+    modelysize = 1;
+    ListFlowControll = NULL;
+    ListFlowControll = lfc;
+}
+
+string ClassFlowDigit::getReadout()
+{
+    string rst = "";
+
+    for (int i = 0; i < ROI.size(); ++i)
+    {
+        if (ROI[i]->resultklasse == 10)
+            rst = rst + "N";
+        else
+            rst = rst + std::to_string(ROI[i]->resultklasse);
+    }
+
+    return rst;
+}
+
+bool ClassFlowDigit::ReadParameter(FILE* pfile, string& aktparamgraph)
+{
+    std::vector<string> zerlegt;
+
+    aktparamgraph = trim(aktparamgraph);
+
+    if (aktparamgraph.size() == 0)
+        if (!this->GetNextParagraph(pfile, aktparamgraph))
+            return false;
+
+
+    if (aktparamgraph.compare("[Digits]") != 0)       // Paragraph passt nich zu MakeImage
+        return false;
+
+    while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
+    {
+        zerlegt = this->ZerlegeZeile(aktparamgraph);
+        if ((zerlegt[0] == "LogImageLocation") && (zerlegt.size() > 1))
+        {
+            isLogImage = true;
+            LogImageLocation = zerlegt[1];
+        }
+        if ((zerlegt[0] == "Model") && (zerlegt.size() > 1))
+        {
+            cnnmodelfile = zerlegt[1];
+        }
+        if ((zerlegt[0] == "ModelInputSize") && (zerlegt.size() > 2))
+        {
+            modelxsize = std::stoi(zerlegt[1]);
+            modelysize = std::stoi(zerlegt[2]);
+        }
+        if (zerlegt.size() >= 5)
+        {
+            roi* neuroi = new roi;
+            neuroi->name = zerlegt[0];
+            neuroi->posx = std::stoi(zerlegt[1]);
+            neuroi->posy = std::stoi(zerlegt[2]);
+            neuroi->deltax = std::stoi(zerlegt[3]);
+            neuroi->deltay = std::stoi(zerlegt[4]);
+            ROI.push_back(neuroi);
+        }
+    }
+    return true;
+}
+
+
+bool ClassFlowDigit::doFlow(string time)
+{
+    doAlignAndCut(time);
+    doNeuralNetwork(time);
+
+    return true;
+}
+
+bool ClassFlowDigit::doAlignAndCut(string time)
+{
+    string input = "/sdcard/img_tmp/alg.jpg";
+    string input_roi = "/sdcard/img_tmp/alg_roi.jpg";
+    string ioresize = "/sdcard/img_tmp/resize.bmp";
+    string output;
+    string nm;
+    input = FormatFileName(input);
+    input_roi = FormatFileName(input_roi);   
+     
+    CResizeImage *rs;
+    CImageBasis *img_roi = NULL;
+    CAlignAndCutImage *caic = new CAlignAndCutImage(input);
+
+    if (input_roi.length() > 0)
+        img_roi = new CImageBasis(input_roi);
+
+
+    for (int i = 0; i < ROI.size(); ++i)
+    {
+        printf("DigitalDigit %d - Align&Cut\n", i);
+        output = "/sdcard/img_tmp/" + ROI[i]->name + ".jpg";
+        output = FormatFileName(output);
+        caic->CutAndSave(output, ROI[i]->posx, ROI[i]->posy, ROI[i]->deltax, ROI[i]->deltay);
+
+        rs = new CResizeImage(output);
+        rs->Resize(modelxsize, modelysize);
+        ioresize = "/sdcard/img_tmp/rd" + std::to_string(i) + ".bmp";
+        ioresize = FormatFileName(ioresize);
+        rs->SaveToFile(ioresize);
+        delete rs;
+
+        if (img_roi)
+        {
+            img_roi->drawRect(ROI[i]->posx, ROI[i]->posy, ROI[i]->deltax, ROI[i]->deltay, 0, 0, 255, 2);
+        }
+    }
+    delete caic;
+
+    if (img_roi)
+    {
+        img_roi->SaveToFile(input_roi);
+        delete img_roi;
+    }
+
+    return true;
+} 
+
+bool ClassFlowDigit::doNeuralNetwork(string time)
+{
+    string input = "/sdcard/img_tmp/alg.jpg";
+    string ioresize = "/sdcard/img_tmp/resize.bmp";
+    string output;
+    string nm;
+    input = FormatFileName(input);
+
+
+#ifndef OHNETFLITE
+    CTfLiteClass *tflite = new CTfLiteClass;  
+    string zwcnn = "/sdcard" + cnnmodelfile;
+    zwcnn = FormatFileName(zwcnn);
+    printf(zwcnn.c_str());printf("\n");
+    tflite->LoadModel(zwcnn); 
+    tflite->MakeAllocate();
+#endif
+
+    for (int i = 0; i < ROI.size(); ++i)
+    {
+        printf("DigitalDigit %d - TfLite\n", i);
+        ioresize = "/sdcard/img_tmp/rd" + std::to_string(i) + ".bmp";
+        ioresize = FormatFileName(ioresize);
+//        printf("output: %s, ioresize: %s\n", output.c_str(), ioresize.c_str());
+
+        ROI[i]->resultklasse = 0;
+#ifndef OHNETFLITE
+        ROI[i]->resultklasse = tflite->GetClassFromImage(ioresize);
+#endif
+        printf("Result Digit%i: %d\n", i, ROI[i]->resultklasse);           
+
+        if (isLogImage)
+        {
+            nm = "/sdcard" + LogImageLocation + "/" + std::to_string(ROI[i]->resultklasse) + "/" + time + "_" + ROI[i]->name + ".jpg";
+            output = "/sdcard/img_tmp/" + ROI[i]->name + ".jpg";
+            output = FormatFileName(output);            
+            nm = FormatFileName(nm);
+            CopyFile(output, nm);
+        }        
+    }
+#ifndef OHNETFLITE
+        delete tflite;
+#endif
+    return true;
+}
+
+
+std::vector<HTMLInfo*> ClassFlowDigit::GetHTMLInfo()
+{
+    std::vector<HTMLInfo*> result;
+
+    for (int i = 0; i < ROI.size(); ++i)
+    {
+        HTMLInfo *zw = new HTMLInfo;
+        zw->filename = ROI[i]->name + ".jpg";
+        zw->val = ROI[i]->resultklasse;
+        result.push_back(zw);
+    }
+
+    return result;
+}

+ 37 - 0
code/lib/jomjol_flowcontroll/ClassFlowDigit.h

@@ -0,0 +1,37 @@
+#pragma once
+#include "ClassFlow.h"
+#include "Helper.h"
+
+#include <string>
+
+struct roi {
+    int posx, posy, deltax, deltay;
+    int resultklasse;
+    string name;
+    roi* next;
+};
+
+class ClassFlowDigit :
+    public ClassFlow
+{
+protected:
+    string LogImageLocation;
+    bool isLogImage;
+    std::vector<roi*> ROI;
+    string cnnmodelfile;
+    int modelxsize, modelysize;
+
+    bool doNeuralNetwork(string time); 
+    bool doAlignAndCut(string time); 
+
+public:
+    ClassFlowDigit();
+    ClassFlowDigit(std::vector<ClassFlow*>* lfc);
+    bool ReadParameter(FILE* pfile, string& aktparamgraph);
+    bool doFlow(string time);
+    string getReadout();
+   	std::vector<HTMLInfo*> GetHTMLInfo();
+
+    string name(){return "ClassFlowDigit";};
+};
+

+ 154 - 0
code/lib/jomjol_flowcontroll/ClassFlowMakeImage.cpp

@@ -0,0 +1,154 @@
+#include "ClassFlowMakeImage.h"
+#include "Helper.h"
+
+
+#include "CFindTemplate.h"
+#include "ClassControllCamera.h"
+
+#include <time.h>
+
+
+esp_err_t ClassFlowMakeImage::camera_capture(){
+    string nm =  namerawimage;
+    Camera.CaptureToFile(nm);
+    return ESP_OK;
+}
+
+void ClassFlowMakeImage::takePictureWithFlash(int flashdauer)
+{
+    string nm = namerawimage;
+    if (isImageSize && (ImageQuality > 0))
+        Camera.SetQualitySize(ImageQuality, ImageSize);
+    printf("Start CaptureFile\n");
+    Camera.CaptureToFile(nm, flashdauer);
+}
+
+
+
+
+ClassFlowMakeImage::ClassFlowMakeImage()
+{
+    isLogImage = false;
+    waitbeforepicture = 5;
+    isImageSize = false;
+    ImageQuality = -1;    
+    TimeImageTaken = 0;
+    namerawimage =  "/sdcard/img_tmp/raw.jpg";
+}
+
+ClassFlowMakeImage::ClassFlowMakeImage(std::vector<ClassFlow*>* lfc)
+{
+    isLogImage = false;
+    waitbeforepicture = 5;
+    isImageSize = false;
+    ImageQuality = -1;
+    TimeImageTaken = 0;
+    namerawimage =  "/sdcard/img_tmp/raw.jpg";
+
+    ListFlowControll = lfc;
+}
+
+bool ClassFlowMakeImage::ReadParameter(FILE* pfile, string& aktparamgraph)
+{
+    std::vector<string> zerlegt;
+
+    aktparamgraph = trim(aktparamgraph);
+
+    if (aktparamgraph.size() == 0)
+        if (!this->GetNextParagraph(pfile, aktparamgraph))
+            return false;
+
+    if (aktparamgraph.compare("[MakeImage]") != 0)       // Paragraph passt nich zu MakeImage
+        return false;
+
+    while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
+    {
+        zerlegt = this->ZerlegeZeile(aktparamgraph);
+        if ((zerlegt[0] ==  "LogImageLocation") && (zerlegt.size() > 1))
+        {
+            this->isLogImage = true;
+            this->LogImageLocation = zerlegt[1];
+        }
+        if ((zerlegt[0] == "ImageQuality") && (zerlegt.size() > 1))
+            this->ImageQuality = std::stod(zerlegt[1]);
+        if ((zerlegt[0] == "ImageSize") && (zerlegt.size() > 1))
+        {
+            ImageSize = Camera.TextToFramesize(zerlegt[1].c_str());
+            isImageSize = true;
+        }
+    }
+   
+    return true;
+}
+
+
+void ClassFlowMakeImage::CopyFile(string input, string output)
+{
+    input = FormatFileName(input);
+    output = FormatFileName(output);
+    input = namerawimage;
+
+
+    printf("Copy Input : %s\n", input.c_str());
+    printf("Copy Output: %s\n", output.c_str());
+
+    char cTemp;
+    FILE* fpSourceFile = fopen(input.c_str(), "rb");
+    FILE* fpTargetFile = fopen(output.c_str(), "wb");
+
+    if (fpSourceFile == NULL)
+    {
+        printf("fpSourceFile == NULL\n");
+        perror("Error");
+    }
+
+    if (fpTargetFile == NULL)
+    {
+        printf("fpTargetFile == NULL\n");
+        perror("Error");
+    }
+
+
+    while (fread(&cTemp, 1, 1, fpSourceFile) == 1)
+    {
+        fwrite(&cTemp, 1, 1, fpTargetFile);
+    }
+
+    // Close The Files
+    fclose(fpSourceFile);
+    fclose(fpTargetFile);
+    printf("Copy done\n");
+}
+
+
+bool ClassFlowMakeImage::doFlow(string zwtime)
+{
+    ////////////////////////////////////////////////////////////////////
+    // TakeImage and Store into /image_tmp/raw.jpg  TO BE DONE
+    ////////////////////////////////////////////////////////////////////
+
+    int flashdauer = (int) waitbeforepicture * 1000;
+    
+
+    takePictureWithFlash(flashdauer);
+    time(&TimeImageTaken);
+    localtime(&TimeImageTaken);
+
+
+    if (this->isLogImage)
+    {
+        string nm = "/sdcard" + this->LogImageLocation + "/" + zwtime + ".jpg";
+        string input = "/sdcard/image_tmp/raw.jgp";
+        printf("loginput from: %s to: %s\n", input.c_str(), nm.c_str());
+        nm = FormatFileName(nm);
+        input = FormatFileName(input);
+        CopyFile(input, nm);
+    }
+
+    return true;
+}
+
+time_t ClassFlowMakeImage::getTimeImageTaken()
+{
+    return TimeImageTaken;
+}

+ 41 - 0
code/lib/jomjol_flowcontroll/ClassFlowMakeImage.h

@@ -0,0 +1,41 @@
+#pragma once
+#include "ClassFlow.h"
+#include "ClassControllCamera.h"
+
+#include <string>
+
+static const char* TAG2 = "example";
+
+#define BLINK_GPIO GPIO_NUM_4
+
+#define CAMERA_MODEL_AI_THINKER
+
+
+
+class ClassFlowMakeImage :
+    public ClassFlow
+{
+protected:
+    string LogImageLocation;
+    bool isLogImage;
+    float waitbeforepicture;
+    framesize_t ImageSize;
+    bool isImageSize;
+    int ImageQuality;
+    time_t TimeImageTaken;
+    string namerawimage;
+
+    void CopyFile(string input, string output);
+
+    esp_err_t camera_capture();
+    void takePictureWithFlash(int flashdauer);   
+
+public:
+    ClassFlowMakeImage();
+    ClassFlowMakeImage(std::vector<ClassFlow*>* lfc);
+    bool ReadParameter(FILE* pfile, string& aktparamgraph);
+    bool doFlow(string time);
+    time_t getTimeImageTaken();
+    string name(){return "ClassFlowMakeImage";};
+};
+

+ 302 - 0
code/lib/jomjol_flowcontroll/ClassFlowPostProcessing.cpp

@@ -0,0 +1,302 @@
+#include "ClassFlowPostProcessing.h"
+
+#include "Helper.h"
+#include "ClassFlowAnalog.h"
+#include "ClassFlowDigit.h"
+#include "ClassFlowMakeImage.h"
+
+#include <time.h>
+
+string ClassFlowPostProcessing::GetPreValue()
+{
+    return to_string(PreValue);
+}
+
+bool ClassFlowPostProcessing::LoadPreValue(void)
+{
+    FILE* pFile;
+    char zw[1024];
+    string zwtime, zwvalue;
+
+    pFile = fopen(FilePreValue.c_str(), "r");
+    if (pFile == NULL)
+        return false;
+
+    fgets(zw, 1024, pFile);
+    printf("%s", zw);
+    zwtime = trim(std::string(zw));
+
+    fgets(zw, 1024, pFile);
+    printf("%s", zw);
+    zwvalue = trim(std::string(zw));
+    PreValue = stof(zwvalue.c_str());
+
+    time_t tStart;
+    int yy, month, dd, hh, mm, ss;
+    struct tm whenStart;
+
+    sscanf(zwtime.c_str(), "%d-%d-%d_%d-%d-%d", &yy, &month, &dd, &hh, &mm, &ss);
+    whenStart.tm_year = yy - 1900;
+    whenStart.tm_mon = month - 1;
+    whenStart.tm_mday = dd;
+    whenStart.tm_hour = hh;
+    whenStart.tm_min = mm;
+    whenStart.tm_sec = ss;
+    whenStart.tm_isdst = -1;
+
+    tStart = mktime(&whenStart);
+
+    time_t now;
+    time(&now);
+    localtime(&now);
+    double difference = difftime(now, tStart);
+    difference /= 60;
+    if (difference > PreValueAgeStartup)
+        return false;
+    return true;
+}
+
+void ClassFlowPostProcessing::SavePreValue(float value, string zwtime)
+{
+    FILE* pFile;
+    PreValue = value;
+
+    pFile = fopen(FilePreValue.c_str(), "w");
+
+    if (strlen(zwtime.c_str()) == 0)
+    {
+        time_t rawtime;
+        struct tm* timeinfo;
+        char buffer[80];
+
+        time(&rawtime);
+        timeinfo = localtime(&rawtime);
+
+        strftime(buffer, 80, "%Y-%m-%d_%H-%M-%S", timeinfo);
+
+        zwtime = std::string(buffer);
+    }
+
+fputs(zwtime.c_str(), pFile);
+fputs("\n", pFile);
+
+fputs(to_string(value).c_str(), pFile);
+fputs("\n", pFile);
+
+fclose(pFile);
+}
+
+
+
+ClassFlowPostProcessing::ClassFlowPostProcessing()
+{
+    PreValueUse = false;
+    PreValueAgeStartup = 30;
+    AllowNegativeRates = false;
+    MaxRateValue = 0.1;
+    ErrorMessage = false;
+    ListFlowControll = NULL;
+    PreValueOkay = false;
+    useMaxRateValue = false;
+    FilePreValue = FormatFileName("/sdcard/config/prevalue.ini");
+}
+
+ClassFlowPostProcessing::ClassFlowPostProcessing(std::vector<ClassFlow*>* lfc)
+{
+    PreValueUse = false;
+    PreValueAgeStartup = 30;
+    AllowNegativeRates = false;
+    MaxRateValue = 0.1;
+    ErrorMessage = false;
+    ListFlowControll = NULL;
+    PreValueOkay = false;
+    useMaxRateValue = false;
+    FilePreValue = FormatFileName("/sdcard/config/prevalue.ini");
+    ListFlowControll = lfc;
+}
+
+bool ClassFlowPostProcessing::ReadParameter(FILE* pfile, string& aktparamgraph)
+{
+    std::vector<string> zerlegt;
+
+    aktparamgraph = trim(aktparamgraph);
+
+    if (aktparamgraph.size() == 0)
+        if (!this->GetNextParagraph(pfile, aktparamgraph))
+            return false;
+
+
+    if (aktparamgraph.compare("[PostProcessing]") != 0)       // Paragraph passt nich zu MakeImage
+        return false;
+
+    while (this->getNextLine(pfile, &aktparamgraph) && !this->isNewParagraph(aktparamgraph))
+    {
+        zerlegt = this->ZerlegeZeile(aktparamgraph);
+        if ((zerlegt[0] == "PreValueUse") && (zerlegt.size() > 1))
+        {
+            if ((zerlegt[1] == "True") || (zerlegt[1] == "true"))
+            {
+                PreValueUse = true;
+                PreValueOkay = LoadPreValue();
+            }
+        }
+        if ((zerlegt[0] == "AllowNegativeRates") && (zerlegt.size() > 1))
+        {
+            if ((zerlegt[1] == "True") || (zerlegt[1] == "true"))
+                AllowNegativeRates = true;
+        }
+        if ((zerlegt[0] == "ErrorMessage") && (zerlegt.size() > 1))
+        {
+            if ((zerlegt[1] == "True") || (zerlegt[1] == "true"))
+                ErrorMessage = true;
+        }
+        if ((zerlegt[0] == "PreValueAgeStartup") && (zerlegt.size() > 1))
+        {
+            PreValueAgeStartup = std::stoi(zerlegt[1]);
+        }
+        if ((zerlegt[0] == "MaxRateValue") && (zerlegt.size() > 1))
+        {
+            useMaxRateValue = true;
+            MaxRateValue = std::stof(zerlegt[1]);
+        }
+    }
+    return true;
+}
+
+bool ClassFlowPostProcessing::doFlow(string zwtime)
+{
+    string result = "";
+    string digit = "";
+    string analog = "";
+    bool isdigit = false;
+    bool isanalog = false;
+    string zw;
+    string error = "";
+    time_t imagetime = 0;
+
+    for (int i = 0; i < ListFlowControll->size(); ++i)
+    {
+        if (((*ListFlowControll)[i])->name().compare("ClassFlowMakeImage") == 0)
+        {
+            imagetime = ((ClassFlowMakeImage*)(*ListFlowControll)[i])->getTimeImageTaken();
+        }
+        if (((*ListFlowControll)[i])->name().compare("ClassFlowDigit") == 0)
+        {
+            isdigit = true;
+            digit = (*ListFlowControll)[i]->getReadout();
+        }
+        if (((*ListFlowControll)[i])->name().compare("ClassFlowAnalog") == 0)
+        {
+            isanalog = true;
+            analog = (*ListFlowControll)[i]->getReadout();
+        }
+    }
+
+    if (imagetime == 0)
+        time(&imagetime);
+
+    struct tm* timeinfo;
+    timeinfo = localtime(&imagetime);
+
+    char strftime_buf[64];
+    strftime(strftime_buf, sizeof(strftime_buf), "%Y-%m-%d_%H-%M-%S", timeinfo);
+    zwtime = std::string(strftime_buf);
+
+
+    //    // TESTING ONLY////////////////////
+    //    isdigit = true; digit = "12N";
+    //    isanalog = true; analog = "456";
+
+
+    if (!PreValueUse || !PreValueOkay)
+    {
+        if (isdigit)
+            ReturnValue = digit;
+        if (isdigit && isanalog)
+            ReturnValue = ReturnValue + ".";
+        if (isanalog)
+            ReturnValue = ReturnValue + analog;
+
+        if ((findDelimiterPos(ReturnValue, "N") == std::string::npos) && (ReturnValue.length() > 0))
+        {
+            while ((ReturnValue.length() > 1) && (ReturnValue[0] == '0'))
+            {
+                ReturnValue.erase(0, 1);
+            }
+            ReturnRawValue = ReturnValue;
+            Value = std::stof(ReturnValue);
+            SavePreValue(Value, zwtime);
+        }
+
+        return true;
+    }
+
+    if (isdigit)
+    {
+        digit = ErsetzteN(digit);
+        zw = zw + digit;
+    }
+    if (isdigit && isanalog)
+        zw = zw + ".";
+    if (isanalog)
+        zw = zw + analog;
+
+    ReturnRawValue = zw;
+
+    Value = std::stof(zw);
+
+    if ((!AllowNegativeRates) && (Value < PreValue))
+    {
+        error = "Negative Rate - Return old value - " + std::to_string(Value);
+        Value = PreValue;
+    }
+
+    if (useMaxRateValue && ((Value - PreValue) > MaxRateValue))
+    {
+        error = "Negative Rate - Return old value - " + std::to_string(Value);
+        Value = PreValue;
+    }
+
+    ReturnValue = std::to_string(Value);
+    if (ErrorMessage && (error.length() > 0))
+        ReturnValue = ReturnValue + "\t" + error;
+
+    if (error.length() == 0)
+        SavePreValue(Value, zwtime);
+
+    return true;
+}
+
+string ClassFlowPostProcessing::getReadout()
+{
+    return ReturnValue;
+}
+
+string ClassFlowPostProcessing::getReadoutParam(bool _rawValue)
+{
+    if (_rawValue)
+        return ReturnRawValue;
+    return ReturnValue;
+}
+
+
+string ClassFlowPostProcessing::ErsetzteN(string input)
+{
+    int posN, posPunkt;
+    int pot, ziffer;
+    float zw;
+
+    posN = findDelimiterPos(input, "N");
+    posPunkt = input.length();
+
+    while (posN != std::string::npos)
+    {
+        pot = posPunkt - posN - 1;
+        zw = PreValue / pow(10, pot);
+        ziffer = ((int) zw) % 10;
+        input[posN] = ziffer + 48;
+
+        posN = findDelimiterPos(input, "N");
+    }
+    return input;
+}

+ 42 - 0
code/lib/jomjol_flowcontroll/ClassFlowPostProcessing.h

@@ -0,0 +1,42 @@
+#pragma once
+#include "ClassFlow.h"
+
+#include <string>
+
+
+class ClassFlowPostProcessing :
+    public ClassFlow
+{
+protected:
+    bool PreValueUse;
+    int PreValueAgeStartup;
+    bool AllowNegativeRates;
+    float MaxRateValue;
+    bool useMaxRateValue;
+    bool ErrorMessage;
+    bool PreValueOkay;
+
+    string FilePreValue;
+    float PreValue;
+    float Value;
+    string ReturnValue;
+    string ReturnRawValue;
+
+    bool LoadPreValue(void);
+
+
+    string ErsetzteN(string);
+
+public:
+    ClassFlowPostProcessing();
+    ClassFlowPostProcessing(std::vector<ClassFlow*>* lfc);
+    bool ReadParameter(FILE* pfile, string& aktparamgraph);
+    bool doFlow(string time);
+    string getReadout();
+    string getReadoutParam(bool _rawValue);
+    void SavePreValue(float value, string time = "");
+    string GetPreValue();
+
+    string name(){return "ClassFlowPostProcessing";};
+};
+

+ 157 - 0
code/lib/jomjol_flowcontroll/Helper.cpp

@@ -0,0 +1,157 @@
+//#pragma warning(disable : 4996)
+
+#include "Helper.h"
+
+//#define ISWINDOWS_TRUE
+
+using namespace std;
+
+std::string FormatFileName(std::string input)
+{
+#ifdef ISWINDOWS_TRUE
+    input.erase(0, 1);
+    std::string os = "/";
+    std::string ns = "\\";
+    FindReplace(input, os, ns);
+#endif
+    return input;
+}
+
+
+
+
+void FindReplace(std::string& line, std::string& oldString, std::string& newString) {
+    const size_t oldSize = oldString.length();
+
+    // do nothing if line is shorter than the string to find
+    if (oldSize > line.length()) return;
+
+    const size_t newSize = newString.length();
+    for (size_t pos = 0; ; pos += newSize) {
+        // Locate the substring to replace
+        pos = line.find(oldString, pos);
+        if (pos == std::string::npos) return;
+        if (oldSize == newSize) {
+            // if they're same size, use std::string::replace
+            line.replace(pos, oldSize, newString);
+        }
+        else {
+            // if not same size, replace by erasing and inserting
+            line.erase(pos, oldSize);
+            line.insert(pos, newString);
+        }
+    }
+}
+
+
+
+
+bool ctype_space(const char c, string adddelimiter)
+{
+	if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 11)
+	{
+		return true;
+	}
+	if (adddelimiter.find(c) != string::npos)
+		return true;
+
+	return false;
+}
+
+string trim(string istring, string adddelimiter)
+{
+	bool trimmed = false;
+
+	if (ctype_space(istring[istring.length() - 1], adddelimiter))
+	{
+		istring.erase(istring.length() - 1);
+		trimmed = true;
+	}
+
+	if (ctype_space(istring[0], adddelimiter))
+	{
+		istring.erase(0, 1);
+		trimmed = true;
+	}
+
+	if ((trimmed == false) || (istring.size() == 0))
+	{
+		return istring;
+	}
+	else
+	{
+		return trim(istring, adddelimiter);
+	}
+}
+
+size_t findDelimiterPos(string input, string delimiter)
+{
+	size_t pos = std::string::npos;
+	size_t zw;
+	string akt_del;
+
+	for (int anz = 0; anz < delimiter.length(); ++anz)
+	{
+		akt_del = delimiter[anz];
+		if ((zw = input.find(akt_del)) != std::string::npos)
+		{
+			if (pos != std::string::npos)
+			{
+				if (zw < pos)
+					pos = zw;
+			}
+			else
+				pos = zw;
+		}
+	}
+	return pos;
+}
+
+
+void CopyFile(string input, string output)
+{
+	input = FormatFileName(input);
+	output = FormatFileName(output);
+
+	char cTemp;
+	FILE* fpSourceFile = fopen(input.c_str(), "rb");
+	FILE* fpTargetFile = fopen(output.c_str(), "wb");
+
+	// Code Section
+
+	// Read From The Source File - "Copy"
+	while (fread(&cTemp, 1, 1, fpSourceFile) == 1)
+	{
+		// Write To The Target File - "Paste"
+		fwrite(&cTemp, 1, 1, fpTargetFile);
+	}
+
+	// Close The Files
+	fclose(fpSourceFile);
+	fclose(fpTargetFile);
+}
+
+
+string getFileType(string filename)
+{
+	int lastpos = filename.find(".", 0);
+	int neu_pos;
+	while ((neu_pos = filename.find(".", lastpos + 1)) > -1)
+	{
+		lastpos = neu_pos;
+	}
+
+	string zw = filename.substr(lastpos + 1, filename.size() - lastpos);
+
+	return zw;
+}
+
+
+string toUpper(string in)
+{
+	for (int i = 0; i < in.length(); ++i)
+		in[i] = toupper(in[i]);
+	
+	return in;
+}
+

+ 22 - 0
code/lib/jomjol_flowcontroll/Helper.h

@@ -0,0 +1,22 @@
+#pragma once
+#include <string>
+#include <fstream>
+
+
+using namespace std;
+
+std::string FormatFileName(std::string input);
+void FindReplace(std::string& line, std::string& oldString, std::string& newString);
+
+void CopyFile(string input, string output);
+
+size_t findDelimiterPos(string input, string delimiter);
+//string trim(string istring);
+string trim(string istring, string adddelimiter = "");
+bool ctype_space(const char c, string adddelimiter);
+
+string getFileType(string filename);
+
+string toUpper(string in);
+
+

+ 101 - 0
code/lib/jomjol_flowcontroll/camera_define.h

@@ -0,0 +1,101 @@
+#ifndef CAMERADEFINED
+#define CAMERADEFINED
+
+
+#if defined(CAMERA_MODEL_WROVER_KIT)
+#define PWDN_GPIO_NUM    -1
+#define RESET_GPIO_NUM   -1
+#define XCLK_GPIO_NUM    21
+#define SIOD_GPIO_NUM    26
+#define SIOC_GPIO_NUM    27
+
+#define Y9_GPIO_NUM      35
+#define Y8_GPIO_NUM      34
+#define Y7_GPIO_NUM      39
+#define Y6_GPIO_NUM      36
+#define Y5_GPIO_NUM      19
+#define Y4_GPIO_NUM      18
+#define Y3_GPIO_NUM       5
+#define Y2_GPIO_NUM       4
+#define VSYNC_GPIO_NUM   25
+#define HREF_GPIO_NUM    23
+#define PCLK_GPIO_NUM    22
+
+#elif defined(CAMERA_MODEL_M5STACK_PSRAM)
+#define PWDN_GPIO_NUM     -1
+#define RESET_GPIO_NUM    15
+#define XCLK_GPIO_NUM     27
+#define SIOD_GPIO_NUM     25
+#define SIOC_GPIO_NUM     23
+
+#define Y9_GPIO_NUM       19
+#define Y8_GPIO_NUM       36
+#define Y7_GPIO_NUM       18
+#define Y6_GPIO_NUM       39
+#define Y5_GPIO_NUM        5
+#define Y4_GPIO_NUM       34
+#define Y3_GPIO_NUM       35
+#define Y2_GPIO_NUM       32
+#define VSYNC_GPIO_NUM    22
+#define HREF_GPIO_NUM     26
+#define PCLK_GPIO_NUM     21
+
+#elif defined(CAMERA_MODEL_AI_THINKER)
+#define PWDN_GPIO_NUM     GPIO_NUM_32
+#define RESET_GPIO_NUM    -1
+#define XCLK_GPIO_NUM      GPIO_NUM_0
+#define SIOD_GPIO_NUM     GPIO_NUM_26
+#define SIOC_GPIO_NUM     GPIO_NUM_27
+
+#define Y9_GPIO_NUM       GPIO_NUM_35
+#define Y8_GPIO_NUM       GPIO_NUM_34
+#define Y7_GPIO_NUM       GPIO_NUM_39
+#define Y6_GPIO_NUM       GPIO_NUM_36
+#define Y5_GPIO_NUM       GPIO_NUM_21
+#define Y4_GPIO_NUM       GPIO_NUM_19
+#define Y3_GPIO_NUM       GPIO_NUM_18
+#define Y2_GPIO_NUM        GPIO_NUM_5
+#define VSYNC_GPIO_NUM    GPIO_NUM_25
+#define HREF_GPIO_NUM     GPIO_NUM_23
+#define PCLK_GPIO_NUM     GPIO_NUM_22
+
+#else
+#error "Camera model not selected"
+#endif
+
+
+
+static camera_config_t camera_config = {
+    .pin_pwdn  = PWDN_GPIO_NUM,
+    .pin_reset = RESET_GPIO_NUM,
+    .pin_xclk = XCLK_GPIO_NUM,
+    .pin_sscb_sda = SIOD_GPIO_NUM,
+    .pin_sscb_scl = SIOC_GPIO_NUM,
+
+    .pin_d7 = Y9_GPIO_NUM,
+    .pin_d6 = Y8_GPIO_NUM,
+    .pin_d5 = Y7_GPIO_NUM,
+    .pin_d4 = Y6_GPIO_NUM,
+    .pin_d3 = Y5_GPIO_NUM,
+    .pin_d2 = Y4_GPIO_NUM,
+    .pin_d1 = Y3_GPIO_NUM,
+    .pin_d0 = Y2_GPIO_NUM,
+    .pin_vsync = VSYNC_GPIO_NUM,
+    .pin_href = HREF_GPIO_NUM,
+    .pin_pclk = PCLK_GPIO_NUM,
+
+    //XCLK 20MHz or 10MHz for OV2640 double FPS (Experimental)
+    .xclk_freq_hz = 20000000,
+    .ledc_timer = LEDC_TIMER_0,
+    .ledc_channel = LEDC_CHANNEL_0,
+
+    .pixel_format = PIXFORMAT_JPEG,//YUV422,GRAYSCALE,RGB565,JPEG
+//    .pixel_format = PIXFORMAT_RGB888,//YUV422,GRAYSCALE,RGB565,JPEG
+//    .frame_size = FRAMESIZE_QVGA,//QQVGA-QXGA Do not use sizes above QVGA when not JPEG
+    .frame_size = FRAMESIZE_SVGA,//QQVGA-QXGA Do not use sizes above QVGA when not JPEG
+
+    .jpeg_quality = 12, //0-63 lower number means higher quality
+    .fb_count = 1 //if more than one, i2s runs in continuous mode. Use only with JPEG
+};
+
+#endif

+ 87 - 0
code/lib/jomjol_image_proc/CFindTemplate._h_

@@ -0,0 +1,87 @@
+#pragma once
+
+
+#ifndef __CFINDTEMPLATE
+#define __CFINGTEMPLATE
+
+#include <stdint.h>
+#include <string>
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+#include <esp_heap_caps.h>
+
+#include "stb_image.h"
+#include "stb_image_write.h"
+#include "stb_image_resize.h"
+
+
+
+class CImageBasis
+{
+    protected:
+        uint8_t* rgb_image;
+        int channels;
+        int width, height, bpp; 
+        bool externalImage;
+        std::string filename;
+
+        void memCopy(uint8_t* _source, uint8_t* _target, int _size);
+    public:
+        int getWidth(){return this->width;};   
+        int getHeight(){return this->width;};   
+        int getChannels(){return this->channels;};   
+
+        CImageBasis();
+        CImageBasis(std::string _image);
+        CImageBasis(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp);
+        uint8_t GetPixelColor(int x, int y, int ch);
+
+        ~CImageBasis();
+
+        void SaveToFile(std::string _imageout);
+};
+
+class CFindTemplate : public CImageBasis
+{
+    public:
+        CFindTemplate(std::string _image);
+
+        void FindTemplate(std::string _template, int* found_x, int* found_y, std::string _imageout);
+        void FindTemplate(std::string _template, int* found_x, int* found_y, int _dx, int _dy, std::string _imageout);
+        void FindTemplate(std::string _template, int* found_x, int* found_y);
+        void FindTemplate(std::string _template, int* found_x, int* found_y, int _dx, int _dy);
+};
+
+class CRotate: public CImageBasis
+{
+    public:
+        CRotate(std::string _image) : CImageBasis(_image) {};
+        CRotate(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp) : CImageBasis(_rgb_image, _channels, _width, _height, _bpp) {};
+
+        void Rotate(float _angle);
+        void Rotate(float _angle, int _centerx, int _centery);
+        void Translate(int _dx, int _dy);
+};
+
+
+class CAlignAndCutImage : public CImageBasis
+{
+    public:
+        CAlignAndCutImage(std::string _image) : CImageBasis(_image) {};
+
+        void Align(std::string _template0, int ref0_x, int ref0_y, std::string _template1, int ref1_x, int ref1_y, int deltax, int deltay);
+        void CutAndSave(std::string _template1, int x1, int y1, int dx, int dy);
+};
+
+
+class CResizeImage : public CImageBasis
+{
+public:
+    CResizeImage(std::string _image) : CImageBasis(_image) {};
+    void Resize(int _new_dx, int _new_dy);
+};
+
+#endif
+

+ 366 - 0
code/lib/jomjol_image_proc/CFindTemplate.cp__p

@@ -0,0 +1,366 @@
+#include "CFindTemplate.h"
+#include "Helper.h"
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+#include <algorithm>
+
+#define _ESP32_PSRAM
+
+using namespace std;
+
+#define GET_MEMORY malloc
+
+
+uint8_t CImageBasis::GetPixelColor(int x, int y, int ch)
+{
+    stbi_uc* p_source;
+    p_source = this->rgb_image + (this->channels * (y * this->width + x));
+    return p_source[channels];
+}
+
+void CResizeImage::Resize(int _new_dx, int _new_dy)
+{
+    int memsize = _new_dx * _new_dy * this->channels;
+    uint8_t* odata = (unsigned char*)GET_MEMORY(memsize);
+
+    stbir_resize_uint8(this->rgb_image, this->width, this->height, 0, odata, _new_dx, _new_dy, 0, this->channels);
+
+    stbi_image_free(this->rgb_image);
+    this->rgb_image = (unsigned char*)GET_MEMORY(memsize);
+
+    this->memCopy(odata, this->rgb_image, memsize);
+    this->width = _new_dx;
+    this->height = _new_dy;
+    stbi_image_free(odata);
+}
+
+void CRotate::Rotate(float _angle, int _centerx, int _centery)
+{
+    float m[2][3];
+
+    float x_center = _centerx;
+    float y_center = _centery;
+    _angle = _angle / 180 * M_PI;
+
+    m[0][0] = cos(_angle);
+    m[0][1] = sin(_angle);
+    m[0][2] = (1 - m[0][0]) * x_center - m[0][1] * y_center;
+
+    m[1][0] = -m[0][1];
+    m[1][1] = m[0][0];
+    m[1][2] = m[0][1] * x_center + (1 - m[0][0]) * y_center;
+
+    int memsize = this->width * this->height * this->channels;
+    uint8_t* odata = (unsigned char*)GET_MEMORY(memsize);
+
+    int x_source, y_source;
+    stbi_uc* p_target;
+    stbi_uc* p_source;
+
+    for (int x = 0; x < this->width; ++x)
+        for (int y = 0; y < this->height; ++y)
+        {
+            p_target = odata + (this->channels * (y * this->width + x));
+
+            x_source = int(m[0][0] * x + m[0][1] * y);
+            y_source = int(m[1][0] * x + m[1][1] * y);
+
+            x_source += int(m[0][2]);
+            y_source += int(m[1][2]);
+
+            if ((x_source >= 0) && (x_source < this->width) && (y_source >= 0) && (y_source < this->height))
+            {
+                p_source = this->rgb_image + (this->channels * (y_source * this->width + x_source));
+                for (int channels = 0; channels < this->channels; ++channels)
+                    p_target[channels] = p_source[channels];
+            }
+            else
+            {
+                for (int channels = 0; channels < this->channels; ++channels)
+                    p_target[channels] = 255;
+            }
+        }
+
+    //    memcpy(this->rgb_image, odata, memsize);
+    this->memCopy(odata, this->rgb_image, memsize);
+    stbi_image_free(odata);
+}
+
+void CRotate::Rotate(float _angle)
+{
+    this->Rotate(_angle, this->width / 2, this->height / 2);
+}
+
+void CRotate::Translate(int _dx, int _dy)
+{
+    int memsize = this->width * this->height * this->channels;
+    uint8_t* odata = (unsigned char*)GET_MEMORY(memsize);
+
+
+    int x_source, y_source;
+    stbi_uc* p_target;
+    stbi_uc* p_source;
+
+    for (int x = 0; x < this->width; ++x)
+        for (int y = 0; y < this->height; ++y)
+        {
+            p_target = odata + (this->channels * (y * this->width + x));
+
+            x_source = x - _dx;
+            y_source = y - _dy;
+
+            if ((x_source >= 0) && (x_source < this->width) && (y_source >= 0) && (y_source < this->height))
+            {
+                p_source = this->rgb_image + (this->channels * (y_source * this->width + x_source));
+                for (int channels = 0; channels < this->channels; ++channels)
+                    p_target[channels] = p_source[channels];
+            }
+            else
+            {
+                for (int channels = 0; channels < this->channels; ++channels)
+                    p_target[channels] = 255;
+            }
+        }
+
+    //    memcpy(this->rgb_image, odata, memsize);
+    this->memCopy(odata, this->rgb_image, memsize);
+    stbi_image_free(odata);
+}
+
+
+
+CFindTemplate::CFindTemplate(std::string _image)
+{
+    this->channels = 1;
+    this->rgb_image = stbi_load(_image.c_str(), &(this->width), &(this->height), &(this->bpp), this->channels);
+}
+
+void CFindTemplate::FindTemplate(std::string _template, int* found_x, int* found_y)
+{
+    this->FindTemplate(_template, found_x, found_y, 0, 0);
+}
+
+void CFindTemplate::FindTemplate(std::string _template, int* found_x, int* found_y, int _dx, int _dy)
+{
+    int tpl_width, tpl_height, tpl_bpp;
+    uint8_t* rgb_template = stbi_load(_template.c_str(), &tpl_width, &tpl_height, &tpl_bpp, this->channels);
+
+    int ow, ow_start, ow_stop;
+    int oh, oh_start, oh_stop;
+
+    if (_dx == 0)
+    {
+        _dx = this->width;
+        *found_x = 0;
+    }
+
+    if (_dy == 0)
+    {
+        _dy = this->height;
+        *found_y = 0;
+    }
+
+
+    ow_start = *found_x - _dx;
+    ow_start = std::max(ow_start, 0);
+    ow_stop = *found_x + _dx;
+    if ((ow_stop + tpl_width) > this->width)
+        ow_stop = this->width - tpl_width;
+    ow = ow_stop - ow_start + 1;
+
+    oh_start = *found_y - _dy;
+    oh_start = std::max(oh_start, 0);
+    oh_stop = *found_y + _dy;
+    if ((oh_stop + tpl_height) > this->height)
+        oh_stop = this->height - tpl_height;
+    oh = oh_stop - oh_start + 1;
+
+    uint8_t* odata = (unsigned char*)GET_MEMORY(ow * oh * this->channels);
+
+    double aktSAD;
+    double minSAD = pow(tpl_width * tpl_height * 255, 2);
+
+    for (int xouter = ow_start; xouter <= ow_stop; xouter++)
+        for (int youter = oh_start; youter <= oh_stop; ++youter)
+        {
+            aktSAD = 0;
+            for (int tpl_x = 0; tpl_x < tpl_width; tpl_x++)
+                for (int tpl_y = 0; tpl_y < tpl_height; tpl_y++)
+                {
+                    stbi_uc* p_org = this->rgb_image + (this->channels * ((youter + tpl_y) * this->width + (xouter + tpl_x)));
+                    stbi_uc* p_tpl = rgb_template + (this->channels * (tpl_y * tpl_width + tpl_x));
+                    aktSAD += pow(p_tpl[0] - p_org[0], 2);
+                }
+            stbi_uc* p_out = odata + (this->channels * ((youter - oh_start) * ow + (xouter - ow_start)));
+
+            p_out[0] = int(sqrt(aktSAD / (tpl_width * tpl_height)));
+            if (aktSAD < minSAD)
+            {
+                minSAD = aktSAD;
+                *found_x = xouter;
+                *found_y = youter;
+            }
+        }
+
+    stbi_image_free(odata);
+    stbi_image_free(rgb_template);
+}
+
+void CFindTemplate::FindTemplate(std::string _template, int* found_x, int* found_y, std::string _imageout)
+{
+    this->FindTemplate(_template, found_x, found_y);
+    this->SaveToFile(_imageout);
+}
+
+void CFindTemplate::FindTemplate(std::string _template, int* found_x, int* found_y, int _dx, int _dy, std::string _imageout)
+{
+    this->FindTemplate(_template, found_x, found_y, _dx, _dy);
+    this->SaveToFile(_imageout);
+}
+
+
+
+void CImageBasis::memCopy(uint8_t* _source, uint8_t* _target, int _size)
+{
+#ifdef _ESP32_PSRAM
+    for (int i = 0; i < _size; ++i)
+        *(_target + i) = *(_source + i);
+#else
+    memcpy(_target, _source, _size);
+#endif
+}
+
+CImageBasis::CImageBasis()
+{
+    this->externalImage = false;
+}
+
+CImageBasis::CImageBasis(std::string _image)
+{
+//    printf("Start CImageBasis\n");
+    channels = 3;
+    externalImage = false;
+    filename = _image;
+//    printf("CImageBasis before load\n");
+//    printf(_image.c_str()); printf("\n");
+    rgb_image = stbi_load(_image.c_str(), &width, &height, &bpp, channels);
+    if (!rgb_image)
+    {
+        printf("Datei konnte nicht geoeffnet werden\n");
+        return;
+    }
+//    printf("CImageBasis after load\n");
+//    printf("w %d, h %d, b %d, c %d\n", width, height, bpp, channels);
+}
+
+CImageBasis::CImageBasis(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp)
+{
+    rgb_image = _rgb_image;
+    channels = _channels;
+    width = _width;
+    height = _height;
+    bpp = _bpp;
+    externalImage = true;
+}
+
+CImageBasis::~CImageBasis()
+{
+    if (!externalImage)
+        stbi_image_free(rgb_image);
+}
+
+void CImageBasis::SaveToFile(std::string _imageout)
+{
+    string typ = getFileType(_imageout);
+
+    if ((typ == "jpg") || (typ == "JPG"))       // ACHTUNG PROBLEMATISCH IM ESP32
+    {
+        stbi_write_jpg(_imageout.c_str(), this->width, this->height, this->channels, this->rgb_image, 0);
+    }
+
+    if ((typ == "bmp") || (typ == "BMP"))
+    {
+        stbi_write_bmp(_imageout.c_str(), this->width, this->height, this->channels, this->rgb_image);
+    }
+    //    stbi_write_jpg(_imageout.c_str(), this->width, this->height, this->channels, this->rgb_image, 0);
+    //      stbi_write_bmp(_imageout.c_str(), this->width, this->height, this->channels, this->rgb_image);
+}
+
+
+
+void CAlignAndCutImage::Align(std::string _template0, int ref0_x, int ref0_y, std::string _template1, int ref1_x, int ref1_y, int deltax = 40, int deltay = 40)
+{
+    int dx, dy;
+    int r0_x, r0_y, r1_x, r1_y;
+
+    CFindTemplate* ft = new CFindTemplate(this->filename);
+
+    r0_x = ref0_x;
+    r0_y = ref0_y;
+    ft->FindTemplate(_template0, &r0_x, &r0_y, deltax, deltay);
+
+    r1_x = ref1_x;
+    r1_y = ref1_y;
+    ft->FindTemplate(_template1, &r1_x, &r1_y, deltax, deltay);
+
+    delete ft;
+
+
+    dx = ref0_x - r0_x;
+    dy = ref0_y - r0_y;
+
+    r0_x += dx;
+    r0_y += dy;
+
+    r1_x += dx;
+    r1_y += dy;
+
+    float w_org, w_ist, d_winkel;
+
+    w_org = atan2(ref1_y - ref0_y, ref1_x - ref0_x);
+    w_ist = atan2(r1_y - r0_y, r1_x - r0_x);
+
+    d_winkel = -(w_org - w_ist) * 180 / M_PI;
+
+    CRotate rt(this->rgb_image, this->channels, this->width, this->height, this->bpp);
+    rt.Translate(dx, dy);
+    rt.Rotate(d_winkel, ref0_x, ref0_y);
+    printf("Alignment: dx %d - dy %d - rot %f\n", dx, dy, d_winkel);
+}
+
+void CAlignAndCutImage::CutAndSave(std::string _template1, int x1, int y1, int dx, int dy)
+{
+
+    int x2, y2;
+
+    x2 = x1 + dx;
+    y2 = y1 + dy;
+    x2 = min(x2, this->width - 1);
+    y2 = min(y2, this->height - 1);
+
+    dx = x2 - x1;
+    dy = y2 - y1;
+
+    int memsize = dx * dy * this->channels;
+    uint8_t* odata = (unsigned char*)GET_MEMORY(memsize);
+
+
+    int x_source, y_source;
+    stbi_uc* p_target;
+    stbi_uc* p_source;
+
+    for (int x = x1; x < x2; ++x)
+        for (int y = y1; y < y2; ++y)
+        {
+            p_target = odata + (this->channels * ((y - y1) * dx + (x - x1)));
+            p_source = this->rgb_image + (this->channels * (y * this->width + x));
+            for (int channels = 0; channels < this->channels; ++channels)
+                p_target[channels] = p_source[channels];
+        }
+
+    //    stbi_write_jpg(_template1.c_str(), dx, dy, this->channels, odata, 0);
+    stbi_write_bmp(_template1.c_str(), dx, dy, this->channels, odata);
+
+    stbi_image_free(odata);
+}

+ 496 - 0
code/lib/jomjol_image_proc/CFindTemplate.cpp

@@ -0,0 +1,496 @@
+#include "CFindTemplate.h"
+#include "Helper.h"
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+#include <algorithm>
+
+#define _ESP32_PSRAM
+
+using namespace std;
+
+#define GET_MEMORY malloc
+
+/*
+CResizeImage::CResizeImage(std::string _image, int _new_dx, int _new_dy)
+{
+    CImageBasis::CImageBasis(_image);
+}
+*/
+
+uint8_t CImageBasis::GetPixelColor(int x, int y, int ch)
+{
+    stbi_uc* p_source;
+    p_source = this->rgb_image + (this->channels * (y * this->width + x));
+    return p_source[ch];
+}
+
+void CResizeImage::Resize(int _new_dx, int _new_dy)
+{
+    int memsize = _new_dx * _new_dy * this->channels;
+    uint8_t* odata = (unsigned char*)GET_MEMORY(memsize);
+
+    stbir_resize_uint8(this->rgb_image, this->width, this->height, 0, odata, _new_dx, _new_dy, 0, this->channels);
+
+    stbi_image_free(this->rgb_image);
+    this->rgb_image = (unsigned char*)GET_MEMORY(memsize);
+
+    this->memCopy(odata, this->rgb_image, memsize);
+    this->width = _new_dx;
+    this->height = _new_dy;
+    stbi_image_free(odata);
+}
+
+void CRotate::Rotate(float _angle, int _centerx, int _centery)
+{
+    float m[2][3];
+
+    float x_center = _centerx;
+    float y_center = _centery;
+    _angle = _angle / 180 * M_PI;
+
+    m[0][0] = cos(_angle);
+    m[0][1] = sin(_angle);
+    m[0][2] = (1 - m[0][0]) * x_center - m[0][1] * y_center;
+
+    m[1][0] = -m[0][1];
+    m[1][1] = m[0][0];
+    m[1][2] = m[0][1] * x_center + (1 - m[0][0]) * y_center;
+
+    int memsize = this->width * this->height * this->channels;
+    uint8_t* odata = (unsigned char*)GET_MEMORY(memsize);
+
+    int x_source, y_source;
+    stbi_uc* p_target;
+    stbi_uc* p_source;
+
+    for (int x = 0; x < this->width; ++x)
+        for (int y = 0; y < this->height; ++y)
+        {
+            p_target = odata + (this->channels * (y * this->width + x));
+
+            x_source = int(m[0][0] * x + m[0][1] * y);
+            y_source = int(m[1][0] * x + m[1][1] * y);
+
+            x_source += int(m[0][2]);
+            y_source += int(m[1][2]);
+
+            if ((x_source >= 0) && (x_source < this->width) && (y_source >= 0) && (y_source < this->height))
+            {
+                p_source = this->rgb_image + (this->channels * (y_source * this->width + x_source));
+                for (int channels = 0; channels < this->channels; ++channels)
+                    p_target[channels] = p_source[channels];
+            }
+            else
+            {
+                for (int channels = 0; channels < this->channels; ++channels)
+                    p_target[channels] = 255;
+            }
+        }
+
+    //    memcpy(this->rgb_image, odata, memsize);
+    this->memCopy(odata, this->rgb_image, memsize);
+    stbi_image_free(odata);
+}
+
+void CRotate::Rotate(float _angle)
+{
+    this->Rotate(_angle, this->width / 2, this->height / 2);
+}
+
+void CRotate::Translate(int _dx, int _dy)
+{
+    int memsize = this->width * this->height * this->channels;
+    uint8_t* odata = (unsigned char*)GET_MEMORY(memsize);
+
+
+    int x_source, y_source;
+    stbi_uc* p_target;
+    stbi_uc* p_source;
+
+    for (int x = 0; x < this->width; ++x)
+        for (int y = 0; y < this->height; ++y)
+        {
+            p_target = odata + (this->channels * (y * this->width + x));
+
+            x_source = x - _dx;
+            y_source = y - _dy;
+
+            if ((x_source >= 0) && (x_source < this->width) && (y_source >= 0) && (y_source < this->height))
+            {
+                p_source = this->rgb_image + (this->channels * (y_source * this->width + x_source));
+                for (int channels = 0; channels < this->channels; ++channels)
+                    p_target[channels] = p_source[channels];
+            }
+            else
+            {
+                for (int channels = 0; channels < this->channels; ++channels)
+                    p_target[channels] = 255;
+            }
+        }
+
+    //    memcpy(this->rgb_image, odata, memsize);
+    this->memCopy(odata, this->rgb_image, memsize);
+    stbi_image_free(odata);
+}
+
+
+
+CFindTemplate::CFindTemplate(std::string _image)
+{
+    this->channels = 1;
+    this->rgb_image = stbi_load(_image.c_str(), &(this->width), &(this->height), &(this->bpp), this->channels);
+}
+
+void CFindTemplate::FindTemplate(std::string _template, int* found_x, int* found_y)
+{
+    this->FindTemplate(_template, found_x, found_y, 0, 0);
+}
+
+void CFindTemplate::FindTemplate(std::string _template, int* found_x, int* found_y, int _dx, int _dy)
+{
+    uint8_t* rgb_template = stbi_load(_template.c_str(), &tpl_width, &tpl_height, &tpl_bpp, this->channels);
+
+    int ow, ow_start, ow_stop;
+    int oh, oh_start, oh_stop;
+
+    if (_dx == 0)
+    {
+        _dx = this->width;
+        *found_x = 0;
+    }
+
+    if (_dy == 0)
+    {
+        _dy = this->height;
+        *found_y = 0;
+    }
+
+
+    ow_start = *found_x - _dx;
+    ow_start = std::max(ow_start, 0);
+    ow_stop = *found_x + _dx;
+    if ((ow_stop + tpl_width) > this->width)
+        ow_stop = this->width - tpl_width;
+    ow = ow_stop - ow_start + 1;
+
+    oh_start = *found_y - _dy;
+    oh_start = std::max(oh_start, 0);
+    oh_stop = *found_y + _dy;
+    if ((oh_stop + tpl_height) > this->height)
+        oh_stop = this->height - tpl_height;
+    oh = oh_stop - oh_start + 1;
+
+    uint8_t* odata = (unsigned char*)GET_MEMORY(ow * oh * this->channels);
+
+    double aktSAD;
+    double minSAD = pow(tpl_width * tpl_height * 255, 2);
+
+    for (int xouter = ow_start; xouter <= ow_stop; xouter++)
+        for (int youter = oh_start; youter <= oh_stop; ++youter)
+        {
+            aktSAD = 0;
+            for (int tpl_x = 0; tpl_x < tpl_width; tpl_x++)
+                for (int tpl_y = 0; tpl_y < tpl_height; tpl_y++)
+                {
+                    stbi_uc* p_org = this->rgb_image + (this->channels * ((youter + tpl_y) * this->width + (xouter + tpl_x)));
+                    stbi_uc* p_tpl = rgb_template + (this->channels * (tpl_y * tpl_width + tpl_x));
+                    aktSAD += pow(p_tpl[0] - p_org[0], 2);
+                }
+            stbi_uc* p_out = odata + (this->channels * ((youter - oh_start) * ow + (xouter - ow_start)));
+
+            p_out[0] = int(sqrt(aktSAD / (tpl_width * tpl_height)));
+            if (aktSAD < minSAD)
+            {
+                minSAD = aktSAD;
+                *found_x = xouter;
+                *found_y = youter;
+            }
+        }
+
+    stbi_write_bmp("sdcard\\find.bmp", ow, oh, this->channels, odata);
+
+    stbi_image_free(odata);
+    stbi_image_free(rgb_template);
+}
+
+void CFindTemplate::FindTemplate(std::string _template, int* found_x, int* found_y, std::string _imageout)
+{
+    this->FindTemplate(_template, found_x, found_y);
+    this->SaveToFile(_imageout);
+}
+
+void CFindTemplate::FindTemplate(std::string _template, int* found_x, int* found_y, int _dx, int _dy, std::string _imageout)
+{
+    this->FindTemplate(_template, found_x, found_y, _dx, _dy);
+    this->SaveToFile(_imageout);
+}
+
+
+
+void CImageBasis::memCopy(uint8_t* _source, uint8_t* _target, int _size)
+{
+#ifdef _ESP32_PSRAM
+    for (int i = 0; i < _size; ++i)
+        *(_target + i) = *(_source + i);
+#else
+    memcpy(_target, _source, _size);
+#endif
+}
+
+bool CImageBasis::isInImage(int x, int y)
+{
+    if ((x < 0) || (x > this->width - 1))
+        return false;
+
+    if ((y < 0) || (y > this->height- 1))
+        return false;
+
+    return true;
+}
+
+void CImageBasis::setPixelColor(int x, int y, int r, int g, int b)
+{
+    stbi_uc* p_source;
+
+    p_source = this->rgb_image + (this->channels * (y * this->width + x));
+    p_source[0] = r;
+    if (this-> channels > 2)
+    {
+        p_source[1] = g;
+        p_source[2] = b;
+    }
+}
+
+void CImageBasis::drawRect(int x, int y, int dx, int dy, int r, int g, int b, int thickness)
+{
+    int zwx1, zwx2, zwy1, zwy2;
+    int _x, _y, _thick;
+
+    zwx1 = x - thickness + 1;
+    zwx2 = x + dx + thickness - 1;
+    zwy1 = y;
+    zwy2 = y;
+    for (_thick = 0; _thick < thickness; _thick++)
+        for (_x = zwx1; _x <= zwx2; ++_x)
+            for (_y = zwy1; _y <= zwy2; _y++)
+                if (isInImage(_x, _y))
+                    setPixelColor(_x, _y - _thick, r, g, b);
+
+    zwx1 = x - thickness + 1;
+    zwx2 = x + dx + thickness - 1;
+    zwy1 = y + dy;
+    zwy2 = y + dy;
+    for (_thick = 0; _thick < thickness; _thick++)
+        for (_x = zwx1; _x <= zwx2; ++_x)
+            for (_y = zwy1; _y <= zwy2; _y++)
+                if (isInImage(_x, _y))
+                    setPixelColor(_x, _y + _thick, r, g, b);
+
+    zwx1 = x;
+    zwx2 = x;
+    zwy1 = y;
+    zwy2 = y + dy;
+    for (_thick = 0; _thick < thickness; _thick++)
+        for (_x = zwx1; _x <= zwx2; ++_x)
+            for (_y = zwy1; _y <= zwy2; _y++)
+                if (isInImage(_x, _y))
+                    setPixelColor(_x - _thick, _y, r, g, b);
+
+    zwx1 = x + dx;
+    zwx2 = x + dx;
+    zwy1 = y;
+    zwy2 = y + dy;
+    for (_thick = 0; _thick < thickness; _thick++)
+        for (_x = zwx1; _x <= zwx2; ++_x)
+            for (_y = zwy1; _y <= zwy2; _y++)
+                if (isInImage(_x, _y))
+                    setPixelColor(_x + _thick, _y, r, g, b);
+
+}
+
+void CImageBasis::drawLine(int x1, int y1, int x2, int y2, int r, int g, int b, int thickness)
+{
+    int _x, _y, _thick;
+    int _zwy1, _zwy2;
+    thickness = (thickness-1) / 2;
+
+    for (_thick = 0; _thick <= thickness; ++_thick)
+        for (_x = x1 - _thick; _x <= x2 + _thick; ++_x)
+        {
+            if (x2 == x1)
+            {
+                _zwy1 = y1;
+                _zwy2 = y2;
+            }
+            else
+            {
+                _zwy1 = (y2 - y1) * (float)(_x - x1) / (float)(x2 - x1) + y1;
+                _zwy2 = (y2 - y1) * (float)(_x + 1 - x1) / (float)(x2 - x1) + y1;
+            }
+
+            for (_y = _zwy1 - _thick; _y <= _zwy2 + _thick; _y++)
+                if (isInImage(_x, _y))
+                    setPixelColor(_x, _y, r, g, b);
+        }
+}
+
+void CImageBasis::drawCircle(int x1, int y1, int rad, int r, int g, int b, int thickness)
+{
+    float deltarad, aktrad;
+    int _thick, _x, _y;
+
+    deltarad = 1 / (4 * M_PI * (rad + thickness - 1));
+
+    for (aktrad = 0; aktrad <= (2 * M_PI); aktrad += deltarad)
+        for (_thick = 0; _thick < thickness; ++_thick)
+        {
+            _x = sin(aktrad) * (rad + _thick) + x1;
+            _y = cos(aktrad) * (rad + _thick) + y1;
+            if (isInImage(_x, _y))
+                setPixelColor(_x, _y, r, g, b);
+        }
+}
+
+CImageBasis::CImageBasis()
+{
+    this->externalImage = false;
+}
+
+CImageBasis::CImageBasis(std::string _image)
+{
+    //    printf("Start CImageBasis\n");
+    this->channels = 3;
+    this->externalImage = false;
+    this->filename = _image;
+    //    printf("CImageBasis before load\n");
+    //    printf(_image.c_str()); printf("\n");
+    this->rgb_image = stbi_load(_image.c_str(), &(this->width), &(this->height), &(this->bpp), this->channels);
+    //    printf("CImageBasis after load\n");
+    //    printf("w %d, h %d, b %d, c %d", this->width, this->height, this->bpp, this->channels);
+}
+
+CImageBasis::CImageBasis(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp)
+{
+    this->rgb_image = _rgb_image;
+    this->channels = _channels;
+    this->width = _width;
+    this->height = _height;
+    this->bpp = _bpp;
+    this->externalImage = true;
+}
+
+CImageBasis::~CImageBasis()
+{
+    if (!this->externalImage)
+        stbi_image_free(this->rgb_image);
+}
+
+void CImageBasis::SaveToFile(std::string _imageout)
+{
+    string typ = getFileType(_imageout);
+
+    if ((typ == "jpg") || (typ == "JPG"))       // ACHTUNG PROBLEMATISCH IM ESP32
+    {
+        stbi_write_jpg(_imageout.c_str(), this->width, this->height, this->channels, this->rgb_image, 0);
+    }
+
+    if ((typ == "bmp") || (typ == "BMP"))
+    {
+        stbi_write_bmp(_imageout.c_str(), this->width, this->height, this->channels, this->rgb_image);
+    }
+    //    stbi_write_jpg(_imageout.c_str(), this->width, this->height, this->channels, this->rgb_image, 0);
+    //      stbi_write_bmp(_imageout.c_str(), this->width, this->height, this->channels, this->rgb_image);
+}
+
+
+
+void CAlignAndCutImage::Align(std::string _template0, int ref0_x, int ref0_y, std::string _template1, int ref1_x, int ref1_y, int deltax, int deltay, std::string imageROI)
+{
+    int dx, dy;
+    int r0_x, r0_y, r1_x, r1_y;
+
+    CFindTemplate* ft = new CFindTemplate(this->filename);
+
+    r0_x = ref0_x;
+    r0_y = ref0_y;
+    ft->FindTemplate(_template0, &r0_x, &r0_y, deltax, deltay);
+    t0_dx = ft->tpl_width;
+    t0_dy = ft->tpl_height;    
+
+    r1_x = ref1_x;
+    r1_y = ref1_y;
+    ft->FindTemplate(_template1, &r1_x, &r1_y, deltax, deltay);
+    t1_dx = ft->tpl_width;
+    t1_dy = ft->tpl_height;
+
+    delete ft;
+
+
+    dx = ref0_x - r0_x;
+    dy = ref0_y - r0_y;
+
+    r0_x += dx;
+    r0_y += dy;
+
+    r1_x += dx;
+    r1_y += dy;
+
+    float w_org, w_ist, d_winkel;
+
+    w_org = atan2(ref1_y - ref0_y, ref1_x - ref0_x);
+    w_ist = atan2(r1_y - r0_y, r1_x - r0_x);
+
+    d_winkel = (w_org - w_ist) * 180 / M_PI;
+
+    if (imageROI.length() > 0)
+    {
+        CImageBasis* imgzw = new CImageBasis(this->filename);
+        imgzw->drawRect(r0_x, r0_y, t0_dx, t0_dy, 255, 0, 0, 2);
+        imgzw->drawRect(r1_x, r1_y, t1_dx, t1_dy, 255, 0, 0, 2);
+        imgzw->SaveToFile(imageROI);
+        printf("Alignment: alignment ROI created: %s\n", imageROI.c_str());
+        delete imgzw;
+    }
+
+    CRotate rt(this->rgb_image, this->channels, this->width, this->height, this->bpp);
+    rt.Translate(dx, dy);
+    rt.Rotate(d_winkel, ref0_x, ref0_y);
+    printf("Alignment: dx %d - dy %d - rot %f\n", dx, dy, d_winkel);
+}
+
+void CAlignAndCutImage::CutAndSave(std::string _template1, int x1, int y1, int dx, int dy)
+{
+
+    int x2, y2;
+
+    x2 = x1 + dx;
+    y2 = y1 + dy;
+    x2 = min(x2, this->width - 1);
+    y2 = min(y2, this->height - 1);
+
+    dx = x2 - x1;
+    dy = y2 - y1;
+
+    int memsize = dx * dy * this->channels;
+    uint8_t* odata = (unsigned char*)GET_MEMORY(memsize);
+
+
+    int x_source, y_source;
+    stbi_uc* p_target;
+    stbi_uc* p_source;
+
+    for (int x = x1; x < x2; ++x)
+        for (int y = y1; y < y2; ++y)
+        {
+            p_target = odata + (this->channels * ((y - y1) * dx + (x - x1)));
+            p_source = this->rgb_image + (this->channels * (y * this->width + x));
+            for (int channels = 0; channels < this->channels; ++channels)
+                p_target[channels] = p_source[channels];
+        }
+
+    //    stbi_write_jpg(_template1.c_str(), dx, dy, this->channels, odata, 0);
+    stbi_write_bmp(_template1.c_str(), dx, dy, this->channels, odata);
+
+    stbi_image_free(odata);
+}

+ 95 - 0
code/lib/jomjol_image_proc/CFindTemplate.h

@@ -0,0 +1,95 @@
+#pragma once
+
+
+#ifndef __CFINDTEMPLATE
+#define __CFINGTEMPLATE
+
+#include <stdint.h>
+#include <string>
+
+#define _USE_MATH_DEFINES
+#include <math.h>
+
+#include "stb_image.h"
+#include "stb_image_write.h"
+#include "stb_image_resize.h"
+
+
+class CImageBasis
+{
+    protected:
+        uint8_t* rgb_image;
+        int channels;
+        int width, height, bpp; 
+        bool externalImage;
+        std::string filename;
+
+        void memCopy(uint8_t* _source, uint8_t* _target, int _size);
+        bool isInImage(int x, int y);
+
+    public:
+        int getWidth(){return this->width;};   
+        int getHeight(){return this->height;};   
+        int getChannels(){return this->channels;};   
+        void drawRect(int x, int y, int dx, int dy, int r = 255, int g = 255, int b = 255, int thickness = 1);
+        void drawLine(int x1, int y1, int x2, int y2, int r, int g, int b, int thickness = 1);
+        void drawCircle(int x1, int y1, int rad, int r, int g, int b, int thickness = 1);
+        void setPixelColor(int x, int y, int r, int g, int b);
+
+
+        CImageBasis();
+        CImageBasis(std::string _image);
+        CImageBasis(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp);
+        uint8_t GetPixelColor(int x, int y, int ch);
+
+        ~CImageBasis();
+
+        void SaveToFile(std::string _imageout);
+};
+
+class CFindTemplate : public CImageBasis
+{
+    public:
+        int tpl_width, tpl_height, tpl_bpp;    
+        CFindTemplate(std::string _image);
+
+        void FindTemplate(std::string _template, int* found_x, int* found_y, std::string _imageout);
+        void FindTemplate(std::string _template, int* found_x, int* found_y, int _dx, int _dy, std::string _imageout);
+        void FindTemplate(std::string _template, int* found_x, int* found_y);
+        void FindTemplate(std::string _template, int* found_x, int* found_y, int _dx, int _dy);
+};
+
+class CRotate: public CImageBasis
+{
+    public:
+        CRotate(std::string _image) : CImageBasis(_image) {};
+        CRotate(uint8_t* _rgb_image, int _channels, int _width, int _height, int _bpp) : CImageBasis(_rgb_image, _channels, _width, _height, _bpp) {};
+
+        void Rotate(float _angle);
+        void Rotate(float _angle, int _centerx, int _centery);
+        void Translate(int _dx, int _dy);
+};
+
+
+class CAlignAndCutImage : public CImageBasis
+{
+    public:
+        int t0_dx, t0_dy, t1_dx, t1_dy;
+        CAlignAndCutImage(std::string _image) : CImageBasis(_image) {};
+
+        void Align(std::string _template1, int x1, int y1, std::string _template2, int x2, int y2, int deltax = 40, int deltay = 40, std::string imageROI = "");
+        void CutAndSave(std::string _template1, int x1, int y1, int dx, int dy);
+};
+
+
+class CResizeImage : public CImageBasis
+{
+public:
+    CResizeImage(std::string _image) : CImageBasis(_image) {};
+//    CResizeImage(std::string _image, int _new_dx, int _new_dy);
+    void Resize(int _new_dx, int _new_dy);
+};
+
+
+#endif
+

+ 157 - 0
code/lib/jomjol_image_proc/Helper.cpp

@@ -0,0 +1,157 @@
+//#pragma warning(disable : 4996)
+
+#include "Helper.h"
+
+//#define ISWINDOWS_TRUE
+
+using namespace std;
+
+std::string FormatFileName(std::string input)
+{
+#ifdef ISWINDOWS_TRUE
+    input.erase(0, 1);
+    std::string os = "/";
+    std::string ns = "\\";
+    FindReplace(input, os, ns);
+#endif
+    return input;
+}
+
+
+
+
+void FindReplace(std::string& line, std::string& oldString, std::string& newString) {
+    const size_t oldSize = oldString.length();
+
+    // do nothing if line is shorter than the string to find
+    if (oldSize > line.length()) return;
+
+    const size_t newSize = newString.length();
+    for (size_t pos = 0; ; pos += newSize) {
+        // Locate the substring to replace
+        pos = line.find(oldString, pos);
+        if (pos == std::string::npos) return;
+        if (oldSize == newSize) {
+            // if they're same size, use std::string::replace
+            line.replace(pos, oldSize, newString);
+        }
+        else {
+            // if not same size, replace by erasing and inserting
+            line.erase(pos, oldSize);
+            line.insert(pos, newString);
+        }
+    }
+}
+
+
+
+
+bool ctype_space(const char c, string adddelimiter)
+{
+	if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 11)
+	{
+		return true;
+	}
+	if (adddelimiter.find(c) != string::npos)
+		return true;
+
+	return false;
+}
+
+string trim(string istring, string adddelimiter)
+{
+	bool trimmed = false;
+
+	if (ctype_space(istring[istring.length() - 1], adddelimiter))
+	{
+		istring.erase(istring.length() - 1);
+		trimmed = true;
+	}
+
+	if (ctype_space(istring[0], adddelimiter))
+	{
+		istring.erase(0, 1);
+		trimmed = true;
+	}
+
+	if ((trimmed == false) || (istring.size() == 0))
+	{
+		return istring;
+	}
+	else
+	{
+		return trim(istring, adddelimiter);
+	}
+}
+
+size_t findDelimiterPos(string input, string delimiter)
+{
+	size_t pos = std::string::npos;
+	size_t zw;
+	string akt_del;
+
+	for (int anz = 0; anz < delimiter.length(); ++anz)
+	{
+		akt_del = delimiter[anz];
+		if ((zw = input.find(akt_del)) != std::string::npos)
+		{
+			if (pos != std::string::npos)
+			{
+				if (zw < pos)
+					pos = zw;
+			}
+			else
+				pos = zw;
+		}
+	}
+	return pos;
+}
+
+
+void CopyFile(string input, string output)
+{
+	input = FormatFileName(input);
+	output = FormatFileName(output);
+
+	char cTemp;
+	FILE* fpSourceFile = fopen(input.c_str(), "rb");
+	FILE* fpTargetFile = fopen(output.c_str(), "wb");
+
+	// Code Section
+
+	// Read From The Source File - "Copy"
+	while (fread(&cTemp, 1, 1, fpSourceFile) == 1)
+	{
+		// Write To The Target File - "Paste"
+		fwrite(&cTemp, 1, 1, fpTargetFile);
+	}
+
+	// Close The Files
+	fclose(fpSourceFile);
+	fclose(fpTargetFile);
+}
+
+
+string getFileType(string filename)
+{
+	int lastpos = filename.find(".", 0);
+	int neu_pos;
+	while ((neu_pos = filename.find(".", lastpos + 1)) > -1)
+	{
+		lastpos = neu_pos;
+	}
+
+	string zw = filename.substr(lastpos + 1, filename.size() - lastpos);
+	zw = toUpper(zw);
+
+	return zw;
+}
+
+string toUpper(string in)
+{
+	for (int i = 0; i < in.length(); ++i)
+		in[i] = toupper(in[i]);
+	
+	return in;
+}
+

+ 22 - 0
code/lib/jomjol_image_proc/Helper.h

@@ -0,0 +1,22 @@
+#pragma once
+#include <string>
+#include <fstream>
+
+
+using namespace std;
+
+std::string FormatFileName(std::string input);
+void FindReplace(std::string& line, std::string& oldString, std::string& newString);
+
+void CopyFile(string input, string output);
+
+size_t findDelimiterPos(string input, string delimiter);
+//string trim(string istring);
+string trim(string istring, string adddelimiter = "");
+bool ctype_space(const char c, string adddelimiter);
+
+string getFileType(string filename);
+
+string toUpper(string in);
+
+

+ 5002 - 0
code/lib/jomjol_image_proc/bitmap_image.hpp

@@ -0,0 +1,5002 @@
+/*
+ *****************************************************************************
+ *                                                                           *
+ *                          Platform Independent                             *
+ *                    Bitmap Image Reader Writer Library                     *
+ *                                                                           *
+ * Author: Arash Partow - 2002                                               *
+ * URL: http://partow.net/programming/bitmap/index.html                      *
+ *                                                                           *
+ * Note: This library only supports 24-bits per pixel bitmap format files.   *
+ *                                                                           *
+ * Copyright notice:                                                         *
+ * Free use of the Platform Independent Bitmap Image Reader Writer Library   *
+ * is permitted under the guidelines and in accordance with the most current *
+ * version of the MIT License.                                               *
+ * http://www.opensource.org/licenses/MIT                                    *
+ *                                                                           *
+ *****************************************************************************
+*/
+
+
+#ifndef INCLUDE_BITMAP_IMAGE_HPP
+#define INCLUDE_BITMAP_IMAGE_HPP
+
+#include <algorithm>
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <limits>
+#include <string>
+#include <vector>
+
+
+class bitmap_image
+{
+public:
+
+   enum channel_mode {
+                        rgb_mode = 0,
+                        bgr_mode = 1
+                     };
+
+   enum color_plane {
+                       blue_plane  = 0,
+                       green_plane = 1,
+                       red_plane   = 2
+                    };
+
+   struct rgb_t
+   {
+      unsigned char   red;
+      unsigned char green;
+      unsigned char  blue;
+   };
+
+   bitmap_image()
+   : file_name_(""),
+     width_          (0),
+     height_         (0),
+     row_increment_  (0),
+     bytes_per_pixel_(3),
+     channel_mode_(bgr_mode)
+   {}
+
+   bitmap_image(const std::string& filename)
+   : file_name_(filename),
+     width_          (0),
+     height_         (0),
+     row_increment_  (0),
+     bytes_per_pixel_(0),
+     channel_mode_(bgr_mode)
+   {
+      load_bitmap();
+   }
+
+   bitmap_image(const unsigned int width, const unsigned int height)
+   : file_name_(""),
+     width_ (width ),
+     height_(height),
+     row_increment_  (0),
+     bytes_per_pixel_(3),
+     channel_mode_(bgr_mode)
+   {
+      create_bitmap();
+   }
+
+   bitmap_image(const bitmap_image& image)
+   : file_name_(image.file_name_),
+     width_    (image.width_    ),
+     height_   (image.height_   ),
+     row_increment_  (0),
+     bytes_per_pixel_(3),
+     channel_mode_(bgr_mode)
+   {
+      create_bitmap();
+      data_ = image.data_;
+   }
+
+   bitmap_image& operator=(const bitmap_image& image)
+   {
+      if (this != &image)
+      {
+         file_name_       = image.file_name_;
+         bytes_per_pixel_ = image.bytes_per_pixel_;
+         width_           = image.width_;
+         height_          = image.height_;
+         row_increment_   = 0;
+         channel_mode_    = image.channel_mode_;
+         create_bitmap();
+         data_ = image.data_;
+      }
+
+      return *this;
+   }
+
+   inline bool operator!()
+   {
+      return (data_.size()   == 0) ||
+             (width_         == 0) ||
+             (height_        == 0) ||
+             (row_increment_ == 0);
+   }
+
+   inline void clear(const unsigned char v = 0x00)
+   {
+      std::fill(data_.begin(), data_.end(), v);
+   }
+
+   inline unsigned char red_channel(const unsigned int x, const unsigned int y) const
+   {
+      return data_[(y * row_increment_) + (x * bytes_per_pixel_ + 2)];
+   }
+
+   inline unsigned char green_channel(const unsigned int x, const unsigned int y) const
+   {
+      return data_[(y * row_increment_) + (x * bytes_per_pixel_ + 1)];
+   }
+
+   inline unsigned char blue_channel (const unsigned int x, const unsigned int y) const
+   {
+      return data_[(y * row_increment_) + (x * bytes_per_pixel_ + 0)];
+   }
+
+   inline void red_channel(const unsigned int x, const unsigned int y, const unsigned char value)
+   {
+      data_[(y * row_increment_) + (x * bytes_per_pixel_ + 2)] = value;
+   }
+
+   inline void green_channel(const unsigned int x, const unsigned int y, const unsigned char value)
+   {
+      data_[(y * row_increment_) + (x * bytes_per_pixel_ + 1)] = value;
+   }
+
+   inline void blue_channel (const unsigned int x, const unsigned int y, const unsigned char value)
+   {
+      data_[(y * row_increment_) + (x * bytes_per_pixel_ + 0)] = value;
+   }
+
+   inline unsigned char* row(unsigned int row_index) const
+   {
+      return const_cast<unsigned char*>(&data_[(row_index * row_increment_)]);
+   }
+
+   inline void get_pixel(const unsigned int x, const unsigned int y,
+                         unsigned char& red,
+                         unsigned char& green,
+                         unsigned char& blue) const
+   {
+      const unsigned int y_offset = y * row_increment_;
+      const unsigned int x_offset = x * bytes_per_pixel_;
+      const unsigned int offset   = y_offset + x_offset;
+
+      blue  = data_[offset + 0];
+      green = data_[offset + 1];
+      red   = data_[offset + 2];
+   }
+
+   template <typename RGB>
+   inline void get_pixel(const unsigned int x, const unsigned int y, RGB& colour) const
+   {
+      get_pixel(x, y, colour.red, colour.green, colour.blue);
+   }
+
+   inline rgb_t get_pixel(const unsigned int x, const unsigned int y) const
+   {
+      rgb_t colour;
+      get_pixel(x, y, colour.red, colour.green, colour.blue);
+      return colour;
+   }
+
+   inline void set_pixel(const unsigned int x, const unsigned int y,
+                         const unsigned char red,
+                         const unsigned char green,
+                         const unsigned char blue)
+   {
+      const unsigned int y_offset = y * row_increment_;
+      const unsigned int x_offset = x * bytes_per_pixel_;
+      const unsigned int offset   = y_offset + x_offset;
+
+      data_[offset + 0] = blue;
+      data_[offset + 1] = green;
+      data_[offset + 2] = red;
+   }
+
+   template <typename RGB>
+   inline void set_pixel(const unsigned int x, const unsigned int y, const RGB& colour)
+   {
+      set_pixel(x, y, colour.red, colour.green, colour.blue);
+   }
+
+   inline bool copy_from(const bitmap_image& image)
+   {
+      if (
+           (image.height_ != height_) ||
+           (image.width_  != width_ )
+         )
+      {
+         return false;
+      }
+
+      data_ = image.data_;
+
+      return true;
+   }
+
+   inline bool copy_from(const bitmap_image& source_image,
+                         const unsigned int& x_offset,
+                         const unsigned int& y_offset)
+   {
+      if ((x_offset + source_image.width_ ) > width_ ) { return false; }
+      if ((y_offset + source_image.height_) > height_) { return false; }
+
+      for (unsigned int y = 0; y < source_image.height_; ++y)
+      {
+         unsigned char* itr1           = row(y + y_offset) + x_offset * bytes_per_pixel_;
+         const unsigned char* itr2     = source_image.row(y);
+         const unsigned char* itr2_end = itr2 + source_image.width_ * bytes_per_pixel_;
+
+         std::copy(itr2, itr2_end, itr1);
+      }
+
+      return true;
+   }
+
+   inline bool region(const unsigned int& x     ,
+                      const unsigned int& y     ,
+                      const unsigned int& width ,
+                      const unsigned int& height,
+                      bitmap_image& dest_image  ) const
+   {
+      if ((x + width ) > width_ ) { return false; }
+      if ((y + height) > height_) { return false; }
+
+      if (
+           (dest_image.width_  < width_ ) ||
+           (dest_image.height_ < height_)
+         )
+      {
+         dest_image.setwidth_height(width,height);
+      }
+
+      for (unsigned int r = 0; r < height; ++r)
+      {
+         unsigned char* itr1     = row(r + y) + x * bytes_per_pixel_;
+         unsigned char* itr1_end = itr1 + (width * bytes_per_pixel_);
+         unsigned char* itr2     = dest_image.row(r);
+
+         std::copy(itr1, itr1_end, itr2);
+      }
+
+      return true;
+   }
+
+   inline bool roi_from_center(const unsigned int& cx    ,
+                               const unsigned int& cy    ,
+                               const unsigned int& width ,
+                               const unsigned int& height,
+                               bitmap_image& dest_image  ) const
+   {
+      return region(cx - (width / 2), cy - (height / 2),
+                    width, height,
+                    dest_image);
+   }
+
+   inline bool set_region(const unsigned int&  x     ,
+                          const unsigned int&  y     ,
+                          const unsigned int&  width ,
+                          const unsigned int&  height,
+                          const unsigned char& value )
+   {
+      if ((x + width ) > width_ ) { return false; }
+      if ((y + height) > height_) { return false; }
+
+      for (unsigned int r = 0; r < height; ++r)
+      {
+         unsigned char* itr     = row(r + y) + x * bytes_per_pixel_;
+         unsigned char* itr_end = itr + (width * bytes_per_pixel_);
+
+         std::fill(itr, itr_end, value);
+      }
+
+      return true;
+   }
+
+   inline bool set_region(const unsigned int&  x     ,
+                          const unsigned int&  y     ,
+                          const unsigned int&  width ,
+                          const unsigned int&  height,
+                          const color_plane    color ,
+                          const unsigned char& value )
+   {
+      if ((x + width ) > width_ ) { return false; }
+      if ((y + height) > height_) { return false; }
+
+      const unsigned int color_plane_offset = offset(color);
+
+      for (unsigned int r = 0; r < height; ++r)
+      {
+         unsigned char* itr     = row(r + y) + x * bytes_per_pixel_ + color_plane_offset;
+         unsigned char* itr_end = itr + (width * bytes_per_pixel_);
+
+         while (itr != itr_end)
+         {
+            *itr  = value;
+             itr += bytes_per_pixel_;
+         }
+      }
+
+      return true;
+   }
+
+   inline bool set_region(const unsigned int&  x     ,
+                          const unsigned int&  y     ,
+                          const unsigned int&  width ,
+                          const unsigned int&  height,
+                          const unsigned char& red   ,
+                          const unsigned char& green ,
+                          const unsigned char& blue  )
+   {
+      if ((x + width ) > width_ ) { return false; }
+      if ((y + height) > height_) { return false; }
+
+      for (unsigned int r = 0; r < height; ++r)
+      {
+         unsigned char* itr     = row(r + y) + x * bytes_per_pixel_;
+         unsigned char* itr_end = itr + (width * bytes_per_pixel_);
+
+         while (itr != itr_end)
+         {
+            *(itr++) =  blue;
+            *(itr++) = green;
+            *(itr++) =   red;
+         }
+      }
+
+      return true;
+   }
+
+   void reflective_image(bitmap_image& image, const bool include_diagnols = false)
+   {
+      image.setwidth_height(3 * width_, 3 * height_, true);
+
+      image.copy_from(*this, width_, height_);
+
+      vertical_flip();
+
+      image.copy_from(*this, width_,           0);
+      image.copy_from(*this, width_, 2 * height_);
+
+      vertical_flip();
+      horizontal_flip();
+
+      image.copy_from(*this,          0, height_);
+      image.copy_from(*this, 2 * width_, height_);
+
+      horizontal_flip();
+
+      if (include_diagnols)
+      {
+         bitmap_image tile = *this;
+
+         tile.vertical_flip();
+         tile.horizontal_flip();
+
+         image.copy_from(tile,          0,           0);
+         image.copy_from(tile, 2 * width_,           0);
+         image.copy_from(tile, 2 * width_, 2 * height_);
+         image.copy_from(tile, 0         , 2 * height_);
+      }
+   }
+
+   inline unsigned int width() const
+   {
+      return width_;
+   }
+
+   inline unsigned int height() const
+   {
+      return height_;
+   }
+
+   inline unsigned int bytes_per_pixel() const
+   {
+      return bytes_per_pixel_;
+   }
+
+   inline unsigned int pixel_count() const
+   {
+      return width_ *  height_;
+   }
+
+   inline void setwidth_height(const unsigned int width,
+                               const unsigned int height,
+                               const bool clear = false)
+   {
+      data_.clear();
+      width_  = width;
+      height_ = height;
+
+      create_bitmap();
+
+      if (clear)
+      {
+         std::fill(data_.begin(), data_.end(), static_cast<unsigned char>(0x00));
+      }
+   }
+
+   void save_image(const std::string& file_name) const
+   {
+      std::ofstream stream(file_name.c_str(),std::ios::binary);
+
+      if (!stream)
+      {
+         std::cerr << "bitmap_image::save_image(): Error - Could not open file "
+                   << file_name << " for writing!" << std::endl;
+         return;
+      }
+
+      bitmap_information_header bih;
+
+      bih.width            = width_;
+      bih.height           = height_;
+      bih.bit_count        = static_cast<unsigned short>(bytes_per_pixel_ << 3);
+      bih.clr_important    = 0;
+      bih.clr_used         = 0;
+      bih.compression      = 0;
+      bih.planes           = 1;
+      bih.size             = bih.struct_size();
+      bih.x_pels_per_meter = 0;
+      bih.y_pels_per_meter = 0;
+      bih.size_image       = (((bih.width * bytes_per_pixel_) + 3) & 0x0000FFFC) * bih.height;
+
+      bitmap_file_header bfh;
+
+      bfh.type             = 19778;
+      bfh.size             = bfh.struct_size() + bih.struct_size() + bih.size_image;
+      bfh.reserved1        = 0;
+      bfh.reserved2        = 0;
+      bfh.off_bits         = bih.struct_size() + bfh.struct_size();
+
+      write_bfh(stream,bfh);
+      write_bih(stream,bih);
+
+      unsigned int padding = (4 - ((3 * width_) % 4)) % 4;
+      char padding_data[4] = { 0x00, 0x00, 0x00, 0x00 };
+
+      for (unsigned int i = 0; i < height_; ++i)
+      {
+         const unsigned char* data_ptr = &data_[(row_increment_ * (height_ - i - 1))];
+
+         stream.write(reinterpret_cast<const char*>(data_ptr), sizeof(unsigned char) * bytes_per_pixel_ * width_);
+         stream.write(padding_data,padding);
+      }
+
+      stream.close();
+   }
+
+   inline void set_all_ith_bits_low(const unsigned int bitr_index)
+   {
+      unsigned char mask = static_cast<unsigned char>(~(1 << bitr_index));
+
+      for (unsigned char* itr = data(); itr != end(); ++itr)
+      {
+         *itr &= mask;
+      }
+   }
+
+   inline void set_all_ith_bits_high(const unsigned int bitr_index)
+   {
+      unsigned char mask = static_cast<unsigned char>(1 << bitr_index);
+
+      for (unsigned char* itr = data(); itr != end(); ++itr)
+      {
+         *itr |= mask;
+      }
+   }
+
+   inline void set_all_ith_channels(const unsigned int& channel, const unsigned char& value)
+   {
+      for (unsigned char* itr = (data() + channel); itr < end(); itr += bytes_per_pixel_)
+      {
+         *itr = value;
+      }
+   }
+
+   inline void set_channel(const color_plane color,const unsigned char& value)
+   {
+      for (unsigned char* itr = (data() + offset(color)); itr < end(); itr += bytes_per_pixel_)
+      {
+         *itr = value;
+      }
+   }
+
+   inline void ror_channel(const color_plane color, const unsigned int& ror)
+   {
+      for (unsigned char* itr = (data() + offset(color)); itr < end(); itr += bytes_per_pixel_)
+      {
+         *itr = static_cast<unsigned char>(((*itr) >> ror) | ((*itr) << (8 - ror)));
+      }
+   }
+
+   inline void set_all_channels(const unsigned char& value)
+   {
+      for (unsigned char* itr = data(); itr < end(); )
+      {
+         *(itr++) = value;
+      }
+   }
+
+   inline void set_all_channels(const unsigned char& r_value,
+                                const unsigned char& g_value,
+                                const unsigned char& b_value)
+   {
+      for (unsigned char* itr = (data() + 0); itr < end(); itr += bytes_per_pixel_)
+      {
+         *(itr + 0) = b_value;
+         *(itr + 1) = g_value;
+         *(itr + 2) = r_value;
+      }
+   }
+
+   inline void invert_color_planes()
+   {
+      for (unsigned char* itr = data(); itr < end(); *itr = ~(*itr), ++itr);
+   }
+
+   inline void add_to_color_plane(const color_plane color, const unsigned char& value)
+   {
+      for (unsigned char* itr = (data() + offset(color)); itr < end(); itr += bytes_per_pixel_)
+      {
+         (*itr) += value;
+      }
+   }
+
+   inline void convert_to_grayscale()
+   {
+      double r_scaler = 0.299;
+      double g_scaler = 0.587;
+      double b_scaler = 0.114;
+
+      if (rgb_mode == channel_mode_)
+      {
+         std::swap(r_scaler, b_scaler);
+      }
+
+      for (unsigned char* itr = data(); itr < end(); )
+      {
+         unsigned char gray_value = static_cast<unsigned char>
+                       (
+                         (r_scaler * (*(itr + 2))) +
+                         (g_scaler * (*(itr + 1))) +
+                         (b_scaler * (*(itr + 0)))
+                       );
+
+         *(itr++) = gray_value;
+         *(itr++) = gray_value;
+         *(itr++) = gray_value;
+      }
+   }
+
+   inline const unsigned char* data() const
+   {
+      return data_.data();
+   }
+
+   inline unsigned char* data()
+   {
+      return const_cast<unsigned char*>(data_.data());
+   }
+
+   inline void bgr_to_rgb()
+   {
+      if ((bgr_mode == channel_mode_) && (3 == bytes_per_pixel_))
+      {
+         reverse_channels();
+         channel_mode_ = rgb_mode;
+      }
+   }
+
+   inline void rgb_to_bgr()
+   {
+      if ((rgb_mode == channel_mode_) && (3 == bytes_per_pixel_))
+      {
+         reverse_channels();
+         channel_mode_ = bgr_mode;
+      }
+   }
+
+   inline void reverse()
+   {
+      unsigned char* itr1 = data();
+      unsigned char* itr2 = end() - bytes_per_pixel_;
+
+      while (itr1 < itr2)
+      {
+         for (std::size_t i = 0; i < bytes_per_pixel_; ++i)
+         {
+            unsigned char* citr1 = itr1 + i;
+            unsigned char* citr2 = itr2 + i;
+
+            std::swap(*citr1,*citr2);
+         }
+
+         itr1 += bytes_per_pixel_;
+         itr2 -= bytes_per_pixel_;
+      }
+   }
+
+   inline void horizontal_flip()
+   {
+      for (unsigned int y = 0; y < height_; ++y)
+      {
+         unsigned char* itr1 = row(y);
+         unsigned char* itr2 = itr1 + row_increment_ - bytes_per_pixel_;
+
+         while (itr1 < itr2)
+         {
+            for (unsigned int i = 0; i < bytes_per_pixel_; ++i)
+            {
+               unsigned char* p1 = (itr1 + i);
+               unsigned char* p2 = (itr2 + i);
+
+               std::swap(*p1,*p2);
+            }
+
+            itr1 += bytes_per_pixel_;
+            itr2 -= bytes_per_pixel_;
+         }
+      }
+   }
+
+   inline void vertical_flip()
+   {
+      for (unsigned int y = 0; y < (height_ / 2); ++y)
+      {
+         unsigned char* itr1 = row(y);
+         unsigned char* itr2 = row(height_ - y - 1);
+
+         for (std::size_t x = 0; x < row_increment_; ++x)
+         {
+            std::swap(*(itr1 + x),*(itr2 + x));
+         }
+      }
+   }
+
+   inline void export_color_plane(const color_plane color, unsigned char* image)
+   {
+      for (unsigned char* itr = (data() + offset(color)); itr < end(); ++image, itr += bytes_per_pixel_)
+      {
+         (*image) = (*itr);
+      }
+   }
+
+   inline void export_color_plane(const color_plane color, bitmap_image& image)
+   {
+      if (
+           (width_  != image.width_ ) ||
+           (height_ != image.height_)
+         )
+      {
+         image.setwidth_height(width_,height_);
+      }
+
+      image.clear();
+
+      unsigned char* itr1     = (data() + offset(color));
+      unsigned char* itr1_end = end();
+      unsigned char* itr2     = (image.data() + offset(color));
+
+      while (itr1 < itr1_end)
+      {
+         (*itr2) = (*itr1);
+
+         itr1 += bytes_per_pixel_;
+         itr2 += bytes_per_pixel_;
+      }
+   }
+
+   inline void export_response_image(const color_plane color, double* response_image)
+   {
+      double* resp_itr = response_image;
+
+      for (unsigned char* itr = (data() + offset(color)); itr < end(); ++response_image, itr += bytes_per_pixel_)
+      {
+         *(resp_itr++) = (1.0 * (*itr)) / 256.0;
+      }
+   }
+
+   inline void export_gray_scale_response_image(double* response_image) const
+   {
+      double* resp_itr = response_image;
+
+      for (const unsigned char* itr = data(); itr < end(); itr += bytes_per_pixel_)
+      {
+         unsigned char gray_value = static_cast<unsigned char>
+                       (
+                         (0.299 * (*(itr + 2))) +
+                         (0.587 * (*(itr + 1))) +
+                         (0.114 * (*(itr + 0)))
+                       );
+
+         *(resp_itr++) = (1.0 * gray_value) / 256.0;
+      }
+   }
+
+   inline void export_rgb(double* red, double* green, double* blue) const
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue)
+      {
+         (*blue ) = (1.0 * (*(itr++))) / 256.0;
+         (*green) = (1.0 * (*(itr++))) / 256.0;
+         (*red  ) = (1.0 * (*(itr++))) / 256.0;
+      }
+   }
+
+   inline void export_rgb(float* red, float* green, float* blue) const
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue)
+      {
+         (*blue ) = (1.0f * (*(itr++))) / 256.0f;
+         (*green) = (1.0f * (*(itr++))) / 256.0f;
+         (*red  ) = (1.0f * (*(itr++))) / 256.0f;
+      }
+   }
+
+   inline void export_rgb(unsigned char* red, unsigned char* green, unsigned char* blue) const
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue)
+      {
+         (*blue ) = *(itr++);
+         (*green) = *(itr++);
+         (*red  ) = *(itr++);
+      }
+   }
+
+   inline void export_ycbcr(double* y, double* cb, double* cr) const
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (const unsigned char* itr = data(); itr < end(); ++y, ++cb, ++cr)
+      {
+         const double blue  = (1.0 * (*(itr++)));
+         const double green = (1.0 * (*(itr++)));
+         const double red   = (1.0 * (*(itr++)));
+
+         ( *y) = clamp<double>( 16.0 + (1.0/256.0) * (  65.738 * red + 129.057 * green +  25.064 * blue),1.0,254);
+         (*cb) = clamp<double>(128.0 + (1.0/256.0) * (- 37.945 * red -  74.494 * green + 112.439 * blue),1.0,254);
+         (*cr) = clamp<double>(128.0 + (1.0/256.0) * ( 112.439 * red -  94.154 * green -  18.285 * blue),1.0,254);
+      }
+   }
+
+   inline void export_rgb_normal(double* red, double* green, double* blue) const
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue)
+      {
+         (*blue ) = (1.0 * (*(itr++)));
+         (*green) = (1.0 * (*(itr++)));
+         (*red  ) = (1.0 * (*(itr++)));
+      }
+   }
+
+   inline void export_rgb_normal(float* red, float* green, float* blue) const
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (const unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue)
+      {
+         (*blue ) = (1.0f * (*(itr++)));
+         (*green) = (1.0f * (*(itr++)));
+         (*red  ) = (1.0f * (*(itr++)));
+      }
+   }
+
+   inline void import_rgb(double* red, double* green, double* blue)
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue)
+      {
+         *(itr++) = static_cast<unsigned char>(256.0 * (*blue ));
+         *(itr++) = static_cast<unsigned char>(256.0 * (*green));
+         *(itr++) = static_cast<unsigned char>(256.0 * (*red  ));
+      }
+   }
+
+   inline void import_rgb(float* red, float* green, float* blue)
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue)
+      {
+         *(itr++) = static_cast<unsigned char>(256.0f * (*blue ));
+         *(itr++) = static_cast<unsigned char>(256.0f * (*green));
+         *(itr++) = static_cast<unsigned char>(256.0f * (*red  ));
+      }
+   }
+
+   inline void import_rgb(unsigned char* red, unsigned char* green, unsigned char* blue)
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue)
+      {
+         *(itr++) = (*blue );
+         *(itr++) = (*green);
+         *(itr++) = (*red  );
+      }
+   }
+
+   inline void import_ycbcr(double* y, double* cb, double* cr)
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (unsigned char* itr = data(); itr < end(); ++y, ++cb, ++cr)
+      {
+         double y_  =  (*y);
+         double cb_ = (*cb);
+         double cr_ = (*cr);
+
+         *(itr++) = static_cast<unsigned char>(clamp((298.082 * y_ + 516.412 * cb_                 ) / 256.0 - 276.836,0.0,255.0));
+         *(itr++) = static_cast<unsigned char>(clamp((298.082 * y_ - 100.291 * cb_ - 208.120 * cr_ ) / 256.0 + 135.576,0.0,255.0));
+         *(itr++) = static_cast<unsigned char>(clamp((298.082 * y_                 + 408.583 * cr_ ) / 256.0 - 222.921,0.0,255.0));
+      }
+   }
+
+   inline void import_gray_scale_clamped(double* gray)
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (unsigned char* itr = data(); itr < end(); ++gray)
+      {
+         unsigned char c = static_cast<unsigned char>(clamp<double>(256.0 * (*gray),0.0,255.0));
+
+         *(itr + 0) = c;
+         *(itr + 1) = c;
+         *(itr + 2) = c;
+
+         itr += 3;
+      }
+   }
+
+   inline void import_rgb_clamped(double* red, double* green, double* blue)
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue)
+      {
+         *(itr++) = static_cast<unsigned char>(clamp<double>(256.0 * (*blue ),0.0,255.0));
+         *(itr++) = static_cast<unsigned char>(clamp<double>(256.0 * (*green),0.0,255.0));
+         *(itr++) = static_cast<unsigned char>(clamp<double>(256.0 * (*red  ),0.0,255.0));
+      }
+   }
+
+   inline void import_rgb_clamped(float* red, float* green, float* blue)
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue)
+      {
+         *(itr++) = static_cast<unsigned char>(clamp<double>(256.0f * (*blue ),0.0,255.0));
+         *(itr++) = static_cast<unsigned char>(clamp<double>(256.0f * (*green),0.0,255.0));
+         *(itr++) = static_cast<unsigned char>(clamp<double>(256.0f * (*red  ),0.0,255.0));
+      }
+   }
+
+   inline void import_rgb_normal(double* red, double* green, double* blue)
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue)
+      {
+         *(itr++) = static_cast<unsigned char>(*blue );
+         *(itr++) = static_cast<unsigned char>(*green);
+         *(itr++) = static_cast<unsigned char>(*red  );
+      }
+   }
+
+   inline void import_rgb_normal(float* red, float* green, float* blue)
+   {
+      if (bgr_mode != channel_mode_)
+         return;
+
+      for (unsigned char* itr = data(); itr < end(); ++red, ++green, ++blue)
+      {
+         *(itr++) = static_cast<unsigned char>(*blue );
+         *(itr++) = static_cast<unsigned char>(*green);
+         *(itr++) = static_cast<unsigned char>(*red  );
+      }
+   }
+
+   inline void subsample(bitmap_image& dest) const
+   {
+      /*
+         Half sub-sample of original image.
+      */
+      unsigned int w = 0;
+      unsigned int h = 0;
+
+      bool odd_width = false;
+      bool odd_height = false;
+
+      if (0 == (width_ % 2))
+         w = width_ / 2;
+      else
+      {
+         w = 1 + (width_ / 2);
+         odd_width = true;
+      }
+
+      if (0 == (height_ % 2))
+         h = height_ / 2;
+      else
+      {
+         h = 1 + (height_ / 2);
+         odd_height = true;
+      }
+
+      unsigned int horizontal_upper = (odd_width)  ? (w - 1) : w;
+      unsigned int vertical_upper   = (odd_height) ? (h - 1) : h;
+
+      dest.setwidth_height(w,h);
+      dest.clear();
+
+            unsigned char* s_itr[3];
+      const unsigned char*  itr1[3];
+      const unsigned char*  itr2[3];
+
+      s_itr[0] = dest.data() + 0;
+      s_itr[1] = dest.data() + 1;
+      s_itr[2] = dest.data() + 2;
+
+      itr1[0] = data() + 0;
+      itr1[1] = data() + 1;
+      itr1[2] = data() + 2;
+
+      itr2[0] = data() + row_increment_ + 0;
+      itr2[1] = data() + row_increment_ + 1;
+      itr2[2] = data() + row_increment_ + 2;
+
+      unsigned int total = 0;
+
+      for (unsigned int j = 0; j < vertical_upper; ++j)
+      {
+         for (unsigned int i = 0; i < horizontal_upper; ++i)
+         {
+            for (unsigned int k = 0; k < bytes_per_pixel_; s_itr[k] += bytes_per_pixel_, ++k)
+            {
+               total = 0;
+               total += *(itr1[k]);
+               total += *(itr1[k]);
+               total += *(itr2[k]);
+               total += *(itr2[k]);
+
+               itr1[k] += bytes_per_pixel_;
+               itr1[k] += bytes_per_pixel_;
+               itr2[k] += bytes_per_pixel_;
+               itr2[k] += bytes_per_pixel_;
+
+               *(s_itr[k]) = static_cast<unsigned char>(total >> 2);
+            }
+         }
+
+         if (odd_width)
+         {
+            for (unsigned int k = 0; k < bytes_per_pixel_; s_itr[k] += bytes_per_pixel_, ++k)
+            {
+               total = 0;
+               total += *(itr1[k]);
+               total += *(itr2[k]);
+
+               itr1[k] += bytes_per_pixel_;
+               itr2[k] += bytes_per_pixel_;
+
+               *(s_itr[k]) = static_cast<unsigned char>(total >> 1);
+            }
+         }
+
+         for (unsigned int k = 0; k < bytes_per_pixel_; ++k)
+         {
+            itr1[k] += row_increment_;
+         }
+
+         if (j != (vertical_upper - 1))
+         {
+            for (unsigned int k = 0; k < bytes_per_pixel_; ++k)
+            {
+               itr2[k] += row_increment_;
+            }
+         }
+      }
+
+      if (odd_height)
+      {
+         for (unsigned int i = 0; i < horizontal_upper; ++i)
+         {
+            for (unsigned int k = 0; k < bytes_per_pixel_; s_itr[k] += bytes_per_pixel_, ++k)
+            {
+               total = 0;
+               total += *(itr1[k]);
+               total += *(itr2[k]);
+
+               itr1[k] += bytes_per_pixel_;
+               itr2[k] += bytes_per_pixel_;
+
+               *(s_itr[k]) = static_cast<unsigned char>(total >> 1);
+            }
+         }
+
+         if (odd_width)
+         {
+            for (unsigned int k = 0; k < bytes_per_pixel_; ++k)
+            {
+               (*(s_itr[k])) = *(itr1[k]);
+            }
+         }
+      }
+   }
+
+   inline void upsample(bitmap_image& dest) const
+   {
+      /*
+         2x up-sample of original image.
+      */
+
+      dest.setwidth_height(2 * width_ ,2 * height_);
+      dest.clear();
+
+      const unsigned char* s_itr[3];
+            unsigned char*  itr1[3];
+            unsigned char*  itr2[3];
+
+      s_itr[0] = data() + 0;
+      s_itr[1] = data() + 1;
+      s_itr[2] = data() + 2;
+
+      itr1[0] = dest.data() + 0;
+      itr1[1] = dest.data() + 1;
+      itr1[2] = dest.data() + 2;
+
+      itr2[0] = dest.data() + dest.row_increment_ + 0;
+      itr2[1] = dest.data() + dest.row_increment_ + 1;
+      itr2[2] = dest.data() + dest.row_increment_ + 2;
+
+      for (unsigned int j = 0; j < height_; ++j)
+      {
+         for (unsigned int i = 0; i < width_; ++i)
+         {
+            for (unsigned int k = 0; k < bytes_per_pixel_; s_itr[k] += bytes_per_pixel_, ++k)
+            {
+               *(itr1[k]) = *(s_itr[k]); itr1[k] += bytes_per_pixel_;
+               *(itr1[k]) = *(s_itr[k]); itr1[k] += bytes_per_pixel_;
+
+               *(itr2[k]) = *(s_itr[k]); itr2[k] += bytes_per_pixel_;
+               *(itr2[k]) = *(s_itr[k]); itr2[k] += bytes_per_pixel_;
+            }
+         }
+
+         for (unsigned int k = 0; k < bytes_per_pixel_; ++k)
+         {
+            itr1[k] += dest.row_increment_;
+            itr2[k] += dest.row_increment_;
+         }
+      }
+   }
+
+   inline void alpha_blend(const double& alpha, const bitmap_image& image)
+   {
+      if (
+           (image.width_  != width_ ) ||
+           (image.height_ != height_)
+         )
+      {
+         return;
+      }
+
+      if ((alpha < 0.0) || (alpha > 1.0))
+      {
+         return;
+      }
+
+      unsigned char* itr1           = data();
+      const unsigned char* itr1_end = end();
+      const unsigned char* itr2     = image.data();
+
+      double alpha_compliment = 1.0 - alpha;
+
+      while (itr1 != itr1_end)
+      {
+         *(itr1) = static_cast<unsigned char>((alpha * (*itr2)) + (alpha_compliment * (*itr1)));
+         ++itr1;
+         ++itr2;
+      }
+   }
+
+   inline double psnr(const bitmap_image& image)
+   {
+      if (
+           (image.width_  != width_ ) ||
+           (image.height_ != height_)
+         )
+      {
+         return 0.0;
+      }
+
+      const unsigned char* itr1 = data();
+      const unsigned char* itr2 = image.data();
+
+      double mse = 0.0;
+
+      while (itr1 != end())
+      {
+         const double v = (static_cast<double>(*itr1) - static_cast<double>(*itr2));
+
+         mse += v * v;
+         ++itr1;
+         ++itr2;
+      }
+
+      if (mse <= 0.0000001)
+      {
+         return 1000000.0;
+      }
+      else
+      {
+         mse /= (3.0 * width_ * height_);
+
+         return 20.0 * std::log10(255.0 / std::sqrt(mse));
+      }
+   }
+
+   inline double psnr(const unsigned int& x,
+                      const unsigned int& y,
+                      const bitmap_image& image)
+   {
+      if ((x + image.width() ) > width_ ) { return 0.0; }
+      if ((y + image.height()) > height_) { return 0.0; }
+
+      double mse = 0.0;
+
+      const unsigned int height = image.height();
+      const unsigned int width  = image.width();
+
+      for (unsigned int r = 0; r < height; ++r)
+      {
+         const unsigned char* itr1     = row(r + y) + x * bytes_per_pixel_;
+         const unsigned char* itr1_end = itr1 + (width * bytes_per_pixel_);
+         const unsigned char* itr2     = image.row(r);
+
+         while (itr1 != itr1_end)
+         {
+            double v = (static_cast<double>(*itr1) - static_cast<double>(*itr2));
+            mse += v * v;
+            ++itr1;
+            ++itr2;
+         }
+      }
+
+      if (mse <= 0.0000001)
+      {
+         return 1000000.0;
+      }
+      else
+      {
+         mse /= (3.0 * image.width() * image.height());
+         return 20.0 * std::log10(255.0 / std::sqrt(mse));
+      }
+   }
+
+   inline void histogram(const color_plane color, double hist[256]) const
+   {
+      std::fill(hist, hist + 256, 0.0);
+
+      for (const unsigned char* itr = (data() + offset(color)); itr < end(); itr += bytes_per_pixel_)
+      {
+         ++hist[(*itr)];
+      }
+   }
+
+   inline void histogram_normalized(const color_plane color, double hist[256]) const
+   {
+      histogram(color,hist);
+
+      double* h_itr = hist;
+      const double* h_end = hist + 256;
+      const double pixel_count = static_cast<double>(width_ * height_);
+
+      while (h_end != h_itr)
+      {
+         *(h_itr++) /= pixel_count;
+      }
+   }
+
+   inline unsigned int offset(const color_plane color) const
+   {
+      switch (channel_mode_)
+      {
+         case rgb_mode : {
+                            switch (color)
+                            {
+                               case red_plane   : return 0;
+                               case green_plane : return 1;
+                               case blue_plane  : return 2;
+                               default          : return std::numeric_limits<unsigned int>::max();
+                            }
+                         }
+
+         case bgr_mode : {
+                            switch (color)
+                            {
+                               case red_plane   : return 2;
+                               case green_plane : return 1;
+                               case blue_plane  : return 0;
+                               default          : return std::numeric_limits<unsigned int>::max();
+                            }
+                         }
+
+         default       : return std::numeric_limits<unsigned int>::max();
+      }
+   }
+
+   inline void incremental()
+   {
+      unsigned char current_color = 0;
+
+      for (unsigned char* itr = data(); itr < end();)
+      {
+         (*itr++) = (current_color);
+         (*itr++) = (current_color);
+         (*itr++) = (current_color);
+
+         ++current_color;
+      }
+   }
+
+   inline void reverse_channels()
+   {
+      if (3 != bytes_per_pixel_)
+         return;
+
+      for (unsigned char* itr = data(); itr < end(); itr += bytes_per_pixel_)
+      {
+         std::swap(*(itr + 0),*(itr + 2));
+      }
+   }
+
+private:
+
+   inline const unsigned char* end() const
+   {
+      return data_.data() + data_.size();
+   }
+
+   inline unsigned char* end()
+   {
+      return const_cast<unsigned char*>(data() + data_.size());
+   }
+
+   struct bitmap_file_header
+   {
+      unsigned short type;
+      unsigned int   size;
+      unsigned short reserved1;
+      unsigned short reserved2;
+      unsigned int   off_bits;
+
+      unsigned int struct_size() const
+      {
+         return sizeof(type     ) +
+                sizeof(size     ) +
+                sizeof(reserved1) +
+                sizeof(reserved2) +
+                sizeof(off_bits ) ;
+      }
+
+      void clear()
+      {
+         std::memset(this, 0x00, sizeof(bitmap_file_header));
+      }
+   };
+
+   struct bitmap_information_header
+   {
+      unsigned int   size;
+      unsigned int   width;
+      unsigned int   height;
+      unsigned short planes;
+      unsigned short bit_count;
+      unsigned int   compression;
+      unsigned int   size_image;
+      unsigned int   x_pels_per_meter;
+      unsigned int   y_pels_per_meter;
+      unsigned int   clr_used;
+      unsigned int   clr_important;
+
+      unsigned int struct_size() const
+      {
+         return sizeof(size            ) +
+                sizeof(width           ) +
+                sizeof(height          ) +
+                sizeof(planes          ) +
+                sizeof(bit_count       ) +
+                sizeof(compression     ) +
+                sizeof(size_image      ) +
+                sizeof(x_pels_per_meter) +
+                sizeof(y_pels_per_meter) +
+                sizeof(clr_used        ) +
+                sizeof(clr_important   ) ;
+      }
+
+      void clear()
+      {
+         std::memset(this, 0x00, sizeof(bitmap_information_header));
+      }
+   };
+
+   inline bool big_endian() const
+   {
+      unsigned int v = 0x01;
+
+      return (1 != reinterpret_cast<char*>(&v)[0]);
+   }
+
+   inline unsigned short flip(const unsigned short& v) const
+   {
+      return ((v >> 8) | (v << 8));
+   }
+
+   inline unsigned int flip(const unsigned int& v) const
+   {
+      return (
+               ((v & 0xFF000000) >> 0x18) |
+               ((v & 0x000000FF) << 0x18) |
+               ((v & 0x00FF0000) >> 0x08) |
+               ((v & 0x0000FF00) << 0x08)
+             );
+   }
+
+   template <typename T>
+   inline void read_from_stream(std::ifstream& stream,T& t)
+   {
+      stream.read(reinterpret_cast<char*>(&t),sizeof(T));
+   }
+
+   template <typename T>
+   inline void write_to_stream(std::ofstream& stream,const T& t) const
+   {
+      stream.write(reinterpret_cast<const char*>(&t),sizeof(T));
+   }
+
+   inline void read_bfh(std::ifstream& stream, bitmap_file_header& bfh)
+   {
+      read_from_stream(stream,bfh.type     );
+      read_from_stream(stream,bfh.size     );
+      read_from_stream(stream,bfh.reserved1);
+      read_from_stream(stream,bfh.reserved2);
+      read_from_stream(stream,bfh.off_bits );
+
+      if (big_endian())
+      {
+         bfh.type      = flip(bfh.type     );
+         bfh.size      = flip(bfh.size     );
+         bfh.reserved1 = flip(bfh.reserved1);
+         bfh.reserved2 = flip(bfh.reserved2);
+         bfh.off_bits  = flip(bfh.off_bits );
+      }
+   }
+
+   inline void write_bfh(std::ofstream& stream, const bitmap_file_header& bfh) const
+   {
+      if (big_endian())
+      {
+         write_to_stream(stream,flip(bfh.type     ));
+         write_to_stream(stream,flip(bfh.size     ));
+         write_to_stream(stream,flip(bfh.reserved1));
+         write_to_stream(stream,flip(bfh.reserved2));
+         write_to_stream(stream,flip(bfh.off_bits ));
+      }
+      else
+      {
+         write_to_stream(stream,bfh.type     );
+         write_to_stream(stream,bfh.size     );
+         write_to_stream(stream,bfh.reserved1);
+         write_to_stream(stream,bfh.reserved2);
+         write_to_stream(stream,bfh.off_bits );
+      }
+   }
+
+   inline void read_bih(std::ifstream& stream,bitmap_information_header& bih)
+   {
+      read_from_stream(stream,bih.size            );
+      read_from_stream(stream,bih.width           );
+      read_from_stream(stream,bih.height          );
+      read_from_stream(stream,bih.planes          );
+      read_from_stream(stream,bih.bit_count       );
+      read_from_stream(stream,bih.compression     );
+      read_from_stream(stream,bih.size_image      );
+      read_from_stream(stream,bih.x_pels_per_meter);
+      read_from_stream(stream,bih.y_pels_per_meter);
+      read_from_stream(stream,bih.clr_used        );
+      read_from_stream(stream,bih.clr_important   );
+
+      if (big_endian())
+      {
+         bih.size          = flip(bih.size               );
+         bih.width         = flip(bih.width              );
+         bih.height        = flip(bih.height             );
+         bih.planes        = flip(bih.planes             );
+         bih.bit_count     = flip(bih.bit_count          );
+         bih.compression   = flip(bih.compression        );
+         bih.size_image    = flip(bih.size_image         );
+         bih.x_pels_per_meter = flip(bih.x_pels_per_meter);
+         bih.y_pels_per_meter = flip(bih.y_pels_per_meter);
+         bih.clr_used      = flip(bih.clr_used           );
+         bih.clr_important = flip(bih.clr_important      );
+      }
+   }
+
+   inline void write_bih(std::ofstream& stream, const bitmap_information_header& bih) const
+   {
+      if (big_endian())
+      {
+         write_to_stream(stream,flip(bih.size            ));
+         write_to_stream(stream,flip(bih.width           ));
+         write_to_stream(stream,flip(bih.height          ));
+         write_to_stream(stream,flip(bih.planes          ));
+         write_to_stream(stream,flip(bih.bit_count       ));
+         write_to_stream(stream,flip(bih.compression     ));
+         write_to_stream(stream,flip(bih.size_image      ));
+         write_to_stream(stream,flip(bih.x_pels_per_meter));
+         write_to_stream(stream,flip(bih.y_pels_per_meter));
+         write_to_stream(stream,flip(bih.clr_used        ));
+         write_to_stream(stream,flip(bih.clr_important   ));
+      }
+      else
+      {
+         write_to_stream(stream,bih.size            );
+         write_to_stream(stream,bih.width           );
+         write_to_stream(stream,bih.height          );
+         write_to_stream(stream,bih.planes          );
+         write_to_stream(stream,bih.bit_count       );
+         write_to_stream(stream,bih.compression     );
+         write_to_stream(stream,bih.size_image      );
+         write_to_stream(stream,bih.x_pels_per_meter);
+         write_to_stream(stream,bih.y_pels_per_meter);
+         write_to_stream(stream,bih.clr_used        );
+         write_to_stream(stream,bih.clr_important   );
+      }
+   }
+
+   inline std::size_t file_size(const std::string& file_name) const
+   {
+      std::ifstream file(file_name.c_str(),std::ios::in | std::ios::binary);
+      if (!file) return 0;
+      file.seekg (0, std::ios::end);
+      return static_cast<std::size_t>(file.tellg());
+   }
+
+   void create_bitmap()
+   {
+      row_increment_ = width_ * bytes_per_pixel_;
+      data_.resize(height_ * row_increment_);
+   }
+
+   void load_bitmap()
+   {
+      std::ifstream stream(file_name_.c_str(),std::ios::binary);
+
+      if (!stream)
+      {
+         std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - "
+                   << "file " << file_name_ << " not found!" << std::endl;
+         return;
+      }
+
+      width_  = 0;
+      height_ = 0;
+
+      bitmap_file_header bfh;
+      bitmap_information_header bih;
+
+      bfh.clear();
+      bih.clear();
+
+      read_bfh(stream,bfh);
+      read_bih(stream,bih);
+
+      if (bfh.type != 19778)
+      {
+         std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - "
+                   << "Invalid type value " << bfh.type << " expected 19778." << std::endl;
+
+         bfh.clear();
+         bih.clear();
+
+         stream.close();
+
+         return;
+      }
+
+      if (bih.bit_count != 24)
+      {
+         std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - "
+                   << "Invalid bit depth " << bih.bit_count << " expected 24." << std::endl;
+
+         bfh.clear();
+         bih.clear();
+
+         stream.close();
+
+         return;
+      }
+
+      if (bih.size != bih.struct_size())
+      {
+         std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - "
+                   << "Invalid BIH size " << bih.size
+                   << " expected " << bih.struct_size() << std::endl;
+
+         bfh.clear();
+         bih.clear();
+
+         stream.close();
+
+         return;
+      }
+
+      width_  = bih.width;
+      height_ = bih.height;
+
+      bytes_per_pixel_ = bih.bit_count >> 3;
+
+      unsigned int padding = (4 - ((3 * width_) % 4)) % 4;
+      char padding_data[4] = { 0x00, 0x00, 0x00, 0x00 };
+
+      std::size_t bitmap_file_size = file_size(file_name_);
+
+      std::size_t bitmap_logical_size = (height_ * width_ * bytes_per_pixel_) +
+                                        (height_ * padding)                   +
+                                         bih.struct_size()                    +
+                                         bfh.struct_size()                    ;
+
+      if (bitmap_file_size != bitmap_logical_size)
+      {
+         std::cerr << "bitmap_image::load_bitmap() ERROR: bitmap_image - "
+                   << "Mismatch between logical and physical sizes of bitmap. "
+                   << "Logical: "  << bitmap_logical_size << " "
+                   << "Physical: " << bitmap_file_size    << std::endl;
+
+         bfh.clear();
+         bih.clear();
+
+         stream.close();
+
+         return;
+      }
+
+      create_bitmap();
+
+      for (unsigned int i = 0; i < height_; ++i)
+      {
+         unsigned char* data_ptr = row(height_ - i - 1); // read in inverted row order
+
+         stream.read(reinterpret_cast<char*>(data_ptr), sizeof(char) * bytes_per_pixel_ * width_);
+         stream.read(padding_data,padding);
+      }
+   }
+
+   template <typename T>
+   inline T clamp(const T& v, const T& lower_range, const T& upper_range) const
+   {
+      if (v < lower_range)
+         return lower_range;
+      else if (v >  upper_range)
+         return upper_range;
+      else
+         return v;
+   }
+
+   std::string  file_name_;
+   unsigned int width_;
+   unsigned int height_;
+   unsigned int row_increment_;
+   unsigned int bytes_per_pixel_;
+   channel_mode channel_mode_;
+   std::vector<unsigned char> data_;
+};
+
+typedef bitmap_image::rgb_t rgb_t;
+
+inline bool operator==(const rgb_t& c0, const rgb_t& c1)
+{
+   return (c0.red   == c1  .red) &&
+          (c0.green == c1.green) &&
+          (c0.blue  == c1 .blue) ;
+}
+
+inline bool operator!=(const rgb_t& c0, const rgb_t& c1)
+{
+   return (c0.red   != c1  .red) ||
+          (c0.green != c1.green) ||
+          (c0.blue  != c1 .blue) ;
+}
+
+inline std::size_t hamming_distance(const rgb_t& c0, const rgb_t& c1)
+{
+   std::size_t result = 0;
+
+   if (c0.red   != c1  .red) ++result;
+   if (c0.green != c1.green) ++result;
+   if (c0.blue  != c1 .blue) ++result;
+
+   return result;
+}
+
+inline rgb_t make_colour(const unsigned int& red, const unsigned int& green, const unsigned int& blue)
+{
+   rgb_t result;
+
+   result.red   = static_cast<unsigned char>(red  );
+   result.green = static_cast<unsigned char>(green);
+   result.blue  = static_cast<unsigned char>(blue );
+
+   return result;
+}
+
+template <typename OutputIterator>
+inline void generate_colours(const std::size_t& steps, const rgb_t c0, const rgb_t& c1, OutputIterator out)
+{
+   double dr = ((double)c1.red   -  (double)c0.red   ) / steps;
+   double dg = ((double)c1.green -  (double)c0.green ) / steps;
+   double db = ((double)c1.blue  -  (double)c0.blue  ) / steps;
+
+   for (std::size_t i = 0; i < steps; ++i)
+   {
+      rgb_t c;
+
+      c.red   = static_cast<unsigned char>(c0.red   + (i * dr));
+      c.green = static_cast<unsigned char>(c0.green + (i * dg));
+      c.blue  = static_cast<unsigned char>(c0.blue  + (i * db));
+
+      *(out++) = c;
+   }
+}
+
+template <typename ResponseImage, typename Palette>
+inline std::size_t convert_rsp_to_image(const ResponseImage& resp_image, const Palette& palette, bitmap_image& image)
+{
+   if (
+        (resp_image.width () > image.width ()) ||
+        (resp_image.height() > image.height())
+      )
+      return 0;
+
+   for (std::size_t y = 0; y < resp_image.height(); ++y)
+   {
+      for (std::size_t x = 0; x < resp_image.width(); ++x)
+      {
+         const double v = resp_image(x,y);
+
+         unsigned int index = static_cast<unsigned int>((v < 0) ? 0 : v > (palette.size()) ? (palette.size() - 1) : v);
+
+         image.set_pixel(x,y,palette[index]);
+      }
+   }
+
+   return (resp_image.width() * resp_image.height());
+}
+
+inline void rgb_to_ycbcr(const unsigned int& length, double* red, double* green, double* blue,
+                                                     double* y,   double* cb,    double* cr)
+{
+   unsigned int i = 0;
+
+   while (i < length)
+   {
+      ( *y) =   16.0 + (  65.481 * (*red) +  128.553 * (*green) +  24.966 * (*blue));
+      (*cb) =  128.0 + ( -37.797 * (*red) +  -74.203 * (*green) + 112.000 * (*blue));
+      (*cr) =  128.0 + ( 112.000 * (*red) +  -93.786 * (*green) -  18.214 * (*blue));
+
+      ++i;
+      ++red; ++green; ++blue;
+      ++y;   ++cb;    ++cr;
+   }
+}
+
+inline void ycbcr_to_rgb(const unsigned int& length, double* y,   double* cb,    double* cr,
+                                                     double* red, double* green, double* blue)
+{
+   unsigned int i = 0;
+
+   while (i < length)
+   {
+      double y_  =  (*y) -  16.0;
+      double cb_ = (*cb) - 128.0;
+      double cr_ = (*cr) - 128.0;
+
+        (*red) = 0.000456621 * y_                    + 0.00625893 * cr_;
+      (*green) = 0.000456621 * y_ - 0.00153632 * cb_ - 0.00318811 * cr_;
+       (*blue) = 0.000456621 * y_                    + 0.00791071 * cb_;
+
+      ++i;
+      ++red; ++green; ++blue;
+      ++y;   ++cb;    ++cr;
+   }
+}
+
+inline void subsample(const unsigned int& width,
+                      const unsigned int& height,
+                      const double* source,
+                      unsigned int& w,
+                      unsigned int& h,
+                      double*& dest)
+{
+   /*  Single channel.  */
+
+   w = 0;
+   h = 0;
+
+   bool odd_width = false;
+   bool odd_height = false;
+
+   if (0 == (width % 2))
+      w = width / 2;
+   else
+   {
+      w = 1 + (width / 2);
+      odd_width = true;
+   }
+
+   if (0 == (height % 2))
+      h = height / 2;
+   else
+   {
+      h = 1 + (height / 2);
+      odd_height = true;
+   }
+
+   unsigned int horizontal_upper = (odd_width)  ? w - 1 : w;
+   unsigned int vertical_upper   = (odd_height) ? h - 1 : h;
+
+   dest = new double[w * h];
+
+         double* s_itr = dest;
+   const double* itr1  = source;
+   const double* itr2  = source + width;
+
+   for (unsigned int j = 0; j < vertical_upper; ++j)
+   {
+      for (unsigned int i = 0; i < horizontal_upper; ++i, ++s_itr)
+      {
+          (*s_itr)  = *(itr1++);
+          (*s_itr) += *(itr1++);
+          (*s_itr) += *(itr2++);
+          (*s_itr) += *(itr2++);
+          (*s_itr) /=  4.0;
+      }
+
+      if (odd_width)
+      {
+         (*(s_itr++)) = ((*itr1++) + (*itr2++)) / 2.0;
+      }
+
+      itr1 += width;
+
+      if (j != (vertical_upper -1))
+      {
+         itr2 += width;
+      }
+   }
+
+   if (odd_height)
+   {
+      for (unsigned int i = 0; i < horizontal_upper; ++i, ++s_itr)
+      {
+         (*s_itr) += (*(itr1++));
+         (*s_itr) += (*(itr1++));
+         (*s_itr) /= 2.0;
+      }
+
+      if (odd_width)
+      {
+         (*(s_itr++)) = (*itr1);
+      }
+   }
+}
+
+inline void upsample(const unsigned int& width,
+                     const unsigned int& height,
+                     const double* source,
+                     unsigned int& w,
+                     unsigned int& h,
+                     double*& dest)
+{
+   /* Single channel. */
+
+   w = 2 * width;
+   h = 2 * height;
+
+   dest = new double[w * h];
+
+   const double* s_itr = source;
+         double* itr1  = dest;
+         double* itr2  = dest + w;
+
+   for (unsigned int j = 0; j < height; ++j)
+   {
+      for (unsigned int i = 0; i < width; ++i, ++s_itr)
+      {
+          *(itr1++) = (*s_itr);
+          *(itr1++) = (*s_itr);
+          *(itr2++) = (*s_itr);
+          *(itr2++) = (*s_itr);
+      }
+
+      itr1 += w;
+      itr2 += w;
+   }
+}
+
+inline void checkered_pattern(const unsigned int x_width,
+                              const unsigned int y_width,
+                              const unsigned char value,
+                              const bitmap_image::color_plane color,
+                              bitmap_image& image)
+{
+   if (
+        (x_width >= image.width ()) ||
+        (y_width >= image.height())
+      )
+   {
+      return;
+   }
+
+   bool setter_x = false;
+   bool setter_y = true;
+
+   const unsigned int color_plane_offset = image.offset(color);
+   const unsigned int height = image.height();
+   const unsigned int width  = image.width();
+
+   for (unsigned int y = 0; y < height; ++y)
+   {
+      if (0 == (y % y_width))
+      {
+         setter_y = !setter_y;
+      }
+
+      unsigned char* row = image.row(y) + color_plane_offset;
+
+      for (unsigned int x = 0; x < width; ++x, row += image.bytes_per_pixel())
+      {
+         if (0 == (x % x_width))
+         {
+            setter_x = !setter_x;
+         }
+
+         if (setter_x ^ setter_y)
+         {
+            *row = value;
+         }
+      }
+   }
+}
+
+inline void checkered_pattern(const unsigned int x_width,
+                              const unsigned int y_width,
+                              const unsigned char red,
+                              const unsigned char green,
+                              const unsigned char blue,
+                              bitmap_image& image)
+{
+   if (
+        (x_width >= image.width ()) ||
+        (y_width >= image.height())
+      )
+   {
+      return;
+   }
+
+   bool setter_x = false;
+   bool setter_y = true;
+
+   const unsigned int height = image.height();
+   const unsigned int width  = image.width();
+
+   for (unsigned int y = 0; y < height; ++y)
+   {
+      if (0 == (y % y_width))
+      {
+         setter_y = !setter_y;
+      }
+
+      unsigned char* row = image.row(y);
+
+      for (unsigned int x = 0; x < width; ++x, row += image.bytes_per_pixel())
+      {
+         if (0 == (x % x_width))
+         {
+            setter_x = !setter_x;
+         }
+
+         if (setter_x ^ setter_y)
+         {
+            *(row + 0) = blue;
+            *(row + 1) = green;
+            *(row + 2) = red;
+         }
+      }
+   }
+}
+
+inline void plasma(bitmap_image& image,
+                   const double& x,     const double& y,
+                   const double& width, const double& height,
+                   const double& c1,    const double& c2,
+                   const double& c3,    const double& c4,
+                   const double& roughness  = 3.0,
+                   const rgb_t   colormap[] = 0)
+{
+   // Note: c1,c2,c3,c4 -> [0.0,1.0]
+
+   const double half_width  = ( width / 2.0);
+   const double half_height = (height / 2.0);
+
+   if ((width >= 1.0) || (height >= 1.0))
+   {
+      const double corner1 = (c1 + c2) / 2.0;
+      const double corner2 = (c2 + c3) / 2.0;
+      const double corner3 = (c3 + c4) / 2.0;
+      const double corner4 = (c4 + c1) / 2.0;
+            double center  = (c1 + c2 + c3 + c4) / 4.0 +
+                             ((1.0 * ::rand() /(1.0 * RAND_MAX))  - 0.5) * // should use a better rng
+                             ((1.0 * half_width + half_height) / (image.width() + image.height()) * roughness);
+
+      center = std::min<double>(std::max<double>(0.0,center),1.0);
+
+      plasma(image, x,                            y, half_width, half_height,      c1, corner1,  center, corner4,roughness,colormap);
+      plasma(image, x + half_width,               y, half_width, half_height, corner1,      c2, corner2,  center,roughness,colormap);
+      plasma(image, x + half_width, y + half_height, half_width, half_height,  center, corner2,      c3, corner3,roughness,colormap);
+      plasma(image, x,              y + half_height, half_width, half_height, corner4,  center, corner3,      c4,roughness,colormap);
+   }
+   else
+   {
+      rgb_t color = colormap[static_cast<unsigned int>(1000.0 * ((c1 + c2 + c3 + c4) / 4.0)) % 1000];
+
+      image.set_pixel(static_cast<unsigned int>(x),static_cast<unsigned int>(y),color);
+   }
+}
+
+inline void plasma(bitmap_image& image,
+                   const double& c1, const double& c2,
+                   const double& c3, const double& c4,
+                   const double& roughness  = 3.0,
+                   const rgb_t   colormap[] = 0)
+{
+   plasma
+   (
+     image, 0, 0, image.width(), image.height(),
+     c1, c2, c3, c4,
+     roughness, colormap
+   );
+}
+
+inline double psnr_region(const unsigned int& x,      const unsigned int& y,
+                          const unsigned int& width,  const unsigned int& height,
+                          const bitmap_image& image1, const bitmap_image& image2)
+{
+   if (
+        (image1.width()  != image2.width ()) ||
+        (image1.height() != image2.height())
+      )
+   {
+      return 0.0;
+   }
+
+   if ((x + width ) > image1.width() ) { return 0.0; }
+   if ((y + height) > image1.height()) { return 0.0; }
+
+   double mse = 0.0;
+
+   for (unsigned int r = 0; r < height; ++r)
+   {
+      const unsigned char* itr1     = image1.row(r + y) + x * image1.bytes_per_pixel();
+      const unsigned char* itr1_end = itr1 + (width * image1.bytes_per_pixel());
+      const unsigned char* itr2     = image2.row(r + y) + x * image2.bytes_per_pixel();
+
+      while (itr1 != itr1_end)
+      {
+         double v = (static_cast<double>(*itr1) - static_cast<double>(*itr2));
+         mse += v * v;
+         ++itr1;
+         ++itr2;
+      }
+   }
+
+   if (mse <= 0.0000001)
+   {
+      return 1000000.0;
+   }
+   else
+   {
+      mse /= (3.0 * width * height);
+      return 20.0 * std::log10(255.0 / std::sqrt(mse));
+   }
+}
+
+inline void hierarchical_psnr_r(const double& x,     const double& y,
+                                const double& width, const double& height,
+                                const bitmap_image& image1,
+                                      bitmap_image& image2,
+                                const double& threshold,
+                                const rgb_t colormap[])
+{
+   if ((width <= 4.0) || (height <= 4.0))
+   {
+      const double psnr = psnr_region
+                          (
+                            static_cast<unsigned int>(x),
+                            static_cast<unsigned int>(y),
+                            static_cast<unsigned int>(width),
+                            static_cast<unsigned int>(height),
+                            image1, image2
+                          );
+
+      if (psnr < threshold)
+      {
+         rgb_t c = colormap[static_cast<unsigned int>(1000.0 * (1.0 - (psnr / threshold)))];
+
+         image2.set_region
+                (
+                  static_cast<unsigned int>(x),
+                  static_cast<unsigned int>(y),
+                  static_cast<unsigned int>(width + 1),
+                  static_cast<unsigned int>(height + 1),
+                  c.red, c.green, c.blue
+                );
+      }
+   }
+   else
+   {
+      const double half_width  = ( width / 2.0);
+      const double half_height = (height / 2.0);
+
+      hierarchical_psnr_r(x             , y              , half_width, half_height, image1, image2, threshold, colormap);
+      hierarchical_psnr_r(x + half_width, y              , half_width, half_height, image1, image2, threshold, colormap);
+      hierarchical_psnr_r(x + half_width, y + half_height, half_width, half_height, image1, image2, threshold, colormap);
+      hierarchical_psnr_r(x             , y + half_height, half_width, half_height, image1, image2, threshold, colormap);
+   }
+}
+
+inline void hierarchical_psnr(bitmap_image& image1, bitmap_image& image2, const double threshold, const rgb_t colormap[])
+{
+   if (
+        (image1.width()  != image2.width ()) ||
+        (image1.height() != image2.height())
+      )
+   {
+      return;
+   }
+
+   const double psnr = psnr_region
+                       (
+                         0, 0, image1.width(), image1.height(),
+                         image1, image2
+                       );
+
+   if (psnr < threshold)
+   {
+      hierarchical_psnr_r
+      (
+        0, 0, image1.width(), image1.height(),
+        image1, image2,
+        threshold,
+        colormap
+      );
+   }
+}
+
+class image_drawer
+{
+public:
+
+   image_drawer(bitmap_image& image)
+   : image_(image),
+     pen_width_(1),
+     pen_color_red_  (0),
+     pen_color_green_(0),
+     pen_color_blue_ (0)
+   {}
+
+   void rectangle(int x1, int y1, int x2, int y2)
+   {
+      line_segment(x1, y1, x2, y1);
+      line_segment(x2, y1, x2, y2);
+      line_segment(x2, y2, x1, y2);
+      line_segment(x1, y2, x1, y1);
+   }
+
+   void triangle(int x1, int y1, int x2, int y2,int x3, int y3)
+   {
+      line_segment(x1, y1, x2, y2);
+      line_segment(x2, y2, x3, y3);
+      line_segment(x3, y3, x1, y1);
+   }
+
+   void quadix(int x1, int y1, int x2, int y2,int x3, int y3, int x4, int y4)
+   {
+      line_segment(x1, y1, x2, y2);
+      line_segment(x2, y2, x3, y3);
+      line_segment(x3, y3, x4, y4);
+      line_segment(x4, y4, x1, y1);
+   }
+
+   void line_segment(int x1, int y1, int x2, int y2)
+   {
+      int steep = 0;
+      int sx    = ((x2 - x1) > 0) ? 1 : -1;
+      int sy    = ((y2 - y1) > 0) ? 1 : -1;
+      int dx    = abs(x2 - x1);
+      int dy    = abs(y2 - y1);
+
+      if (dy > dx)
+      {
+         std::swap(x1,y1);
+         std::swap(dx,dy);
+         std::swap(sx,sy);
+
+         steep = 1;
+      }
+
+      int e = 2 * dy - dx;
+
+      for (int i = 0; i < dx; ++i)
+      {
+         if (steep)
+            plot_pen_pixel(y1,x1);
+         else
+            plot_pen_pixel(x1,y1);
+
+         while (e >= 0)
+         {
+            y1 += sy;
+            e -= (dx << 1);
+         }
+
+         x1 += sx;
+         e  += (dy << 1);
+      }
+
+      plot_pen_pixel(x2,y2);
+   }
+
+   void horiztonal_line_segment(int x1, int x2, int y)
+   {
+      if (x1 > x2)
+      {
+         std::swap(x1,x2);
+      }
+
+      for (int i = 0; i < (x2 - x1); ++i)
+      {
+         plot_pen_pixel(x1 +  i,y);
+      }
+   }
+
+   void vertical_line_segment(int y1, int y2, int x)
+   {
+      if (y1 > y2)
+      {
+         std::swap(y1,y2);
+      }
+
+      for (int i = 0; i < (y2 - y1); ++i)
+      {
+         plot_pen_pixel(x, y1 +  i);
+      }
+   }
+
+   void ellipse(int centerx, int centery, int a, int b)
+   {
+      int t1 = a * a;
+      int t2 = t1 << 1;
+      int t3 = t2 << 1;
+      int t4 = b * b;
+      int t5 = t4 << 1;
+      int t6 = t5 << 1;
+      int t7 = a * t5;
+      int t8 = t7 << 1;
+      int t9 = 0;
+
+      int d1 = t2 - t7 + (t4 >> 1);
+      int d2 = (t1 >> 1) - t8 + t5;
+      int x  = a;
+      int y  = 0;
+
+      int negative_tx = centerx - x;
+      int positive_tx = centerx + x;
+      int negative_ty = centery - y;
+      int positive_ty = centery + y;
+
+      while (d2 < 0)
+      {
+         plot_pen_pixel(positive_tx, positive_ty);
+         plot_pen_pixel(positive_tx, negative_ty);
+         plot_pen_pixel(negative_tx, positive_ty);
+         plot_pen_pixel(negative_tx, negative_ty);
+
+         ++y;
+
+         t9 = t9 + t3;
+
+         if (d1 < 0)
+         {
+            d1 = d1 + t9 + t2;
+            d2 = d2 + t9;
+         }
+         else
+         {
+            x--;
+            t8 = t8 - t6;
+            d1 = d1 + (t9 + t2 - t8);
+            d2 = d2 + (t9 + t5 - t8);
+            negative_tx = centerx - x;
+            positive_tx = centerx + x;
+         }
+
+         negative_ty = centery - y;
+         positive_ty = centery + y;
+      }
+
+      do
+      {
+         plot_pen_pixel(positive_tx, positive_ty);
+         plot_pen_pixel(positive_tx, negative_ty);
+         plot_pen_pixel(negative_tx, positive_ty);
+         plot_pen_pixel(negative_tx, negative_ty);
+
+         x--;
+         t8 = t8 - t6;
+
+         if (d2 < 0)
+         {
+            ++y;
+            t9 = t9 + t3;
+            d2 = d2 + (t9 + t5 - t8);
+            negative_ty = centery - y;
+            positive_ty = centery + y;
+         }
+         else
+            d2 = d2 + (t5 - t8);
+
+         negative_tx = centerx - x;
+         positive_tx = centerx + x;
+      }
+      while (x >= 0);
+   }
+
+   void circle(int centerx, int centery, int radius)
+   {
+      int x = 0;
+      int d = (1 - radius) << 1;
+
+      while (radius >= 0)
+      {
+         plot_pen_pixel(centerx + x, centery + radius);
+         plot_pen_pixel(centerx + x, centery - radius);
+         plot_pen_pixel(centerx - x, centery + radius);
+         plot_pen_pixel(centerx - x, centery - radius);
+
+         if ((d + radius) > 0)
+            d -= ((--radius) << 1) - 1;
+         if (x > d)
+            d += ((++x) << 1) + 1;
+      }
+   }
+
+   void plot_pen_pixel(int x, int y)
+   {
+      switch (pen_width_)
+      {
+         case 1  : plot_pixel(x,y);
+                   break;
+
+         case 2  : {
+                      plot_pixel(x    , y    );
+                      plot_pixel(x + 1, y    );
+                      plot_pixel(x + 1, y + 1);
+                      plot_pixel(x    , y + 1);
+                   }
+                   break;
+
+         case  3 : {
+                      plot_pixel(x    , y - 1);
+                      plot_pixel(x - 1, y - 1);
+                      plot_pixel(x + 1, y - 1);
+
+                      plot_pixel(x    , y    );
+                      plot_pixel(x - 1, y    );
+                      plot_pixel(x + 1, y    );
+
+                      plot_pixel(x    , y + 1);
+                      plot_pixel(x - 1, y + 1);
+                      plot_pixel(x + 1, y + 1);
+                   }
+                   break;
+
+         default : plot_pixel(x,y);
+                   break;
+      }
+   }
+
+   void plot_pixel(int x, int y)
+   {
+      if (
+           (x < 0) ||
+           (y < 0) ||
+           (x >= static_cast<int>(image_.width ())) ||
+           (y >= static_cast<int>(image_.height()))
+         )
+         return;
+
+      image_.set_pixel(x,y,pen_color_red_,pen_color_green_,pen_color_blue_);
+   }
+
+   void pen_width(const unsigned int& width)
+   {
+      if ((width > 0) && (width < 4))
+      {
+         pen_width_ = width;
+      }
+   }
+
+   void pen_color(const unsigned char& red,
+                  const unsigned char& green,
+                  const unsigned char& blue)
+   {
+      pen_color_red_   = red;
+      pen_color_green_ = green;
+      pen_color_blue_  = blue;
+   }
+
+   template <typename RGB>
+   void pen_color(const RGB colour)
+   {
+      pen_color_red_   = colour.red;
+      pen_color_green_ = colour.green;
+      pen_color_blue_  = colour.blue;
+   }
+
+private:
+
+   image_drawer(const image_drawer& id);
+   image_drawer& operator =(const image_drawer& id);
+
+   bitmap_image& image_;
+   unsigned int  pen_width_;
+   unsigned char pen_color_red_;
+   unsigned char pen_color_green_;
+   unsigned char pen_color_blue_;
+};
+
+class cartesian_canvas
+{
+public:
+
+   cartesian_canvas(const double x_length, const double y_length)
+   : width_div2_ (0.0),
+     height_div2_(0.0),
+     min_x_      (0.0),
+     min_y_      (0.0),
+     max_x_      (0.0),
+     max_y_      (0.0),
+     draw_       (image_)
+   {
+      setup_canvas(x_length,y_length);
+   }
+
+   inline bool operator!()
+   {
+      return !image_;
+   }
+
+   void rectangle(double x1, double y1, double x2, double y2)
+   {
+      line_segment(x1, y1, x2, y1);
+      line_segment(x2, y1, x2, y2);
+      line_segment(x2, y2, x1, y2);
+      line_segment(x1, y2, x1, y1);
+   }
+
+   void triangle(double x1, double y1, double x2, double y2, double x3, double y3)
+   {
+      line_segment(x1, y1, x2, y2);
+      line_segment(x2, y2, x3, y3);
+      line_segment(x3, y3, x1, y1);
+   }
+
+   void quadix(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
+   {
+      line_segment(x1, y1, x2, y2);
+      line_segment(x2, y2, x3, y3);
+      line_segment(x3, y3, x4, y4);
+      line_segment(x4, y4, x1, y1);
+   }
+
+   void line_segment(double x1, double y1, double x2, double y2)
+   {
+      if (clip(x1, y1, x2, y2))
+      {
+         const int sc_x1 = static_cast<int>(cart_to_screen_x(x1));
+         const int sc_x2 = static_cast<int>(cart_to_screen_x(x2));
+         const int sc_y1 = static_cast<int>(cart_to_screen_y(y1));
+         const int sc_y2 = static_cast<int>(cart_to_screen_y(y2));
+
+         draw_.line_segment(sc_x1, sc_y1, sc_x2, sc_y2);
+      }
+   }
+
+   void horiztonal_line_segment(double x1, double x2, double y)
+   {
+      x1 = clamp_x(x1);
+      x2 = clamp_x(x2);
+      y  = clamp_y( y);
+
+      const int sc_x1 = static_cast<int>(cart_to_screen_x(x1));
+      const int sc_x2 = static_cast<int>(cart_to_screen_x(x2));
+      const int sc_y  = static_cast<int>(cart_to_screen_y(y ));
+
+      draw_.horiztonal_line_segment(sc_x1, sc_x2, sc_y);
+   }
+
+   void vertical_line_segment(double y1, double y2, double x)
+   {
+      y1 = clamp_y(y1);
+      y2 = clamp_y(y2);
+      x  = clamp_x( x);
+
+      const int sc_y1 = static_cast<int>(cart_to_screen_y(y1));
+      const int sc_y2 = static_cast<int>(cart_to_screen_y(y2));
+      const int sc_x  = static_cast<int>(cart_to_screen_x(x ));
+
+      draw_.vertical_line_segment(sc_y1, sc_y2, sc_x);
+   }
+
+   void ellipse(double centerx, double centery, double a, double b)
+   {
+
+      const int sc_cx = static_cast<int>(cart_to_screen_x(centerx));
+      const int sc_cy = static_cast<int>(cart_to_screen_y(centery));
+
+      draw_.ellipse(sc_cx, sc_cy, static_cast<int>(a), static_cast<int>(b));
+   }
+
+   void circle(double centerx, double centery, double radius)
+   {
+      const int sc_cx = static_cast<int>(cart_to_screen_x(centerx));
+      const int sc_cy = static_cast<int>(cart_to_screen_y(centery));
+
+      draw_.circle(sc_cx, sc_cy, static_cast<int>(radius));
+   }
+
+   void fill_rectangle(double x1, double y1, double x2, double y2)
+   {
+      if (y1 > y2)
+         std::swap(y1, y2);
+
+      for (double y = y1; y <= y2; y += 0.5)
+      {
+        line_segment(x1, y, x2, y);
+      }
+   }
+
+   void fill_triangle(double x1, double y1, double x2, double y2, double x3, double y3)
+   {
+      typedef std::pair<double,double> point_t;
+
+      std::vector<point_t> p;
+
+      p.push_back(std::make_pair(x1,y1));
+      p.push_back(std::make_pair(x2,y2));
+      p.push_back(std::make_pair(x3,y3));
+
+      if (p[0].second > p[1].second)
+         std::swap(p[0],p[1]);
+      if (p[0].second > p[2].second)
+         std::swap(p[0],p[2]);
+      if (p[1].second > p[2].second)
+         std::swap(p[1],p[2]);
+
+      class draw_modes
+      {
+      private:
+
+         cartesian_canvas& canvas;
+
+         // Needed for incompetent and broken msvc compiler versions
+         #ifdef _MSC_VER
+            #pragma warning(push)
+            #pragma warning(disable: 4822)
+         #endif
+         draw_modes& operator=(const draw_modes&);
+         #ifdef _MSC_VER
+            #pragma warning(pop)
+         #endif
+
+      public:
+
+         draw_modes(cartesian_canvas& c)
+         : canvas(c)
+         {}
+
+         void bottom(const point_t& p0, const point_t& p1, const point_t& p2)
+         {
+            const double m0 = (p1.first - p0.first) / (2.0 * (p1.second - p0.second));
+            const double m1 = (p2.first - p0.first) / (2.0 * (p2.second - p0.second));
+
+            double x0 = p0.first;
+            double x1 = p0.first;
+
+            for (double y = p0.second; y <= p1.second; y += 0.5)
+            {
+               canvas.horiztonal_line_segment(x0, x1, y);
+
+               x0 += m0;
+               x1 += m1;
+            }
+         }
+
+         void top(const point_t& p0, const point_t& p1, const point_t& p2)
+         {
+            const double m0 = (p2.first - p0.first) / (2.0 * (p2.second - p0.second));
+            const double m1 = (p2.first - p1.first) / (2.0 * (p2.second - p1.second));
+
+            double x0 = p2.first;
+            double x1 = p2.first;
+
+            for (double y = p2.second; y >= p0.second; y -= 0.5)
+            {
+               canvas.horiztonal_line_segment(x0, x1, y);
+
+               x0 -= m0;
+               x1 -= m1;
+            }
+         }
+      };
+
+      draw_modes dm(*this);
+
+      const double eps = 0.00001;
+
+      if (std::abs(p[1].second - p[2].second) < eps)
+         dm.bottom(p[0], p[1], p[2]);
+      else if (std::abs(p[0].second - p[1].second) < eps)
+         dm.top(p[0], p[1], p[2]);
+      else
+      {
+         point_t p3;
+
+         p3.first  = (p[0].first + ((p[1].second - p[0].second) / (p[2].second - p[0].second)) * (p[2].first - p[0].first));
+         p3.second = p[1].second;
+
+         dm.bottom(p[0], p[1], p3  );
+         dm.top   (p[1], p3  , p[2]);
+      }
+   }
+
+   void fill_quadix(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
+   {
+      fill_triangle(x1, y1, x2, y2, x3, y3);
+      fill_triangle(x1, y1, x3, y3, x4, y4);
+   }
+
+   void fill_circle(double cx, double cy, double radius)
+   {
+      const double delta = 1.0;
+      double  x = radius;
+      double  y = 0.0;
+      double dx = delta - (2.0 * delta * radius);
+      double dy = 0.0;
+      double dr = 0.0;
+
+      while (x >= y)
+      {
+         for (double i = cx - x; i <= cx + x; i += delta)
+         {
+            horiztonal_line_segment(cx - x, cx + x, cy + y);
+            horiztonal_line_segment(cx - x, cx + x, cy - y);
+         }
+
+         for (double i = cx - y; i <= cx + y; i += delta)
+         {
+            horiztonal_line_segment(cx - y, cx + y, cy + x);
+            horiztonal_line_segment(cx - y, cx + y, cy - x);
+         }
+
+         y += delta;
+
+         dr += dy;
+         dy += 2.0 * delta;
+
+         if ((2.0 * delta * dr + dx) > 0)
+         {
+             x -= delta;
+            dr +=  dx;
+            dx += 2.0 * delta;
+         }
+      }
+   }
+
+   void plot_pen_pixel(double x, double y)
+   {
+      if ((x < min_x_) || (x > max_x_)) return;
+      if ((y < min_y_) || (y > max_y_)) return;
+
+      const int sc_x = static_cast<int>(cart_to_screen_x(x));
+      const int sc_y = static_cast<int>(cart_to_screen_y(y));
+
+      draw_.plot_pen_pixel(sc_x, sc_y);
+   }
+
+   void plot_pixel(double x, double y)
+   {
+      if ((x < min_x_) || (x > max_x_)) return;
+      if ((y < min_y_) || (y > max_y_)) return;
+
+      const int sc_x = static_cast<int>(cart_to_screen_x(x));
+      const int sc_y = static_cast<int>(cart_to_screen_y(y));
+
+      draw_.plot_pixel(sc_x, sc_y);
+   }
+
+   void pen_width(const unsigned int& width)
+   {
+      draw_.pen_width(width);
+   }
+
+   void pen_color(const unsigned char&   red,
+                  const unsigned char& green,
+                  const unsigned char&  blue)
+   {
+      draw_.pen_color(red,green,blue);
+   }
+
+   template <typename RGB>
+   void pen_color(const RGB colour)
+   {
+      draw_.pen_color(colour);
+   }
+
+   const bitmap_image& image() const
+   {
+      return image_;
+   }
+
+   bitmap_image& image()
+   {
+      return image_;
+   }
+
+   void set_widthheight(const double x_length, const double y_length)
+   {
+      setup_canvas(x_length, y_length);
+   }
+
+   double min_x() const { return min_x_; }
+   double min_y() const { return min_y_; }
+   double max_x() const { return max_x_; }
+   double max_y() const { return max_y_; }
+
+private:
+
+   void setup_canvas(const double x_length, const double y_length)
+   {
+      if ((x_length < 2.0) || (y_length < 2.0))
+         return;
+
+      width_div2_  = x_length / 2.0;
+      height_div2_ = y_length / 2.0;
+
+      min_x_ = -width_div2_ ;
+      min_y_ = -height_div2_;
+      max_x_ =  width_div2_ ;
+      max_y_ =  height_div2_;
+
+      image_.setwidth_height(static_cast<unsigned int>(x_length) + 1, static_cast<unsigned int>(y_length) + 1);
+
+      image_.clear(0xFF);
+   }
+
+   double clamp_x(const double& x)
+   {
+           if (x < min_x_)  return min_x_;
+      else if (x > max_x_)  return max_x_;
+      else                  return x;
+   }
+
+   double clamp_y(const double& y)
+   {
+           if (y < min_y_)  return min_y_;
+      else if (y > max_y_)  return max_y_;
+      else                  return y;
+   }
+
+   double cart_to_screen_x(const double& x)
+   {
+      return x + width_div2_;
+   }
+
+   double cart_to_screen_y(const double& y)
+   {
+      return height_div2_ - y;
+   }
+
+   enum clip_code
+   {
+      e_clip_bottom = 1,
+      e_clip_top    = 2,
+      e_clip_left   = 4,
+      e_clip_right  = 8
+   };
+
+   int out_code(
+                 const double&  x, const double&  y,
+                 const double& x1, const double& y1,
+                 const double& x2, const double& y2
+               )
+   {
+      int result = 0;
+      if (y < y1)      result |= e_clip_bottom;
+      else if (y > y2) result |= e_clip_top;
+
+      if (x < x1)      result |= e_clip_left;
+      else if (x > x2) result |= e_clip_right;
+
+      return result;
+   }
+
+   bool clip(double& x1, double& y1, double& x2, double& y2)
+   {
+      bool   result = false;
+      double x      = 0.0;
+      double y      = 0.0;
+
+      int outcode0   = out_code(x1, y1, min_x_, min_y_, max_x_, max_y_);
+      int outcode1   = out_code(x2, y2, min_x_, min_y_, max_x_, max_y_);
+      int outcodeout = 0;
+
+      while ((outcode0 != 0) || (outcode1 != 0))
+      {
+         if ((outcode0 & outcode1) != 0)
+            return result;
+         else
+         {
+            if (outcode0 != 0)
+               outcodeout = outcode0;
+            else
+               outcodeout = outcode1;
+
+            double dx = (x2 - x1);
+            double dy = (y2 - y1);
+
+            if ((outcodeout & e_clip_bottom) == e_clip_bottom)
+            {
+               x = x1 + dx * (min_y_ - y1) / dy;
+               y = min_y_;
+            }
+            else if ((outcodeout & e_clip_top) == e_clip_top)
+            {
+               x = x1 + dx * (max_y_ - y1) / dy;
+               y = max_y_;
+            }
+            else if ((outcodeout & e_clip_right) == e_clip_right)
+            {
+               y = y1 + dy * (max_x_ - x1) / dx;
+               x = max_x_;
+            }
+            else if ((outcodeout & e_clip_left) == e_clip_left)
+            {
+               y = y1 + dy * (min_x_ - x1) / dx;
+               x = min_x_;
+            }
+
+            if (outcodeout == outcode0)
+            {
+               x1 = x;
+               y1 = y;
+               outcode0 = out_code(x1, y1, min_x_, min_y_, max_x_, max_y_);
+            }
+            else
+            {
+               x2 = x;
+               y2 = y;
+               outcode1 = out_code(x2, y2, min_x_, min_y_, max_x_, max_y_);
+            }
+         }
+      }
+
+      return true;
+   }
+
+   cartesian_canvas(const cartesian_canvas&);
+   cartesian_canvas operator=(const cartesian_canvas&);
+
+   double width_div2_;
+   double height_div2_;
+   double min_x_;
+   double min_y_;
+   double max_x_;
+   double max_y_;
+   bitmap_image image_;
+   image_drawer draw_;
+};
+
+inline rgb_t convert_wave_length_nm_to_rgb(const double wave_length_nm)
+{
+   // Credits: Dan Bruton http://www.physics.sfasu.edu/astro/color.html
+   double red   = 0.0;
+   double green = 0.0;
+   double blue  = 0.0;
+
+   if ((380.0 <= wave_length_nm) && (wave_length_nm <= 439.0))
+   {
+      red   = -(wave_length_nm - 440.0) / (440.0 - 380.0);
+      green = 0.0;
+      blue  = 1.0;
+   }
+   else if ((440.0 <= wave_length_nm) && (wave_length_nm <= 489.0))
+   {
+      red   = 0.0;
+      green = (wave_length_nm - 440.0) / (490.0 - 440.0);
+      blue  = 1.0;
+   }
+   else if ((490.0 <= wave_length_nm) && (wave_length_nm <= 509.0))
+   {
+      red   = 0.0;
+      green = 1.0;
+      blue  = -(wave_length_nm - 510.0) / (510.0 - 490.0);
+   }
+   else if ((510.0 <= wave_length_nm) && (wave_length_nm <= 579.0))
+   {
+      red   = (wave_length_nm - 510.0) / (580.0 - 510.0);
+      green = 1.0;
+      blue  = 0.0;
+   }
+   else if ((580.0 <= wave_length_nm) && (wave_length_nm <= 644.0))
+   {
+      red   = 1.0;
+      green = -(wave_length_nm - 645.0) / (645.0 - 580.0);
+      blue  = 0.0;
+   }
+   else if ((645.0 <= wave_length_nm) && (wave_length_nm <= 780.0))
+   {
+      red   = 1.0;
+      green = 0.0;
+      blue  = 0.0;
+   }
+
+   double factor = 0.0;
+
+   if ((380.0 <= wave_length_nm) && (wave_length_nm <= 419.0))
+      factor = 0.3 + 0.7 * (wave_length_nm - 380.0) / (420.0 - 380.0);
+   else if ((420.0 <= wave_length_nm) && (wave_length_nm <= 700.0))
+      factor = 1.0;
+   else if ((701.0 <= wave_length_nm) && (wave_length_nm <= 780.0))
+      factor = 0.3 + 0.7 * (780.0 - wave_length_nm) / (780.0 - 700.0);
+   else
+      factor = 0.0;
+
+   rgb_t result;
+
+   const double gamma         =   0.8;
+   const double intensity_max = 255.0;
+
+   #define round(d) std::floor(d + 0.5)
+
+   result.red   = static_cast<unsigned char>((red   == 0.0) ? red   : round(intensity_max * std::pow(red   * factor, gamma)));
+   result.green = static_cast<unsigned char>((green == 0.0) ? green : round(intensity_max * std::pow(green * factor, gamma)));
+   result.blue  = static_cast<unsigned char>((blue  == 0.0) ? blue  : round(intensity_max * std::pow(blue  * factor, gamma)));
+
+   #undef round
+
+   return result;
+}
+
+inline double weighted_distance(const unsigned char r0, const unsigned char g0, const unsigned char b0,
+                                const unsigned char r1, const unsigned char g1, const unsigned char b1)
+{
+   const double diff_r = /*0.30 */ (r0 - r1);
+   const double diff_g = /*0.59 */ (g0 - g1);
+   const double diff_b = /*0.11 */ (b0 - b1);
+
+   return std::sqrt((diff_r * diff_r) + (diff_g * diff_g) + (diff_b * diff_b));
+}
+
+inline double weighted_distance(const rgb_t c0, const rgb_t c1)
+{
+   return weighted_distance(c0.red, c0.green, c0.blue,
+                            c1.red, c1.green, c1.blue);
+}
+
+template <typename Iterator>
+inline rgb_t find_nearest_color(const rgb_t& c, const Iterator begin, const Iterator end)
+{
+   if (0 == std::distance(begin,end))
+      return c;
+
+   double min_d = std::numeric_limits<double>::max();
+   rgb_t result = *begin;
+
+   for (Iterator itr = begin; itr != end; ++itr)
+   {
+      if (c == (*itr))
+      {
+         return (*itr);
+      }
+
+      double curr_d = weighted_distance(c,*itr);
+
+      if (curr_d < min_d)
+      {
+          min_d = curr_d;
+         result = *itr;
+      }
+   }
+
+   return result;
+}
+
+template <template <typename,typename> class Sequence,
+          typename Allocator>
+inline rgb_t find_nearest_color(const rgb_t& c, const Sequence<rgb_t,Allocator>& seq)
+{
+   return find_nearest_color(c, seq.begin(),seq.end());
+}
+
+template <std::size_t N>
+inline rgb_t find_nearest_color(const rgb_t& c, const rgb_t (&colors)[N])
+{
+   return find_nearest_color(c, colors, colors + N);
+}
+
+inline double find_nearest_wave_length(const rgb_t& c, const double increment = 0.001)
+{
+   const double max_wave_length = 800.0; //800nm
+
+   double min_wave_length = 0.0;
+   double min_d           = std::numeric_limits<double>::max();
+
+   for (double i = 0.0; i < max_wave_length; i += increment)
+   {
+      const rgb_t  curr_rgb = convert_wave_length_nm_to_rgb(i);
+
+      if (c == curr_rgb)
+      {
+         return i;
+      }
+
+      const double curr_d = weighted_distance(c, curr_rgb);
+
+      if (curr_d <= min_d)
+      {
+         min_wave_length = i;
+         min_d = curr_d;
+      }
+   }
+
+   return min_wave_length;
+}
+
+template <typename T>
+class response_image
+{
+public:
+
+   response_image(const std::size_t& width, const std::size_t& height, const T null = T(0))
+   : width_ (width ),
+     height_(height),
+     null_  (null  )
+   {
+      data_.resize(width_ * height_);
+   }
+
+   std::size_t width () const { return  width_; }
+   std::size_t height() const { return height_; }
+
+   void set_all(const T& t)
+   {
+      std::fill_n(data_.begin(), data_.size(), t);
+   }
+
+   const T& operator()(const std::size_t& x, const std::size_t& y) const
+   {
+      if (y >= height_) return null_;
+      if (x >= width_ ) return null_;
+
+      return data_[width_ * y + x];
+   }
+
+   T& operator()(const std::size_t& x, const std::size_t& y)
+   {
+      if (y >= height_) return null_;
+      if (x >= width_ ) return null_;
+
+      return data_[width_ * y + x];
+   }
+
+   bool valid(const std::size_t& x, const std::size_t& y)
+   {
+      return ((x < width_ ) || (y < height_));
+   }
+
+   void inc_all(const T& v)
+   {
+      for (std::size_t i = 0; i < data_.size(); ++i)
+      {
+         data_[i] += v;
+      }
+   }
+
+   void mul_all(const T& v)
+   {
+      for (std::size_t i = 0; i < data_.size(); ++i)
+      {
+         data_[i] *= v;
+      }
+   }
+
+   T* row (const std::size_t& row_index)
+   {
+      if (row_index < height_)
+         return &data_[width_ * row_index];
+      else
+         return reinterpret_cast<T*>(0);
+   }
+
+   const T* row (const std::size_t& row_index) const
+   {
+      if (row_index < height_)
+         return data_[width_ * row_index];
+      else
+         return reinterpret_cast<T*>(0);
+   }
+
+private:
+
+   std::size_t    width_;
+   std::size_t    height_;
+   std::vector<T> data_;
+   T              null_;
+};
+
+inline void sobel_operator(const bitmap_image& src_image,
+                                 bitmap_image& dst_image,
+                           const double threshold = 0.0)
+{
+   typedef double T;
+
+   response_image<T> im0(src_image.width(), src_image.height(), 0.0);
+   response_image<T> im1(src_image.width(), src_image.height(), 0.0);
+
+   src_image.export_gray_scale_response_image(&im0(0,0));
+
+   for (std::size_t y = 1; y < im0.height() - 1; ++y)
+   {
+      const T* itr0 = im0.row(y - 1);
+      const T* itr1 = im0.row(y    );
+      const T* itr2 = im0.row(y + 1);
+            T* out  = im1.row(y    ) + 1;
+
+      for (std::size_t x = 1; x < im0.width() - 1; ++x)
+      {
+         const T c0 = *(itr0 + x - 1);   const T c1 = *(itr0 + x);   const T c2 = *(itr0 + x + 1);
+         const T c3 = *(itr1 + x - 1); /*const T c4 = *(itr1 + x);*/ const T c5 = *(itr1 + x + 1);
+         const T c6 = *(itr2 + x - 1);   const T c7 = *(itr2 + x);   const T c8 = *(itr2 + x + 1);
+
+         const T gx = (2.0 * (c5 - c3)) + (c2 - c0) + (c8 - c6);
+         const T gy = (2.0 * (c1 - c7)) + (c0 - c6) + (c2 - c8);
+
+         *(out++) = std::sqrt((gx * gx) + (gy * gy));
+      }
+   }
+
+   if (threshold > 0.0)
+   {
+      const T* end = im1.row(0) + (im1.width() * im1.height());
+
+      for (T* itr = im1.row(0); itr != end; ++itr)
+      {
+         T& v = *itr;
+         if (v <= threshold) v = 0;
+      }
+   }
+
+   dst_image.setwidth_height
+             (
+               static_cast<unsigned int>(im1.width()),
+               static_cast<unsigned int>(im1.height())
+             );
+
+   dst_image.import_gray_scale_clamped(&im1(0,0));
+}
+
+enum palette_name
+{
+   e_red,           e_scarlet,      e_vermilion,        e_tangelo,         e_orange,
+   e_gamboge,       e_amber,        e_gold,             e_yellow,          e_apple_green,
+   e_lime_green,    e_spring_bud,   e_chartreuse_green, e_pistachio,       e_harlequin,
+   e_sap_green,     e_green,        e_emerald_green,    e_malachite_green, e_sea_green,
+   e_spring_green,  e_aquamarine,   e_turquoise,        e_opal,            e_cyan,
+   e_arctic_blue,   e_cerulean,     e_cornflower_blue,  e_azure,           e_cobalt_blue,
+   e_sapphire_blue, e_phthalo_blue, e_blue,             e_persian_blue,    e_indigo,
+   e_blue_violet,   e_violet,       e_purple,           e_mulberry,        e_heliotrope,
+   e_magenta,       e_orchid,       e_fuchsia,          e_cerise,          e_rose,
+   e_raspberry,     e_crimson,      e_amaranth,         e_white,           e_black
+};
+
+const rgb_t palette_colormap[] = {
+   {255,   0,   0}, {255,  31,   0}, {255,  63,   0}, {255,  95,   0}, {255, 127,   0},
+   {255, 159,   0}, {255, 191,   0}, {255, 223,   0}, {255, 255,   0}, {223, 255,   0},
+   {191, 255,   0}, {159, 255,   0}, {127, 255,   0}, { 95, 255,   0}, { 63, 255,   0},
+   { 31, 255,   0}, {  0, 255,   0}, {  0, 255,  31}, {  0, 255,  63}, {  0, 255,  95},
+   {  0, 255, 127}, {  0, 255, 159}, {  0, 255, 191}, {  0, 255, 223}, {  0, 255, 255},
+   {  0, 223, 255}, {  0, 191, 255}, {  0, 159, 255}, {  0, 127, 255}, {  0,  95, 255},
+   {  0,  63, 255}, {  0,  31, 255}, {  0,   0, 255}, { 31,   0, 255}, { 63,   0, 255},
+   { 95,   0, 255}, {127,   0, 255}, {159,   0, 255}, {191,   0, 255}, {223,   0, 255},
+   {255,   0, 255}, {255,   0, 223}, {255,   0, 191}, {255,   0, 159}, {255,   0, 127},
+   {255,   0,  95}, {255,   0,  63}, {255,   0,  31}, {255, 255, 255}, {  0,   0,   0}
+};
+
+const rgb_t autumn_colormap[1000] = {
+   {255,   0,   0}, {255,   0,   0}, {255,   1,   0}, {255,   1,   0}, {255,   1,   0},
+   {255,   1,   0}, {255,   2,   0}, {255,   2,   0}, {255,   2,   0}, {255,   2,   0},
+   {255,   3,   0}, {255,   3,   0}, {255,   3,   0}, {255,   3,   0}, {255,   4,   0},
+   {255,   4,   0}, {255,   4,   0}, {255,   4,   0}, {255,   5,   0}, {255,   5,   0},
+   {255,   5,   0}, {255,   5,   0}, {255,   6,   0}, {255,   6,   0}, {255,   6,   0},
+   {255,   6,   0}, {255,   7,   0}, {255,   7,   0}, {255,   7,   0}, {255,   7,   0},
+   {255,   8,   0}, {255,   8,   0}, {255,   8,   0}, {255,   8,   0}, {255,   9,   0},
+   {255,   9,   0}, {255,   9,   0}, {255,   9,   0}, {255,  10,   0}, {255,  10,   0},
+   {255,  10,   0}, {255,  10,   0}, {255,  11,   0}, {255,  11,   0}, {255,  11,   0},
+   {255,  11,   0}, {255,  12,   0}, {255,  12,   0}, {255,  12,   0}, {255,  13,   0},
+   {255,  13,   0}, {255,  13,   0}, {255,  13,   0}, {255,  14,   0}, {255,  14,   0},
+   {255,  14,   0}, {255,  14,   0}, {255,  15,   0}, {255,  15,   0}, {255,  15,   0},
+   {255,  15,   0}, {255,  16,   0}, {255,  16,   0}, {255,  16,   0}, {255,  16,   0},
+   {255,  17,   0}, {255,  17,   0}, {255,  17,   0}, {255,  17,   0}, {255,  18,   0},
+   {255,  18,   0}, {255,  18,   0}, {255,  18,   0}, {255,  19,   0}, {255,  19,   0},
+   {255,  19,   0}, {255,  19,   0}, {255,  20,   0}, {255,  20,   0}, {255,  20,   0},
+   {255,  20,   0}, {255,  21,   0}, {255,  21,   0}, {255,  21,   0}, {255,  21,   0},
+   {255,  22,   0}, {255,  22,   0}, {255,  22,   0}, {255,  22,   0}, {255,  23,   0},
+   {255,  23,   0}, {255,  23,   0}, {255,  23,   0}, {255,  24,   0}, {255,  24,   0},
+   {255,  24,   0}, {255,  25,   0}, {255,  25,   0}, {255,  25,   0}, {255,  25,   0},
+   {255,  26,   0}, {255,  26,   0}, {255,  26,   0}, {255,  26,   0}, {255,  27,   0},
+   {255,  27,   0}, {255,  27,   0}, {255,  27,   0}, {255,  28,   0}, {255,  28,   0},
+   {255,  28,   0}, {255,  28,   0}, {255,  29,   0}, {255,  29,   0}, {255,  29,   0},
+   {255,  29,   0}, {255,  30,   0}, {255,  30,   0}, {255,  30,   0}, {255,  30,   0},
+   {255,  31,   0}, {255,  31,   0}, {255,  31,   0}, {255,  31,   0}, {255,  32,   0},
+   {255,  32,   0}, {255,  32,   0}, {255,  32,   0}, {255,  33,   0}, {255,  33,   0},
+   {255,  33,   0}, {255,  33,   0}, {255,  34,   0}, {255,  34,   0}, {255,  34,   0},
+   {255,  34,   0}, {255,  35,   0}, {255,  35,   0}, {255,  35,   0}, {255,  35,   0},
+   {255,  36,   0}, {255,  36,   0}, {255,  36,   0}, {255,  37,   0}, {255,  37,   0},
+   {255,  37,   0}, {255,  37,   0}, {255,  38,   0}, {255,  38,   0}, {255,  38,   0},
+   {255,  38,   0}, {255,  39,   0}, {255,  39,   0}, {255,  39,   0}, {255,  39,   0},
+   {255,  40,   0}, {255,  40,   0}, {255,  40,   0}, {255,  40,   0}, {255,  41,   0},
+   {255,  41,   0}, {255,  41,   0}, {255,  41,   0}, {255,  42,   0}, {255,  42,   0},
+   {255,  42,   0}, {255,  42,   0}, {255,  43,   0}, {255,  43,   0}, {255,  43,   0},
+   {255,  43,   0}, {255,  44,   0}, {255,  44,   0}, {255,  44,   0}, {255,  44,   0},
+   {255,  45,   0}, {255,  45,   0}, {255,  45,   0}, {255,  45,   0}, {255,  46,   0},
+   {255,  46,   0}, {255,  46,   0}, {255,  46,   0}, {255,  47,   0}, {255,  47,   0},
+   {255,  47,   0}, {255,  47,   0}, {255,  48,   0}, {255,  48,   0}, {255,  48,   0},
+   {255,  48,   0}, {255,  49,   0}, {255,  49,   0}, {255,  49,   0}, {255,  50,   0},
+   {255,  50,   0}, {255,  50,   0}, {255,  50,   0}, {255,  51,   0}, {255,  51,   0},
+   {255,  51,   0}, {255,  51,   0}, {255,  52,   0}, {255,  52,   0}, {255,  52,   0},
+   {255,  52,   0}, {255,  53,   0}, {255,  53,   0}, {255,  53,   0}, {255,  53,   0},
+   {255,  54,   0}, {255,  54,   0}, {255,  54,   0}, {255,  54,   0}, {255,  55,   0},
+   {255,  55,   0}, {255,  55,   0}, {255,  55,   0}, {255,  56,   0}, {255,  56,   0},
+   {255,  56,   0}, {255,  56,   0}, {255,  57,   0}, {255,  57,   0}, {255,  57,   0},
+   {255,  57,   0}, {255,  58,   0}, {255,  58,   0}, {255,  58,   0}, {255,  58,   0},
+   {255,  59,   0}, {255,  59,   0}, {255,  59,   0}, {255,  59,   0}, {255,  60,   0},
+   {255,  60,   0}, {255,  60,   0}, {255,  60,   0}, {255,  61,   0}, {255,  61,   0},
+   {255,  61,   0}, {255,  62,   0}, {255,  62,   0}, {255,  62,   0}, {255,  62,   0},
+   {255,  63,   0}, {255,  63,   0}, {255,  63,   0}, {255,  63,   0}, {255,  64,   0},
+   {255,  64,   0}, {255,  64,   0}, {255,  64,   0}, {255,  65,   0}, {255,  65,   0},
+   {255,  65,   0}, {255,  65,   0}, {255,  66,   0}, {255,  66,   0}, {255,  66,   0},
+   {255,  66,   0}, {255,  67,   0}, {255,  67,   0}, {255,  67,   0}, {255,  67,   0},
+   {255,  68,   0}, {255,  68,   0}, {255,  68,   0}, {255,  68,   0}, {255,  69,   0},
+   {255,  69,   0}, {255,  69,   0}, {255,  69,   0}, {255,  70,   0}, {255,  70,   0},
+   {255,  70,   0}, {255,  70,   0}, {255,  71,   0}, {255,  71,   0}, {255,  71,   0},
+   {255,  71,   0}, {255,  72,   0}, {255,  72,   0}, {255,  72,   0}, {255,  72,   0},
+   {255,  73,   0}, {255,  73,   0}, {255,  73,   0}, {255,  74,   0}, {255,  74,   0},
+   {255,  74,   0}, {255,  74,   0}, {255,  75,   0}, {255,  75,   0}, {255,  75,   0},
+   {255,  75,   0}, {255,  76,   0}, {255,  76,   0}, {255,  76,   0}, {255,  76,   0},
+   {255,  77,   0}, {255,  77,   0}, {255,  77,   0}, {255,  77,   0}, {255,  78,   0},
+   {255,  78,   0}, {255,  78,   0}, {255,  78,   0}, {255,  79,   0}, {255,  79,   0},
+   {255,  79,   0}, {255,  79,   0}, {255,  80,   0}, {255,  80,   0}, {255,  80,   0},
+   {255,  80,   0}, {255,  81,   0}, {255,  81,   0}, {255,  81,   0}, {255,  81,   0},
+   {255,  82,   0}, {255,  82,   0}, {255,  82,   0}, {255,  82,   0}, {255,  83,   0},
+   {255,  83,   0}, {255,  83,   0}, {255,  83,   0}, {255,  84,   0}, {255,  84,   0},
+   {255,  84,   0}, {255,  84,   0}, {255,  85,   0}, {255,  85,   0}, {255,  85,   0},
+   {255,  86,   0}, {255,  86,   0}, {255,  86,   0}, {255,  86,   0}, {255,  87,   0},
+   {255,  87,   0}, {255,  87,   0}, {255,  87,   0}, {255,  88,   0}, {255,  88,   0},
+   {255,  88,   0}, {255,  88,   0}, {255,  89,   0}, {255,  89,   0}, {255,  89,   0},
+   {255,  89,   0}, {255,  90,   0}, {255,  90,   0}, {255,  90,   0}, {255,  90,   0},
+   {255,  91,   0}, {255,  91,   0}, {255,  91,   0}, {255,  91,   0}, {255,  92,   0},
+   {255,  92,   0}, {255,  92,   0}, {255,  92,   0}, {255,  93,   0}, {255,  93,   0},
+   {255,  93,   0}, {255,  93,   0}, {255,  94,   0}, {255,  94,   0}, {255,  94,   0},
+   {255,  94,   0}, {255,  95,   0}, {255,  95,   0}, {255,  95,   0}, {255,  95,   0},
+   {255,  96,   0}, {255,  96,   0}, {255,  96,   0}, {255,  96,   0}, {255,  97,   0},
+   {255,  97,   0}, {255,  97,   0}, {255,  98,   0}, {255,  98,   0}, {255,  98,   0},
+   {255,  98,   0}, {255,  99,   0}, {255,  99,   0}, {255,  99,   0}, {255,  99,   0},
+   {255, 100,   0}, {255, 100,   0}, {255, 100,   0}, {255, 100,   0}, {255, 101,   0},
+   {255, 101,   0}, {255, 101,   0}, {255, 101,   0}, {255, 102,   0}, {255, 102,   0},
+   {255, 102,   0}, {255, 102,   0}, {255, 103,   0}, {255, 103,   0}, {255, 103,   0},
+   {255, 103,   0}, {255, 104,   0}, {255, 104,   0}, {255, 104,   0}, {255, 104,   0},
+   {255, 105,   0}, {255, 105,   0}, {255, 105,   0}, {255, 105,   0}, {255, 106,   0},
+   {255, 106,   0}, {255, 106,   0}, {255, 106,   0}, {255, 107,   0}, {255, 107,   0},
+   {255, 107,   0}, {255, 107,   0}, {255, 108,   0}, {255, 108,   0}, {255, 108,   0},
+   {255, 108,   0}, {255, 109,   0}, {255, 109,   0}, {255, 109,   0}, {255, 110,   0},
+   {255, 110,   0}, {255, 110,   0}, {255, 110,   0}, {255, 111,   0}, {255, 111,   0},
+   {255, 111,   0}, {255, 111,   0}, {255, 112,   0}, {255, 112,   0}, {255, 112,   0},
+   {255, 112,   0}, {255, 113,   0}, {255, 113,   0}, {255, 113,   0}, {255, 113,   0},
+   {255, 114,   0}, {255, 114,   0}, {255, 114,   0}, {255, 114,   0}, {255, 115,   0},
+   {255, 115,   0}, {255, 115,   0}, {255, 115,   0}, {255, 116,   0}, {255, 116,   0},
+   {255, 116,   0}, {255, 116,   0}, {255, 117,   0}, {255, 117,   0}, {255, 117,   0},
+   {255, 117,   0}, {255, 118,   0}, {255, 118,   0}, {255, 118,   0}, {255, 118,   0},
+   {255, 119,   0}, {255, 119,   0}, {255, 119,   0}, {255, 119,   0}, {255, 120,   0},
+   {255, 120,   0}, {255, 120,   0}, {255, 120,   0}, {255, 121,   0}, {255, 121,   0},
+   {255, 121,   0}, {255, 122,   0}, {255, 122,   0}, {255, 122,   0}, {255, 122,   0},
+   {255, 123,   0}, {255, 123,   0}, {255, 123,   0}, {255, 123,   0}, {255, 124,   0},
+   {255, 124,   0}, {255, 124,   0}, {255, 124,   0}, {255, 125,   0}, {255, 125,   0},
+   {255, 125,   0}, {255, 125,   0}, {255, 126,   0}, {255, 126,   0}, {255, 126,   0},
+   {255, 126,   0}, {255, 127,   0}, {255, 127,   0}, {255, 127,   0}, {255, 127,   0},
+   {255, 128,   0}, {255, 128,   0}, {255, 128,   0}, {255, 128,   0}, {255, 129,   0},
+   {255, 129,   0}, {255, 129,   0}, {255, 129,   0}, {255, 130,   0}, {255, 130,   0},
+   {255, 130,   0}, {255, 130,   0}, {255, 131,   0}, {255, 131,   0}, {255, 131,   0},
+   {255, 131,   0}, {255, 132,   0}, {255, 132,   0}, {255, 132,   0}, {255, 132,   0},
+   {255, 133,   0}, {255, 133,   0}, {255, 133,   0}, {255, 133,   0}, {255, 134,   0},
+   {255, 134,   0}, {255, 134,   0}, {255, 135,   0}, {255, 135,   0}, {255, 135,   0},
+   {255, 135,   0}, {255, 136,   0}, {255, 136,   0}, {255, 136,   0}, {255, 136,   0},
+   {255, 137,   0}, {255, 137,   0}, {255, 137,   0}, {255, 137,   0}, {255, 138,   0},
+   {255, 138,   0}, {255, 138,   0}, {255, 138,   0}, {255, 139,   0}, {255, 139,   0},
+   {255, 139,   0}, {255, 139,   0}, {255, 140,   0}, {255, 140,   0}, {255, 140,   0},
+   {255, 140,   0}, {255, 141,   0}, {255, 141,   0}, {255, 141,   0}, {255, 141,   0},
+   {255, 142,   0}, {255, 142,   0}, {255, 142,   0}, {255, 142,   0}, {255, 143,   0},
+   {255, 143,   0}, {255, 143,   0}, {255, 143,   0}, {255, 144,   0}, {255, 144,   0},
+   {255, 144,   0}, {255, 144,   0}, {255, 145,   0}, {255, 145,   0}, {255, 145,   0},
+   {255, 145,   0}, {255, 146,   0}, {255, 146,   0}, {255, 146,   0}, {255, 147,   0},
+   {255, 147,   0}, {255, 147,   0}, {255, 147,   0}, {255, 148,   0}, {255, 148,   0},
+   {255, 148,   0}, {255, 148,   0}, {255, 149,   0}, {255, 149,   0}, {255, 149,   0},
+   {255, 149,   0}, {255, 150,   0}, {255, 150,   0}, {255, 150,   0}, {255, 150,   0},
+   {255, 151,   0}, {255, 151,   0}, {255, 151,   0}, {255, 151,   0}, {255, 152,   0},
+   {255, 152,   0}, {255, 152,   0}, {255, 152,   0}, {255, 153,   0}, {255, 153,   0},
+   {255, 153,   0}, {255, 153,   0}, {255, 154,   0}, {255, 154,   0}, {255, 154,   0},
+   {255, 154,   0}, {255, 155,   0}, {255, 155,   0}, {255, 155,   0}, {255, 155,   0},
+   {255, 156,   0}, {255, 156,   0}, {255, 156,   0}, {255, 156,   0}, {255, 157,   0},
+   {255, 157,   0}, {255, 157,   0}, {255, 157,   0}, {255, 158,   0}, {255, 158,   0},
+   {255, 158,   0}, {255, 159,   0}, {255, 159,   0}, {255, 159,   0}, {255, 159,   0},
+   {255, 160,   0}, {255, 160,   0}, {255, 160,   0}, {255, 160,   0}, {255, 161,   0},
+   {255, 161,   0}, {255, 161,   0}, {255, 161,   0}, {255, 162,   0}, {255, 162,   0},
+   {255, 162,   0}, {255, 162,   0}, {255, 163,   0}, {255, 163,   0}, {255, 163,   0},
+   {255, 163,   0}, {255, 164,   0}, {255, 164,   0}, {255, 164,   0}, {255, 164,   0},
+   {255, 165,   0}, {255, 165,   0}, {255, 165,   0}, {255, 165,   0}, {255, 166,   0},
+   {255, 166,   0}, {255, 166,   0}, {255, 166,   0}, {255, 167,   0}, {255, 167,   0},
+   {255, 167,   0}, {255, 167,   0}, {255, 168,   0}, {255, 168,   0}, {255, 168,   0},
+   {255, 168,   0}, {255, 169,   0}, {255, 169,   0}, {255, 169,   0}, {255, 169,   0},
+   {255, 170,   0}, {255, 170,   0}, {255, 170,   0}, {255, 171,   0}, {255, 171,   0},
+   {255, 171,   0}, {255, 171,   0}, {255, 172,   0}, {255, 172,   0}, {255, 172,   0},
+   {255, 172,   0}, {255, 173,   0}, {255, 173,   0}, {255, 173,   0}, {255, 173,   0},
+   {255, 174,   0}, {255, 174,   0}, {255, 174,   0}, {255, 174,   0}, {255, 175,   0},
+   {255, 175,   0}, {255, 175,   0}, {255, 175,   0}, {255, 176,   0}, {255, 176,   0},
+   {255, 176,   0}, {255, 176,   0}, {255, 177,   0}, {255, 177,   0}, {255, 177,   0},
+   {255, 177,   0}, {255, 178,   0}, {255, 178,   0}, {255, 178,   0}, {255, 178,   0},
+   {255, 179,   0}, {255, 179,   0}, {255, 179,   0}, {255, 179,   0}, {255, 180,   0},
+   {255, 180,   0}, {255, 180,   0}, {255, 180,   0}, {255, 181,   0}, {255, 181,   0},
+   {255, 181,   0}, {255, 181,   0}, {255, 182,   0}, {255, 182,   0}, {255, 182,   0},
+   {255, 183,   0}, {255, 183,   0}, {255, 183,   0}, {255, 183,   0}, {255, 184,   0},
+   {255, 184,   0}, {255, 184,   0}, {255, 184,   0}, {255, 185,   0}, {255, 185,   0},
+   {255, 185,   0}, {255, 185,   0}, {255, 186,   0}, {255, 186,   0}, {255, 186,   0},
+   {255, 186,   0}, {255, 187,   0}, {255, 187,   0}, {255, 187,   0}, {255, 187,   0},
+   {255, 188,   0}, {255, 188,   0}, {255, 188,   0}, {255, 188,   0}, {255, 189,   0},
+   {255, 189,   0}, {255, 189,   0}, {255, 189,   0}, {255, 190,   0}, {255, 190,   0},
+   {255, 190,   0}, {255, 190,   0}, {255, 191,   0}, {255, 191,   0}, {255, 191,   0},
+   {255, 191,   0}, {255, 192,   0}, {255, 192,   0}, {255, 192,   0}, {255, 192,   0},
+   {255, 193,   0}, {255, 193,   0}, {255, 193,   0}, {255, 193,   0}, {255, 194,   0},
+   {255, 194,   0}, {255, 194,   0}, {255, 195,   0}, {255, 195,   0}, {255, 195,   0},
+   {255, 195,   0}, {255, 196,   0}, {255, 196,   0}, {255, 196,   0}, {255, 196,   0},
+   {255, 197,   0}, {255, 197,   0}, {255, 197,   0}, {255, 197,   0}, {255, 198,   0},
+   {255, 198,   0}, {255, 198,   0}, {255, 198,   0}, {255, 199,   0}, {255, 199,   0},
+   {255, 199,   0}, {255, 199,   0}, {255, 200,   0}, {255, 200,   0}, {255, 200,   0},
+   {255, 200,   0}, {255, 201,   0}, {255, 201,   0}, {255, 201,   0}, {255, 201,   0},
+   {255, 202,   0}, {255, 202,   0}, {255, 202,   0}, {255, 202,   0}, {255, 203,   0},
+   {255, 203,   0}, {255, 203,   0}, {255, 203,   0}, {255, 204,   0}, {255, 204,   0},
+   {255, 204,   0}, {255, 204,   0}, {255, 205,   0}, {255, 205,   0}, {255, 205,   0},
+   {255, 205,   0}, {255, 206,   0}, {255, 206,   0}, {255, 206,   0}, {255, 207,   0},
+   {255, 207,   0}, {255, 207,   0}, {255, 207,   0}, {255, 208,   0}, {255, 208,   0},
+   {255, 208,   0}, {255, 208,   0}, {255, 209,   0}, {255, 209,   0}, {255, 209,   0},
+   {255, 209,   0}, {255, 210,   0}, {255, 210,   0}, {255, 210,   0}, {255, 210,   0},
+   {255, 211,   0}, {255, 211,   0}, {255, 211,   0}, {255, 211,   0}, {255, 212,   0},
+   {255, 212,   0}, {255, 212,   0}, {255, 212,   0}, {255, 213,   0}, {255, 213,   0},
+   {255, 213,   0}, {255, 213,   0}, {255, 214,   0}, {255, 214,   0}, {255, 214,   0},
+   {255, 214,   0}, {255, 215,   0}, {255, 215,   0}, {255, 215,   0}, {255, 215,   0},
+   {255, 216,   0}, {255, 216,   0}, {255, 216,   0}, {255, 216,   0}, {255, 217,   0},
+   {255, 217,   0}, {255, 217,   0}, {255, 217,   0}, {255, 218,   0}, {255, 218,   0},
+   {255, 218,   0}, {255, 218,   0}, {255, 219,   0}, {255, 219,   0}, {255, 219,   0},
+   {255, 220,   0}, {255, 220,   0}, {255, 220,   0}, {255, 220,   0}, {255, 221,   0},
+   {255, 221,   0}, {255, 221,   0}, {255, 221,   0}, {255, 222,   0}, {255, 222,   0},
+   {255, 222,   0}, {255, 222,   0}, {255, 223,   0}, {255, 223,   0}, {255, 223,   0},
+   {255, 223,   0}, {255, 224,   0}, {255, 224,   0}, {255, 224,   0}, {255, 224,   0},
+   {255, 225,   0}, {255, 225,   0}, {255, 225,   0}, {255, 225,   0}, {255, 226,   0},
+   {255, 226,   0}, {255, 226,   0}, {255, 226,   0}, {255, 227,   0}, {255, 227,   0},
+   {255, 227,   0}, {255, 227,   0}, {255, 228,   0}, {255, 228,   0}, {255, 228,   0},
+   {255, 228,   0}, {255, 229,   0}, {255, 229,   0}, {255, 229,   0}, {255, 229,   0},
+   {255, 230,   0}, {255, 230,   0}, {255, 230,   0}, {255, 230,   0}, {255, 231,   0},
+   {255, 231,   0}, {255, 231,   0}, {255, 232,   0}, {255, 232,   0}, {255, 232,   0},
+   {255, 232,   0}, {255, 233,   0}, {255, 233,   0}, {255, 233,   0}, {255, 233,   0},
+   {255, 234,   0}, {255, 234,   0}, {255, 234,   0}, {255, 234,   0}, {255, 235,   0},
+   {255, 235,   0}, {255, 235,   0}, {255, 235,   0}, {255, 236,   0}, {255, 236,   0},
+   {255, 236,   0}, {255, 236,   0}, {255, 237,   0}, {255, 237,   0}, {255, 237,   0},
+   {255, 237,   0}, {255, 238,   0}, {255, 238,   0}, {255, 238,   0}, {255, 238,   0},
+   {255, 239,   0}, {255, 239,   0}, {255, 239,   0}, {255, 239,   0}, {255, 240,   0},
+   {255, 240,   0}, {255, 240,   0}, {255, 240,   0}, {255, 241,   0}, {255, 241,   0},
+   {255, 241,   0}, {255, 241,   0}, {255, 242,   0}, {255, 242,   0}, {255, 242,   0},
+   {255, 242,   0}, {255, 243,   0}, {255, 243,   0}, {255, 243,   0}, {255, 244,   0},
+   {255, 244,   0}, {255, 244,   0}, {255, 244,   0}, {255, 245,   0}, {255, 245,   0},
+   {255, 245,   0}, {255, 245,   0}, {255, 246,   0}, {255, 246,   0}, {255, 246,   0},
+   {255, 246,   0}, {255, 247,   0}, {255, 247,   0}, {255, 247,   0}, {255, 247,   0},
+   {255, 248,   0}, {255, 248,   0}, {255, 248,   0}, {255, 248,   0}, {255, 249,   0},
+   {255, 249,   0}, {255, 249,   0}, {255, 249,   0}, {255, 250,   0}, {255, 250,   0},
+   {255, 250,   0}, {255, 250,   0}, {255, 251,   0}, {255, 251,   0}, {255, 251,   0},
+   {255, 251,   0}, {255, 252,   0}, {255, 252,   0}, {255, 252,   0}, {255, 252,   0},
+   {255, 253,   0}, {255, 253,   0}, {255, 253,   0}, {255, 253,   0}, {255, 254,   0},
+   {255, 254,   0}, {255, 254,   0}, {255, 254,   0}, {255, 255,   0}, {255, 255,   0}
+};
+
+const rgb_t copper_colormap[1000] = {
+   {  0,   0,   0}, {  0,   0,   0}, {  1,   0,   0}, {  1,   1,   0}, {  1,   1,   1},
+   {  2,   1,   1}, {  2,   1,   1}, {  2,   1,   1}, {  3,   2,   1}, {  3,   2,   1},
+   {  3,   2,   1}, {  4,   2,   1}, {  4,   2,   2}, {  4,   3,   2}, {  4,   3,   2},
+   {  5,   3,   2}, {  5,   3,   2}, {  5,   3,   2}, {  6,   4,   2}, {  6,   4,   2},
+   {  6,   4,   3}, {  7,   4,   3}, {  7,   4,   3}, {  7,   5,   3}, {  8,   5,   3},
+   {  8,   5,   3}, {  8,   5,   3}, {  9,   5,   3}, {  9,   6,   4}, {  9,   6,   4},
+   { 10,   6,   4}, { 10,   6,   4}, { 10,   6,   4}, { 11,   7,   4}, { 11,   7,   4},
+   { 11,   7,   4}, { 11,   7,   5}, { 12,   7,   5}, { 12,   8,   5}, { 12,   8,   5},
+   { 13,   8,   5}, { 13,   8,   5}, { 13,   8,   5}, { 14,   9,   5}, { 14,   9,   6},
+   { 14,   9,   6}, { 15,   9,   6}, { 15,   9,   6}, { 15,  10,   6}, { 16,  10,   6},
+   { 16,  10,   6}, { 16,  10,   6}, { 17,  10,   7}, { 17,  11,   7}, { 17,  11,   7},
+   { 18,  11,   7}, { 18,  11,   7}, { 18,  11,   7}, { 19,  12,   7}, { 19,  12,   7},
+   { 19,  12,   8}, { 19,  12,   8}, { 20,  12,   8}, { 20,  13,   8}, { 20,  13,   8},
+   { 21,  13,   8}, { 21,  13,   8}, { 21,  13,   9}, { 22,  14,   9}, { 22,  14,   9},
+   { 22,  14,   9}, { 23,  14,   9}, { 23,  14,   9}, { 23,  15,   9}, { 24,  15,   9},
+   { 24,  15,  10}, { 24,  15,  10}, { 25,  15,  10}, { 25,  16,  10}, { 25,  16,  10},
+   { 26,  16,  10}, { 26,  16,  10}, { 26,  16,  10}, { 26,  17,  11}, { 27,  17,  11},
+   { 27,  17,  11}, { 27,  17,  11}, { 28,  17,  11}, { 28,  18,  11}, { 28,  18,  11},
+   { 29,  18,  11}, { 29,  18,  12}, { 29,  18,  12}, { 30,  19,  12}, { 30,  19,  12},
+   { 30,  19,  12}, { 31,  19,  12}, { 31,  19,  12}, { 31,  20,  12}, { 32,  20,  13},
+   { 32,  20,  13}, { 32,  20,  13}, { 33,  20,  13}, { 33,  21,  13}, { 33,  21,  13},
+   { 34,  21,  13}, { 34,  21,  13}, { 34,  21,  14}, { 34,  22,  14}, { 35,  22,  14},
+   { 35,  22,  14}, { 35,  22,  14}, { 36,  22,  14}, { 36,  23,  14}, { 36,  23,  14},
+   { 37,  23,  15}, { 37,  23,  15}, { 37,  23,  15}, { 38,  24,  15}, { 38,  24,  15},
+   { 38,  24,  15}, { 39,  24,  15}, { 39,  24,  15}, { 39,  25,  16}, { 40,  25,  16},
+   { 40,  25,  16}, { 40,  25,  16}, { 41,  25,  16}, { 41,  26,  16}, { 41,  26,  16},
+   { 41,  26,  17}, { 42,  26,  17}, { 42,  26,  17}, { 42,  27,  17}, { 43,  27,  17},
+   { 43,  27,  17}, { 43,  27,  17}, { 44,  27,  17}, { 44,  28,  18}, { 44,  28,  18},
+   { 45,  28,  18}, { 45,  28,  18}, { 45,  28,  18}, { 46,  29,  18}, { 46,  29,  18},
+   { 46,  29,  18}, { 47,  29,  19}, { 47,  29,  19}, { 47,  30,  19}, { 48,  30,  19},
+   { 48,  30,  19}, { 48,  30,  19}, { 48,  30,  19}, { 49,  31,  19}, { 49,  31,  20},
+   { 49,  31,  20}, { 50,  31,  20}, { 50,  31,  20}, { 50,  32,  20}, { 51,  32,  20},
+   { 51,  32,  20}, { 51,  32,  20}, { 52,  32,  21}, { 52,  33,  21}, { 52,  33,  21},
+   { 53,  33,  21}, { 53,  33,  21}, { 53,  33,  21}, { 54,  34,  21}, { 54,  34,  21},
+   { 54,  34,  22}, { 55,  34,  22}, { 55,  34,  22}, { 55,  34,  22}, { 56,  35,  22},
+   { 56,  35,  22}, { 56,  35,  22}, { 56,  35,  22}, { 57,  35,  23}, { 57,  36,  23},
+   { 57,  36,  23}, { 58,  36,  23}, { 58,  36,  23}, { 58,  36,  23}, { 59,  37,  23},
+   { 59,  37,  23}, { 59,  37,  24}, { 60,  37,  24}, { 60,  37,  24}, { 60,  38,  24},
+   { 61,  38,  24}, { 61,  38,  24}, { 61,  38,  24}, { 62,  38,  25}, { 62,  39,  25},
+   { 62,  39,  25}, { 63,  39,  25}, { 63,  39,  25}, { 63,  39,  25}, { 63,  40,  25},
+   { 64,  40,  25}, { 64,  40,  26}, { 64,  40,  26}, { 65,  40,  26}, { 65,  41,  26},
+   { 65,  41,  26}, { 66,  41,  26}, { 66,  41,  26}, { 66,  41,  26}, { 67,  42,  27},
+   { 67,  42,  27}, { 67,  42,  27}, { 68,  42,  27}, { 68,  42,  27}, { 68,  43,  27},
+   { 69,  43,  27}, { 69,  43,  27}, { 69,  43,  28}, { 70,  43,  28}, { 70,  44,  28},
+   { 70,  44,  28}, { 71,  44,  28}, { 71,  44,  28}, { 71,  44,  28}, { 71,  45,  28},
+   { 72,  45,  29}, { 72,  45,  29}, { 72,  45,  29}, { 73,  45,  29}, { 73,  46,  29},
+   { 73,  46,  29}, { 74,  46,  29}, { 74,  46,  29}, { 74,  46,  30}, { 75,  47,  30},
+   { 75,  47,  30}, { 75,  47,  30}, { 76,  47,  30}, { 76,  47,  30}, { 76,  48,  30},
+   { 77,  48,  30}, { 77,  48,  31}, { 77,  48,  31}, { 78,  48,  31}, { 78,  49,  31},
+   { 78,  49,  31}, { 78,  49,  31}, { 79,  49,  31}, { 79,  49,  31}, { 79,  50,  32},
+   { 80,  50,  32}, { 80,  50,  32}, { 80,  50,  32}, { 81,  50,  32}, { 81,  51,  32},
+   { 81,  51,  32}, { 82,  51,  33}, { 82,  51,  33}, { 82,  51,  33}, { 83,  52,  33},
+   { 83,  52,  33}, { 83,  52,  33}, { 84,  52,  33}, { 84,  52,  33}, { 84,  53,  34},
+   { 85,  53,  34}, { 85,  53,  34}, { 85,  53,  34}, { 86,  53,  34}, { 86,  54,  34},
+   { 86,  54,  34}, { 86,  54,  34}, { 87,  54,  35}, { 87,  54,  35}, { 87,  55,  35},
+   { 88,  55,  35}, { 88,  55,  35}, { 88,  55,  35}, { 89,  55,  35}, { 89,  56,  35},
+   { 89,  56,  36}, { 90,  56,  36}, { 90,  56,  36}, { 90,  56,  36}, { 91,  57,  36},
+   { 91,  57,  36}, { 91,  57,  36}, { 92,  57,  36}, { 92,  57,  37}, { 92,  58,  37},
+   { 93,  58,  37}, { 93,  58,  37}, { 93,  58,  37}, { 93,  58,  37}, { 94,  59,  37},
+   { 94,  59,  37}, { 94,  59,  38}, { 95,  59,  38}, { 95,  59,  38}, { 95,  60,  38},
+   { 96,  60,  38}, { 96,  60,  38}, { 96,  60,  38}, { 97,  60,  38}, { 97,  61,  39},
+   { 97,  61,  39}, { 98,  61,  39}, { 98,  61,  39}, { 98,  61,  39}, { 99,  62,  39},
+   { 99,  62,  39}, { 99,  62,  39}, {100,  62,  40}, {100,  62,  40}, {100,  63,  40},
+   {101,  63,  40}, {101,  63,  40}, {101,  63,  40}, {101,  63,  40}, {102,  64,  41},
+   {102,  64,  41}, {102,  64,  41}, {103,  64,  41}, {103,  64,  41}, {103,  65,  41},
+   {104,  65,  41}, {104,  65,  41}, {104,  65,  42}, {105,  65,  42}, {105,  66,  42},
+   {105,  66,  42}, {106,  66,  42}, {106,  66,  42}, {106,  66,  42}, {107,  67,  42},
+   {107,  67,  43}, {107,  67,  43}, {108,  67,  43}, {108,  67,  43}, {108,  68,  43},
+   {108,  68,  43}, {109,  68,  43}, {109,  68,  43}, {109,  68,  44}, {110,  69,  44},
+   {110,  69,  44}, {110,  69,  44}, {111,  69,  44}, {111,  69,  44}, {111,  70,  44},
+   {112,  70,  44}, {112,  70,  45}, {112,  70,  45}, {113,  70,  45}, {113,  71,  45},
+   {113,  71,  45}, {114,  71,  45}, {114,  71,  45}, {114,  71,  45}, {115,  72,  46},
+   {115,  72,  46}, {115,  72,  46}, {116,  72,  46}, {116,  72,  46}, {116,  73,  46},
+   {116,  73,  46}, {117,  73,  46}, {117,  73,  47}, {117,  73,  47}, {118,  74,  47},
+   {118,  74,  47}, {118,  74,  47}, {119,  74,  47}, {119,  74,  47}, {119,  75,  47},
+   {120,  75,  48}, {120,  75,  48}, {120,  75,  48}, {121,  75,  48}, {121,  76,  48},
+   {121,  76,  48}, {122,  76,  48}, {122,  76,  49}, {122,  76,  49}, {123,  77,  49},
+   {123,  77,  49}, {123,  77,  49}, {123,  77,  49}, {124,  77,  49}, {124,  78,  49},
+   {124,  78,  50}, {125,  78,  50}, {125,  78,  50}, {125,  78,  50}, {126,  79,  50},
+   {126,  79,  50}, {126,  79,  50}, {127,  79,  50}, {127,  79,  51}, {127,  80,  51},
+   {128,  80,  51}, {128,  80,  51}, {128,  80,  51}, {129,  80,  51}, {129,  81,  51},
+   {129,  81,  51}, {130,  81,  52}, {130,  81,  52}, {130,  81,  52}, {130,  82,  52},
+   {131,  82,  52}, {131,  82,  52}, {131,  82,  52}, {132,  82,  52}, {132,  83,  53},
+   {132,  83,  53}, {133,  83,  53}, {133,  83,  53}, {133,  83,  53}, {134,  84,  53},
+   {134,  84,  53}, {134,  84,  53}, {135,  84,  54}, {135,  84,  54}, {135,  85,  54},
+   {136,  85,  54}, {136,  85,  54}, {136,  85,  54}, {137,  85,  54}, {137,  86,  54},
+   {137,  86,  55}, {138,  86,  55}, {138,  86,  55}, {138,  86,  55}, {138,  87,  55},
+   {139,  87,  55}, {139,  87,  55}, {139,  87,  55}, {140,  87,  56}, {140,  88,  56},
+   {140,  88,  56}, {141,  88,  56}, {141,  88,  56}, {141,  88,  56}, {142,  89,  56},
+   {142,  89,  57}, {142,  89,  57}, {143,  89,  57}, {143,  89,  57}, {143,  90,  57},
+   {144,  90,  57}, {144,  90,  57}, {144,  90,  57}, {145,  90,  58}, {145,  91,  58},
+   {145,  91,  58}, {145,  91,  58}, {146,  91,  58}, {146,  91,  58}, {146,  92,  58},
+   {147,  92,  58}, {147,  92,  59}, {147,  92,  59}, {148,  92,  59}, {148,  93,  59},
+   {148,  93,  59}, {149,  93,  59}, {149,  93,  59}, {149,  93,  59}, {150,  94,  60},
+   {150,  94,  60}, {150,  94,  60}, {151,  94,  60}, {151,  94,  60}, {151,  95,  60},
+   {152,  95,  60}, {152,  95,  60}, {152,  95,  61}, {153,  95,  61}, {153,  96,  61},
+   {153,  96,  61}, {153,  96,  61}, {154,  96,  61}, {154,  96,  61}, {154,  97,  61},
+   {155,  97,  62}, {155,  97,  62}, {155,  97,  62}, {156,  97,  62}, {156,  98,  62},
+   {156,  98,  62}, {157,  98,  62}, {157,  98,  62}, {157,  98,  63}, {158,  99,  63},
+   {158,  99,  63}, {158,  99,  63}, {159,  99,  63}, {159,  99,  63}, {159, 100,  63},
+   {160, 100,  63}, {160, 100,  64}, {160, 100,  64}, {160, 100,  64}, {161, 101,  64},
+   {161, 101,  64}, {161, 101,  64}, {162, 101,  64}, {162, 101,  65}, {162, 101,  65},
+   {163, 102,  65}, {163, 102,  65}, {163, 102,  65}, {164, 102,  65}, {164, 102,  65},
+   {164, 103,  65}, {165, 103,  66}, {165, 103,  66}, {165, 103,  66}, {166, 103,  66},
+   {166, 104,  66}, {166, 104,  66}, {167, 104,  66}, {167, 104,  66}, {167, 104,  67},
+   {168, 105,  67}, {168, 105,  67}, {168, 105,  67}, {168, 105,  67}, {169, 105,  67},
+   {169, 106,  67}, {169, 106,  67}, {170, 106,  68}, {170, 106,  68}, {170, 106,  68},
+   {171, 107,  68}, {171, 107,  68}, {171, 107,  68}, {172, 107,  68}, {172, 107,  68},
+   {172, 108,  69}, {173, 108,  69}, {173, 108,  69}, {173, 108,  69}, {174, 108,  69},
+   {174, 109,  69}, {174, 109,  69}, {175, 109,  69}, {175, 109,  70}, {175, 109,  70},
+   {175, 110,  70}, {176, 110,  70}, {176, 110,  70}, {176, 110,  70}, {177, 110,  70},
+   {177, 111,  70}, {177, 111,  71}, {178, 111,  71}, {178, 111,  71}, {178, 111,  71},
+   {179, 112,  71}, {179, 112,  71}, {179, 112,  71}, {180, 112,  71}, {180, 112,  72},
+   {180, 113,  72}, {181, 113,  72}, {181, 113,  72}, {181, 113,  72}, {182, 113,  72},
+   {182, 114,  72}, {182, 114,  73}, {183, 114,  73}, {183, 114,  73}, {183, 114,  73},
+   {183, 115,  73}, {184, 115,  73}, {184, 115,  73}, {184, 115,  73}, {185, 115,  74},
+   {185, 116,  74}, {185, 116,  74}, {186, 116,  74}, {186, 116,  74}, {186, 116,  74},
+   {187, 117,  74}, {187, 117,  74}, {187, 117,  75}, {188, 117,  75}, {188, 117,  75},
+   {188, 118,  75}, {189, 118,  75}, {189, 118,  75}, {189, 118,  75}, {190, 118,  75},
+   {190, 119,  76}, {190, 119,  76}, {190, 119,  76}, {191, 119,  76}, {191, 119,  76},
+   {191, 120,  76}, {192, 120,  76}, {192, 120,  76}, {192, 120,  77}, {193, 120,  77},
+   {193, 121,  77}, {193, 121,  77}, {194, 121,  77}, {194, 121,  77}, {194, 121,  77},
+   {195, 122,  77}, {195, 122,  78}, {195, 122,  78}, {196, 122,  78}, {196, 122,  78},
+   {196, 123,  78}, {197, 123,  78}, {197, 123,  78}, {197, 123,  78}, {198, 123,  79},
+   {198, 124,  79}, {198, 124,  79}, {198, 124,  79}, {199, 124,  79}, {199, 124,  79},
+   {199, 125,  79}, {200, 125,  79}, {200, 125,  80}, {200, 125,  80}, {201, 125,  80},
+   {201, 126,  80}, {201, 126,  80}, {202, 126,  80}, {202, 126,  80}, {202, 126,  81},
+   {203, 127,  81}, {203, 127,  81}, {203, 127,  81}, {204, 127,  81}, {204, 127,  81},
+   {204, 128,  81}, {205, 128,  81}, {205, 128,  82}, {205, 128,  82}, {205, 128,  82},
+   {206, 129,  82}, {206, 129,  82}, {206, 129,  82}, {207, 129,  82}, {207, 129,  82},
+   {207, 130,  83}, {208, 130,  83}, {208, 130,  83}, {208, 130,  83}, {209, 130,  83},
+   {209, 131,  83}, {209, 131,  83}, {210, 131,  83}, {210, 131,  84}, {210, 131,  84},
+   {211, 132,  84}, {211, 132,  84}, {211, 132,  84}, {212, 132,  84}, {212, 132,  84},
+   {212, 133,  84}, {212, 133,  85}, {213, 133,  85}, {213, 133,  85}, {213, 133,  85},
+   {214, 134,  85}, {214, 134,  85}, {214, 134,  85}, {215, 134,  85}, {215, 134,  86},
+   {215, 135,  86}, {216, 135,  86}, {216, 135,  86}, {216, 135,  86}, {217, 135,  86},
+   {217, 136,  86}, {217, 136,  86}, {218, 136,  87}, {218, 136,  87}, {218, 136,  87},
+   {219, 137,  87}, {219, 137,  87}, {219, 137,  87}, {220, 137,  87}, {220, 137,  87},
+   {220, 138,  88}, {220, 138,  88}, {221, 138,  88}, {221, 138,  88}, {221, 138,  88},
+   {222, 139,  88}, {222, 139,  88}, {222, 139,  89}, {223, 139,  89}, {223, 139,  89},
+   {223, 140,  89}, {224, 140,  89}, {224, 140,  89}, {224, 140,  89}, {225, 140,  89},
+   {225, 141,  90}, {225, 141,  90}, {226, 141,  90}, {226, 141,  90}, {226, 141,  90},
+   {227, 142,  90}, {227, 142,  90}, {227, 142,  90}, {227, 142,  91}, {228, 142,  91},
+   {228, 143,  91}, {228, 143,  91}, {229, 143,  91}, {229, 143,  91}, {229, 143,  91},
+   {230, 144,  91}, {230, 144,  92}, {230, 144,  92}, {231, 144,  92}, {231, 144,  92},
+   {231, 145,  92}, {232, 145,  92}, {232, 145,  92}, {232, 145,  92}, {233, 145,  93},
+   {233, 146,  93}, {233, 146,  93}, {234, 146,  93}, {234, 146,  93}, {234, 146,  93},
+   {235, 147,  93}, {235, 147,  93}, {235, 147,  94}, {235, 147,  94}, {236, 147,  94},
+   {236, 148,  94}, {236, 148,  94}, {237, 148,  94}, {237, 148,  94}, {237, 148,  94},
+   {238, 149,  95}, {238, 149,  95}, {238, 149,  95}, {239, 149,  95}, {239, 149,  95},
+   {239, 150,  95}, {240, 150,  95}, {240, 150,  95}, {240, 150,  96}, {241, 150,  96},
+   {241, 151,  96}, {241, 151,  96}, {242, 151,  96}, {242, 151,  96}, {242, 151,  96},
+   {242, 152,  97}, {243, 152,  97}, {243, 152,  97}, {243, 152,  97}, {244, 152,  97},
+   {244, 153,  97}, {244, 153,  97}, {245, 153,  97}, {245, 153,  98}, {245, 153,  98},
+   {246, 154,  98}, {246, 154,  98}, {246, 154,  98}, {247, 154,  98}, {247, 154,  98},
+   {247, 155,  98}, {248, 155,  99}, {248, 155,  99}, {248, 155,  99}, {249, 155,  99},
+   {249, 156,  99}, {249, 156,  99}, {250, 156,  99}, {250, 156,  99}, {250, 156, 100},
+   {250, 157, 100}, {251, 157, 100}, {251, 157, 100}, {251, 157, 100}, {252, 157, 100},
+   {252, 158, 100}, {252, 158, 100}, {253, 158, 101}, {253, 158, 101}, {253, 158, 101},
+   {253, 159, 101}, {253, 159, 101}, {254, 159, 101}, {254, 159, 101}, {254, 159, 101},
+   {254, 160, 102}, {254, 160, 102}, {254, 160, 102}, {254, 160, 102}, {254, 160, 102},
+   {255, 161, 102}, {255, 161, 102}, {255, 161, 102}, {255, 161, 103}, {255, 161, 103},
+   {255, 162, 103}, {255, 162, 103}, {255, 162, 103}, {255, 162, 103}, {255, 162, 103},
+   {255, 163, 103}, {255, 163, 104}, {255, 163, 104}, {255, 163, 104}, {255, 163, 104},
+   {255, 164, 104}, {255, 164, 104}, {255, 164, 104}, {255, 164, 105}, {255, 164, 105},
+   {255, 165, 105}, {255, 165, 105}, {255, 165, 105}, {255, 165, 105}, {255, 165, 105},
+   {255, 166, 105}, {255, 166, 106}, {255, 166, 106}, {255, 166, 106}, {255, 166, 106},
+   {255, 167, 106}, {255, 167, 106}, {255, 167, 106}, {255, 167, 106}, {255, 167, 107},
+   {255, 168, 107}, {255, 168, 107}, {255, 168, 107}, {255, 168, 107}, {255, 168, 107},
+   {255, 168, 107}, {255, 169, 107}, {255, 169, 108}, {255, 169, 108}, {255, 169, 108},
+   {255, 169, 108}, {255, 170, 108}, {255, 170, 108}, {255, 170, 108}, {255, 170, 108},
+   {255, 170, 109}, {255, 171, 109}, {255, 171, 109}, {255, 171, 109}, {255, 171, 109},
+   {255, 171, 109}, {255, 172, 109}, {255, 172, 109}, {255, 172, 110}, {255, 172, 110},
+   {255, 172, 110}, {255, 173, 110}, {255, 173, 110}, {255, 173, 110}, {255, 173, 110},
+   {255, 173, 110}, {255, 174, 111}, {255, 174, 111}, {255, 174, 111}, {255, 174, 111},
+   {255, 174, 111}, {255, 175, 111}, {255, 175, 111}, {255, 175, 111}, {255, 175, 112},
+   {255, 175, 112}, {255, 176, 112}, {255, 176, 112}, {255, 176, 112}, {255, 176, 112},
+   {255, 176, 112}, {255, 177, 113}, {255, 177, 113}, {255, 177, 113}, {255, 177, 113},
+   {255, 177, 113}, {255, 178, 113}, {255, 178, 113}, {255, 178, 113}, {255, 178, 114},
+   {255, 178, 114}, {255, 179, 114}, {255, 179, 114}, {255, 179, 114}, {255, 179, 114},
+   {255, 179, 114}, {255, 180, 114}, {255, 180, 115}, {255, 180, 115}, {255, 180, 115},
+   {255, 180, 115}, {255, 181, 115}, {255, 181, 115}, {255, 181, 115}, {255, 181, 115},
+   {255, 181, 116}, {255, 182, 116}, {255, 182, 116}, {255, 182, 116}, {255, 182, 116},
+   {255, 182, 116}, {255, 183, 116}, {255, 183, 116}, {255, 183, 117}, {255, 183, 117},
+   {255, 183, 117}, {255, 184, 117}, {255, 184, 117}, {255, 184, 117}, {255, 184, 117},
+   {255, 184, 117}, {255, 185, 118}, {255, 185, 118}, {255, 185, 118}, {255, 185, 118},
+   {255, 185, 118}, {255, 186, 118}, {255, 186, 118}, {255, 186, 118}, {255, 186, 119},
+   {255, 186, 119}, {255, 187, 119}, {255, 187, 119}, {255, 187, 119}, {255, 187, 119},
+   {255, 187, 119}, {255, 188, 119}, {255, 188, 120}, {255, 188, 120}, {255, 188, 120},
+   {255, 188, 120}, {255, 189, 120}, {255, 189, 120}, {255, 189, 120}, {255, 189, 121},
+   {255, 189, 121}, {255, 190, 121}, {255, 190, 121}, {255, 190, 121}, {255, 190, 121},
+   {255, 190, 121}, {255, 191, 121}, {255, 191, 122}, {255, 191, 122}, {255, 191, 122},
+   {255, 191, 122}, {255, 192, 122}, {255, 192, 122}, {255, 192, 122}, {255, 192, 122},
+   {255, 192, 123}, {255, 193, 123}, {255, 193, 123}, {255, 193, 123}, {255, 193, 123},
+   {255, 193, 123}, {255, 194, 123}, {255, 194, 123}, {255, 194, 124}, {255, 194, 124},
+   {255, 194, 124}, {255, 195, 124}, {255, 195, 124}, {255, 195, 124}, {255, 195, 124},
+   {255, 195, 124}, {255, 196, 125}, {255, 196, 125}, {255, 196, 125}, {255, 196, 125},
+   {255, 196, 125}, {255, 197, 125}, {255, 197, 125}, {255, 197, 125}, {255, 197, 126},
+   {255, 197, 126}, {255, 198, 126}, {255, 198, 126}, {255, 198, 126}, {255, 198, 126},
+   {255, 198, 126}, {255, 199, 126}, {255, 199, 127}, {255, 199, 127}, {255, 199, 127}
+};
+
+const rgb_t gray_colormap[1000] = {
+   {255, 255, 255}, {255, 255, 255}, {254, 254, 254}, {254, 254, 254}, {254, 254, 254},
+   {254, 254, 254}, {253, 253, 253}, {253, 253, 253}, {253, 253, 253}, {253, 253, 253},
+   {252, 252, 252}, {252, 252, 252}, {252, 252, 252}, {252, 252, 252}, {251, 251, 251},
+   {251, 251, 251}, {251, 251, 251}, {251, 251, 251}, {250, 250, 250}, {250, 250, 250},
+   {250, 250, 250}, {250, 250, 250}, {249, 249, 249}, {249, 249, 249}, {249, 249, 249},
+   {249, 249, 249}, {248, 248, 248}, {248, 248, 248}, {248, 248, 248}, {248, 248, 248},
+   {247, 247, 247}, {247, 247, 247}, {247, 247, 247}, {247, 247, 247}, {246, 246, 246},
+   {246, 246, 246}, {246, 246, 246}, {246, 246, 246}, {245, 245, 245}, {245, 245, 245},
+   {245, 245, 245}, {245, 245, 245}, {244, 244, 244}, {244, 244, 244}, {244, 244, 244},
+   {244, 244, 244}, {243, 243, 243}, {243, 243, 243}, {243, 243, 243}, {242, 242, 242},
+   {242, 242, 242}, {242, 242, 242}, {242, 242, 242}, {241, 241, 241}, {241, 241, 241},
+   {241, 241, 241}, {241, 241, 241}, {240, 240, 240}, {240, 240, 240}, {240, 240, 240},
+   {240, 240, 240}, {239, 239, 239}, {239, 239, 239}, {239, 239, 239}, {239, 239, 239},
+   {238, 238, 238}, {238, 238, 238}, {238, 238, 238}, {238, 238, 238}, {237, 237, 237},
+   {237, 237, 237}, {237, 237, 237}, {237, 237, 237}, {236, 236, 236}, {236, 236, 236},
+   {236, 236, 236}, {236, 236, 236}, {235, 235, 235}, {235, 235, 235}, {235, 235, 235},
+   {235, 235, 235}, {234, 234, 234}, {234, 234, 234}, {234, 234, 234}, {234, 234, 234},
+   {233, 233, 233}, {233, 233, 233}, {233, 233, 233}, {233, 233, 233}, {232, 232, 232},
+   {232, 232, 232}, {232, 232, 232}, {232, 232, 232}, {231, 231, 231}, {231, 231, 231},
+   {231, 231, 231}, {230, 230, 230}, {230, 230, 230}, {230, 230, 230}, {230, 230, 230},
+   {229, 229, 229}, {229, 229, 229}, {229, 229, 229}, {229, 229, 229}, {228, 228, 228},
+   {228, 228, 228}, {228, 228, 228}, {228, 228, 228}, {227, 227, 227}, {227, 227, 227},
+   {227, 227, 227}, {227, 227, 227}, {226, 226, 226}, {226, 226, 226}, {226, 226, 226},
+   {226, 226, 226}, {225, 225, 225}, {225, 225, 225}, {225, 225, 225}, {225, 225, 225},
+   {224, 224, 224}, {224, 224, 224}, {224, 224, 224}, {224, 224, 224}, {223, 223, 223},
+   {223, 223, 223}, {223, 223, 223}, {223, 223, 223}, {222, 222, 222}, {222, 222, 222},
+   {222, 222, 222}, {222, 222, 222}, {221, 221, 221}, {221, 221, 221}, {221, 221, 221},
+   {221, 221, 221}, {220, 220, 220}, {220, 220, 220}, {220, 220, 220}, {220, 220, 220},
+   {219, 219, 219}, {219, 219, 219}, {219, 219, 219}, {218, 218, 218}, {218, 218, 218},
+   {218, 218, 218}, {218, 218, 218}, {217, 217, 217}, {217, 217, 217}, {217, 217, 217},
+   {217, 217, 217}, {216, 216, 216}, {216, 216, 216}, {216, 216, 216}, {216, 216, 216},
+   {215, 215, 215}, {215, 215, 215}, {215, 215, 215}, {215, 215, 215}, {214, 214, 214},
+   {214, 214, 214}, {214, 214, 214}, {214, 214, 214}, {213, 213, 213}, {213, 213, 213},
+   {213, 213, 213}, {213, 213, 213}, {212, 212, 212}, {212, 212, 212}, {212, 212, 212},
+   {212, 212, 212}, {211, 211, 211}, {211, 211, 211}, {211, 211, 211}, {211, 211, 211},
+   {210, 210, 210}, {210, 210, 210}, {210, 210, 210}, {210, 210, 210}, {209, 209, 209},
+   {209, 209, 209}, {209, 209, 209}, {209, 209, 209}, {208, 208, 208}, {208, 208, 208},
+   {208, 208, 208}, {208, 208, 208}, {207, 207, 207}, {207, 207, 207}, {207, 207, 207},
+   {207, 207, 207}, {206, 206, 206}, {206, 206, 206}, {206, 206, 206}, {205, 205, 205},
+   {205, 205, 205}, {205, 205, 205}, {205, 205, 205}, {204, 204, 204}, {204, 204, 204},
+   {204, 204, 204}, {204, 204, 204}, {203, 203, 203}, {203, 203, 203}, {203, 203, 203},
+   {203, 203, 203}, {202, 202, 202}, {202, 202, 202}, {202, 202, 202}, {202, 202, 202},
+   {201, 201, 201}, {201, 201, 201}, {201, 201, 201}, {201, 201, 201}, {200, 200, 200},
+   {200, 200, 200}, {200, 200, 200}, {200, 200, 200}, {199, 199, 199}, {199, 199, 199},
+   {199, 199, 199}, {199, 199, 199}, {198, 198, 198}, {198, 198, 198}, {198, 198, 198},
+   {198, 198, 198}, {197, 197, 197}, {197, 197, 197}, {197, 197, 197}, {197, 197, 197},
+   {196, 196, 196}, {196, 196, 196}, {196, 196, 196}, {196, 196, 196}, {195, 195, 195},
+   {195, 195, 195}, {195, 195, 195}, {195, 195, 195}, {194, 194, 194}, {194, 194, 194},
+   {194, 194, 194}, {193, 193, 193}, {193, 193, 193}, {193, 193, 193}, {193, 193, 193},
+   {192, 192, 192}, {192, 192, 192}, {192, 192, 192}, {192, 192, 192}, {191, 191, 191},
+   {191, 191, 191}, {191, 191, 191}, {191, 191, 191}, {190, 190, 190}, {190, 190, 190},
+   {190, 190, 190}, {190, 190, 190}, {189, 189, 189}, {189, 189, 189}, {189, 189, 189},
+   {189, 189, 189}, {188, 188, 188}, {188, 188, 188}, {188, 188, 188}, {188, 188, 188},
+   {187, 187, 187}, {187, 187, 187}, {187, 187, 187}, {187, 187, 187}, {186, 186, 186},
+   {186, 186, 186}, {186, 186, 186}, {186, 186, 186}, {185, 185, 185}, {185, 185, 185},
+   {185, 185, 185}, {185, 185, 185}, {184, 184, 184}, {184, 184, 184}, {184, 184, 184},
+   {184, 184, 184}, {183, 183, 183}, {183, 183, 183}, {183, 183, 183}, {183, 183, 183},
+   {182, 182, 182}, {182, 182, 182}, {182, 182, 182}, {181, 181, 181}, {181, 181, 181},
+   {181, 181, 181}, {181, 181, 181}, {180, 180, 180}, {180, 180, 180}, {180, 180, 180},
+   {180, 180, 180}, {179, 179, 179}, {179, 179, 179}, {179, 179, 179}, {179, 179, 179},
+   {178, 178, 178}, {178, 178, 178}, {178, 178, 178}, {178, 178, 178}, {177, 177, 177},
+   {177, 177, 177}, {177, 177, 177}, {177, 177, 177}, {176, 176, 176}, {176, 176, 176},
+   {176, 176, 176}, {176, 176, 176}, {175, 175, 175}, {175, 175, 175}, {175, 175, 175},
+   {175, 175, 175}, {174, 174, 174}, {174, 174, 174}, {174, 174, 174}, {174, 174, 174},
+   {173, 173, 173}, {173, 173, 173}, {173, 173, 173}, {173, 173, 173}, {172, 172, 172},
+   {172, 172, 172}, {172, 172, 172}, {172, 172, 172}, {171, 171, 171}, {171, 171, 171},
+   {171, 171, 171}, {171, 171, 171}, {170, 170, 170}, {170, 170, 170}, {170, 170, 170},
+   {169, 169, 169}, {169, 169, 169}, {169, 169, 169}, {169, 169, 169}, {168, 168, 168},
+   {168, 168, 168}, {168, 168, 168}, {168, 168, 168}, {167, 167, 167}, {167, 167, 167},
+   {167, 167, 167}, {167, 167, 167}, {166, 166, 166}, {166, 166, 166}, {166, 166, 166},
+   {166, 166, 166}, {165, 165, 165}, {165, 165, 165}, {165, 165, 165}, {165, 165, 165},
+   {164, 164, 164}, {164, 164, 164}, {164, 164, 164}, {164, 164, 164}, {163, 163, 163},
+   {163, 163, 163}, {163, 163, 163}, {163, 163, 163}, {162, 162, 162}, {162, 162, 162},
+   {162, 162, 162}, {162, 162, 162}, {161, 161, 161}, {161, 161, 161}, {161, 161, 161},
+   {161, 161, 161}, {160, 160, 160}, {160, 160, 160}, {160, 160, 160}, {160, 160, 160},
+   {159, 159, 159}, {159, 159, 159}, {159, 159, 159}, {159, 159, 159}, {158, 158, 158},
+   {158, 158, 158}, {158, 158, 158}, {157, 157, 157}, {157, 157, 157}, {157, 157, 157},
+   {157, 157, 157}, {156, 156, 156}, {156, 156, 156}, {156, 156, 156}, {156, 156, 156},
+   {155, 155, 155}, {155, 155, 155}, {155, 155, 155}, {155, 155, 155}, {154, 154, 154},
+   {154, 154, 154}, {154, 154, 154}, {154, 154, 154}, {153, 153, 153}, {153, 153, 153},
+   {153, 153, 153}, {153, 153, 153}, {152, 152, 152}, {152, 152, 152}, {152, 152, 152},
+   {152, 152, 152}, {151, 151, 151}, {151, 151, 151}, {151, 151, 151}, {151, 151, 151},
+   {150, 150, 150}, {150, 150, 150}, {150, 150, 150}, {150, 150, 150}, {149, 149, 149},
+   {149, 149, 149}, {149, 149, 149}, {149, 149, 149}, {148, 148, 148}, {148, 148, 148},
+   {148, 148, 148}, {148, 148, 148}, {147, 147, 147}, {147, 147, 147}, {147, 147, 147},
+   {147, 147, 147}, {146, 146, 146}, {146, 146, 146}, {146, 146, 146}, {145, 145, 145},
+   {145, 145, 145}, {145, 145, 145}, {145, 145, 145}, {144, 144, 144}, {144, 144, 144},
+   {144, 144, 144}, {144, 144, 144}, {143, 143, 143}, {143, 143, 143}, {143, 143, 143},
+   {143, 143, 143}, {142, 142, 142}, {142, 142, 142}, {142, 142, 142}, {142, 142, 142},
+   {141, 141, 141}, {141, 141, 141}, {141, 141, 141}, {141, 141, 141}, {140, 140, 140},
+   {140, 140, 140}, {140, 140, 140}, {140, 140, 140}, {139, 139, 139}, {139, 139, 139},
+   {139, 139, 139}, {139, 139, 139}, {138, 138, 138}, {138, 138, 138}, {138, 138, 138},
+   {138, 138, 138}, {137, 137, 137}, {137, 137, 137}, {137, 137, 137}, {137, 137, 137},
+   {136, 136, 136}, {136, 136, 136}, {136, 136, 136}, {136, 136, 136}, {135, 135, 135},
+   {135, 135, 135}, {135, 135, 135}, {135, 135, 135}, {134, 134, 134}, {134, 134, 134},
+   {134, 134, 134}, {133, 133, 133}, {133, 133, 133}, {133, 133, 133}, {133, 133, 133},
+   {132, 132, 132}, {132, 132, 132}, {132, 132, 132}, {132, 132, 132}, {131, 131, 131},
+   {131, 131, 131}, {131, 131, 131}, {131, 131, 131}, {130, 130, 130}, {130, 130, 130},
+   {130, 130, 130}, {130, 130, 130}, {129, 129, 129}, {129, 129, 129}, {129, 129, 129},
+   {129, 129, 129}, {128, 128, 128}, {128, 128, 128}, {128, 128, 128}, {128, 128, 128},
+   {127, 127, 127}, {127, 127, 127}, {127, 127, 127}, {127, 127, 127}, {126, 126, 126},
+   {126, 126, 126}, {126, 126, 126}, {126, 126, 126}, {125, 125, 125}, {125, 125, 125},
+   {125, 125, 125}, {125, 125, 125}, {124, 124, 124}, {124, 124, 124}, {124, 124, 124},
+   {124, 124, 124}, {123, 123, 123}, {123, 123, 123}, {123, 123, 123}, {123, 123, 123},
+   {122, 122, 122}, {122, 122, 122}, {122, 122, 122}, {122, 122, 122}, {121, 121, 121},
+   {121, 121, 121}, {121, 121, 121}, {120, 120, 120}, {120, 120, 120}, {120, 120, 120},
+   {120, 120, 120}, {119, 119, 119}, {119, 119, 119}, {119, 119, 119}, {119, 119, 119},
+   {118, 118, 118}, {118, 118, 118}, {118, 118, 118}, {118, 118, 118}, {117, 117, 117},
+   {117, 117, 117}, {117, 117, 117}, {117, 117, 117}, {116, 116, 116}, {116, 116, 116},
+   {116, 116, 116}, {116, 116, 116}, {115, 115, 115}, {115, 115, 115}, {115, 115, 115},
+   {115, 115, 115}, {114, 114, 114}, {114, 114, 114}, {114, 114, 114}, {114, 114, 114},
+   {113, 113, 113}, {113, 113, 113}, {113, 113, 113}, {113, 113, 113}, {112, 112, 112},
+   {112, 112, 112}, {112, 112, 112}, {112, 112, 112}, {111, 111, 111}, {111, 111, 111},
+   {111, 111, 111}, {111, 111, 111}, {110, 110, 110}, {110, 110, 110}, {110, 110, 110},
+   {110, 110, 110}, {109, 109, 109}, {109, 109, 109}, {109, 109, 109}, {108, 108, 108},
+   {108, 108, 108}, {108, 108, 108}, {108, 108, 108}, {107, 107, 107}, {107, 107, 107},
+   {107, 107, 107}, {107, 107, 107}, {106, 106, 106}, {106, 106, 106}, {106, 106, 106},
+   {106, 106, 106}, {105, 105, 105}, {105, 105, 105}, {105, 105, 105}, {105, 105, 105},
+   {104, 104, 104}, {104, 104, 104}, {104, 104, 104}, {104, 104, 104}, {103, 103, 103},
+   {103, 103, 103}, {103, 103, 103}, {103, 103, 103}, {102, 102, 102}, {102, 102, 102},
+   {102, 102, 102}, {102, 102, 102}, {101, 101, 101}, {101, 101, 101}, {101, 101, 101},
+   {101, 101, 101}, {100, 100, 100}, {100, 100, 100}, {100, 100, 100}, {100, 100, 100},
+   { 99,  99,  99}, { 99,  99,  99}, { 99,  99,  99}, { 99,  99,  99}, { 98,  98,  98},
+   { 98,  98,  98}, { 98,  98,  98}, { 98,  98,  98}, { 97,  97,  97}, { 97,  97,  97},
+   { 97,  97,  97}, { 96,  96,  96}, { 96,  96,  96}, { 96,  96,  96}, { 96,  96,  96},
+   { 95,  95,  95}, { 95,  95,  95}, { 95,  95,  95}, { 95,  95,  95}, { 94,  94,  94},
+   { 94,  94,  94}, { 94,  94,  94}, { 94,  94,  94}, { 93,  93,  93}, { 93,  93,  93},
+   { 93,  93,  93}, { 93,  93,  93}, { 92,  92,  92}, { 92,  92,  92}, { 92,  92,  92},
+   { 92,  92,  92}, { 91,  91,  91}, { 91,  91,  91}, { 91,  91,  91}, { 91,  91,  91},
+   { 90,  90,  90}, { 90,  90,  90}, { 90,  90,  90}, { 90,  90,  90}, { 89,  89,  89},
+   { 89,  89,  89}, { 89,  89,  89}, { 89,  89,  89}, { 88,  88,  88}, { 88,  88,  88},
+   { 88,  88,  88}, { 88,  88,  88}, { 87,  87,  87}, { 87,  87,  87}, { 87,  87,  87},
+   { 87,  87,  87}, { 86,  86,  86}, { 86,  86,  86}, { 86,  86,  86}, { 86,  86,  86},
+   { 85,  85,  85}, { 85,  85,  85}, { 85,  85,  85}, { 84,  84,  84}, { 84,  84,  84},
+   { 84,  84,  84}, { 84,  84,  84}, { 83,  83,  83}, { 83,  83,  83}, { 83,  83,  83},
+   { 83,  83,  83}, { 82,  82,  82}, { 82,  82,  82}, { 82,  82,  82}, { 82,  82,  82},
+   { 81,  81,  81}, { 81,  81,  81}, { 81,  81,  81}, { 81,  81,  81}, { 80,  80,  80},
+   { 80,  80,  80}, { 80,  80,  80}, { 80,  80,  80}, { 79,  79,  79}, { 79,  79,  79},
+   { 79,  79,  79}, { 79,  79,  79}, { 78,  78,  78}, { 78,  78,  78}, { 78,  78,  78},
+   { 78,  78,  78}, { 77,  77,  77}, { 77,  77,  77}, { 77,  77,  77}, { 77,  77,  77},
+   { 76,  76,  76}, { 76,  76,  76}, { 76,  76,  76}, { 76,  76,  76}, { 75,  75,  75},
+   { 75,  75,  75}, { 75,  75,  75}, { 75,  75,  75}, { 74,  74,  74}, { 74,  74,  74},
+   { 74,  74,  74}, { 74,  74,  74}, { 73,  73,  73}, { 73,  73,  73}, { 73,  73,  73},
+   { 72,  72,  72}, { 72,  72,  72}, { 72,  72,  72}, { 72,  72,  72}, { 71,  71,  71},
+   { 71,  71,  71}, { 71,  71,  71}, { 71,  71,  71}, { 70,  70,  70}, { 70,  70,  70},
+   { 70,  70,  70}, { 70,  70,  70}, { 69,  69,  69}, { 69,  69,  69}, { 69,  69,  69},
+   { 69,  69,  69}, { 68,  68,  68}, { 68,  68,  68}, { 68,  68,  68}, { 68,  68,  68},
+   { 67,  67,  67}, { 67,  67,  67}, { 67,  67,  67}, { 67,  67,  67}, { 66,  66,  66},
+   { 66,  66,  66}, { 66,  66,  66}, { 66,  66,  66}, { 65,  65,  65}, { 65,  65,  65},
+   { 65,  65,  65}, { 65,  65,  65}, { 64,  64,  64}, { 64,  64,  64}, { 64,  64,  64},
+   { 64,  64,  64}, { 63,  63,  63}, { 63,  63,  63}, { 63,  63,  63}, { 63,  63,  63},
+   { 62,  62,  62}, { 62,  62,  62}, { 62,  62,  62}, { 62,  62,  62}, { 61,  61,  61},
+   { 61,  61,  61}, { 61,  61,  61}, { 60,  60,  60}, { 60,  60,  60}, { 60,  60,  60},
+   { 60,  60,  60}, { 59,  59,  59}, { 59,  59,  59}, { 59,  59,  59}, { 59,  59,  59},
+   { 58,  58,  58}, { 58,  58,  58}, { 58,  58,  58}, { 58,  58,  58}, { 57,  57,  57},
+   { 57,  57,  57}, { 57,  57,  57}, { 57,  57,  57}, { 56,  56,  56}, { 56,  56,  56},
+   { 56,  56,  56}, { 56,  56,  56}, { 55,  55,  55}, { 55,  55,  55}, { 55,  55,  55},
+   { 55,  55,  55}, { 54,  54,  54}, { 54,  54,  54}, { 54,  54,  54}, { 54,  54,  54},
+   { 53,  53,  53}, { 53,  53,  53}, { 53,  53,  53}, { 53,  53,  53}, { 52,  52,  52},
+   { 52,  52,  52}, { 52,  52,  52}, { 52,  52,  52}, { 51,  51,  51}, { 51,  51,  51},
+   { 51,  51,  51}, { 51,  51,  51}, { 50,  50,  50}, { 50,  50,  50}, { 50,  50,  50},
+   { 50,  50,  50}, { 49,  49,  49}, { 49,  49,  49}, { 49,  49,  49}, { 48,  48,  48},
+   { 48,  48,  48}, { 48,  48,  48}, { 48,  48,  48}, { 47,  47,  47}, { 47,  47,  47},
+   { 47,  47,  47}, { 47,  47,  47}, { 46,  46,  46}, { 46,  46,  46}, { 46,  46,  46},
+   { 46,  46,  46}, { 45,  45,  45}, { 45,  45,  45}, { 45,  45,  45}, { 45,  45,  45},
+   { 44,  44,  44}, { 44,  44,  44}, { 44,  44,  44}, { 44,  44,  44}, { 43,  43,  43},
+   { 43,  43,  43}, { 43,  43,  43}, { 43,  43,  43}, { 42,  42,  42}, { 42,  42,  42},
+   { 42,  42,  42}, { 42,  42,  42}, { 41,  41,  41}, { 41,  41,  41}, { 41,  41,  41},
+   { 41,  41,  41}, { 40,  40,  40}, { 40,  40,  40}, { 40,  40,  40}, { 40,  40,  40},
+   { 39,  39,  39}, { 39,  39,  39}, { 39,  39,  39}, { 39,  39,  39}, { 38,  38,  38},
+   { 38,  38,  38}, { 38,  38,  38}, { 38,  38,  38}, { 37,  37,  37}, { 37,  37,  37},
+   { 37,  37,  37}, { 37,  37,  37}, { 36,  36,  36}, { 36,  36,  36}, { 36,  36,  36},
+   { 35,  35,  35}, { 35,  35,  35}, { 35,  35,  35}, { 35,  35,  35}, { 34,  34,  34},
+   { 34,  34,  34}, { 34,  34,  34}, { 34,  34,  34}, { 33,  33,  33}, { 33,  33,  33},
+   { 33,  33,  33}, { 33,  33,  33}, { 32,  32,  32}, { 32,  32,  32}, { 32,  32,  32},
+   { 32,  32,  32}, { 31,  31,  31}, { 31,  31,  31}, { 31,  31,  31}, { 31,  31,  31},
+   { 30,  30,  30}, { 30,  30,  30}, { 30,  30,  30}, { 30,  30,  30}, { 29,  29,  29},
+   { 29,  29,  29}, { 29,  29,  29}, { 29,  29,  29}, { 28,  28,  28}, { 28,  28,  28},
+   { 28,  28,  28}, { 28,  28,  28}, { 27,  27,  27}, { 27,  27,  27}, { 27,  27,  27},
+   { 27,  27,  27}, { 26,  26,  26}, { 26,  26,  26}, { 26,  26,  26}, { 26,  26,  26},
+   { 25,  25,  25}, { 25,  25,  25}, { 25,  25,  25}, { 25,  25,  25}, { 24,  24,  24},
+   { 24,  24,  24}, { 24,  24,  24}, { 23,  23,  23}, { 23,  23,  23}, { 23,  23,  23},
+   { 23,  23,  23}, { 22,  22,  22}, { 22,  22,  22}, { 22,  22,  22}, { 22,  22,  22},
+   { 21,  21,  21}, { 21,  21,  21}, { 21,  21,  21}, { 21,  21,  21}, { 20,  20,  20},
+   { 20,  20,  20}, { 20,  20,  20}, { 20,  20,  20}, { 19,  19,  19}, { 19,  19,  19},
+   { 19,  19,  19}, { 19,  19,  19}, { 18,  18,  18}, { 18,  18,  18}, { 18,  18,  18},
+   { 18,  18,  18}, { 17,  17,  17}, { 17,  17,  17}, { 17,  17,  17}, { 17,  17,  17},
+   { 16,  16,  16}, { 16,  16,  16}, { 16,  16,  16}, { 16,  16,  16}, { 15,  15,  15},
+   { 15,  15,  15}, { 15,  15,  15}, { 15,  15,  15}, { 14,  14,  14}, { 14,  14,  14},
+   { 14,  14,  14}, { 14,  14,  14}, { 13,  13,  13}, { 13,  13,  13}, { 13,  13,  13},
+   { 13,  13,  13}, { 12,  12,  12}, { 12,  12,  12}, { 12,  12,  12}, { 11,  11,  11},
+   { 11,  11,  11}, { 11,  11,  11}, { 11,  11,  11}, { 10,  10,  10}, { 10,  10,  10},
+   { 10,  10,  10}, { 10,  10,  10}, {  9,   9,   9}, {  9,   9,   9}, {  9,   9,   9},
+   {  9,   9,   9}, {  8,   8,   8}, {  8,   8,   8}, {  8,   8,   8}, {  8,   8,   8},
+   {  7,   7,   7}, {  7,   7,   7}, {  7,   7,   7}, {  7,   7,   7}, {  6,   6,   6},
+   {  6,   6,   6}, {  6,   6,   6}, {  6,   6,   6}, {  5,   5,   5}, {  5,   5,   5},
+   {  5,   5,   5}, {  5,   5,   5}, {  4,   4,   4}, {  4,   4,   4}, {  4,   4,   4},
+   {  4,   4,   4}, {  3,   3,   3}, {  3,   3,   3}, {  3,   3,   3}, {  3,   3,   3},
+   {  2,   2,   2}, {  2,   2,   2}, {  2,   2,   2}, {  2,   2,   2}, {  1,   1,   1},
+   {  1,   1,   1}, {  1,   1,   1}, {  1,   1,   1}, {  0,   0,   0}, {  0,   0,   0}
+};
+
+const rgb_t hot_colormap[1000] = {
+   { 11,   0,   0}, { 11,   0,   0}, { 12,   0,   0}, { 13,   0,   0}, { 13,   0,   0},
+   { 14,   0,   0}, { 15,   0,   0}, { 15,   0,   0}, { 16,   0,   0}, { 17,   0,   0},
+   { 17,   0,   0}, { 18,   0,   0}, { 19,   0,   0}, { 19,   0,   0}, { 20,   0,   0},
+   { 21,   0,   0}, { 21,   0,   0}, { 22,   0,   0}, { 23,   0,   0}, { 23,   0,   0},
+   { 24,   0,   0}, { 25,   0,   0}, { 25,   0,   0}, { 26,   0,   0}, { 27,   0,   0},
+   { 27,   0,   0}, { 28,   0,   0}, { 29,   0,   0}, { 29,   0,   0}, { 30,   0,   0},
+   { 31,   0,   0}, { 31,   0,   0}, { 32,   0,   0}, { 33,   0,   0}, { 33,   0,   0},
+   { 34,   0,   0}, { 35,   0,   0}, { 35,   0,   0}, { 36,   0,   0}, { 37,   0,   0},
+   { 37,   0,   0}, { 38,   0,   0}, { 39,   0,   0}, { 39,   0,   0}, { 40,   0,   0},
+   { 41,   0,   0}, { 41,   0,   0}, { 42,   0,   0}, { 43,   0,   0}, { 43,   0,   0},
+   { 44,   0,   0}, { 45,   0,   0}, { 45,   0,   0}, { 46,   0,   0}, { 47,   0,   0},
+   { 47,   0,   0}, { 48,   0,   0}, { 49,   0,   0}, { 49,   0,   0}, { 50,   0,   0},
+   { 51,   0,   0}, { 51,   0,   0}, { 52,   0,   0}, { 53,   0,   0}, { 54,   0,   0},
+   { 54,   0,   0}, { 55,   0,   0}, { 56,   0,   0}, { 56,   0,   0}, { 57,   0,   0},
+   { 58,   0,   0}, { 58,   0,   0}, { 59,   0,   0}, { 60,   0,   0}, { 60,   0,   0},
+   { 61,   0,   0}, { 62,   0,   0}, { 62,   0,   0}, { 63,   0,   0}, { 64,   0,   0},
+   { 64,   0,   0}, { 65,   0,   0}, { 66,   0,   0}, { 66,   0,   0}, { 67,   0,   0},
+   { 68,   0,   0}, { 68,   0,   0}, { 69,   0,   0}, { 70,   0,   0}, { 70,   0,   0},
+   { 71,   0,   0}, { 72,   0,   0}, { 72,   0,   0}, { 73,   0,   0}, { 74,   0,   0},
+   { 74,   0,   0}, { 75,   0,   0}, { 76,   0,   0}, { 76,   0,   0}, { 77,   0,   0},
+   { 78,   0,   0}, { 78,   0,   0}, { 79,   0,   0}, { 80,   0,   0}, { 80,   0,   0},
+   { 81,   0,   0}, { 82,   0,   0}, { 82,   0,   0}, { 83,   0,   0}, { 84,   0,   0},
+   { 84,   0,   0}, { 85,   0,   0}, { 86,   0,   0}, { 86,   0,   0}, { 87,   0,   0},
+   { 88,   0,   0}, { 88,   0,   0}, { 89,   0,   0}, { 90,   0,   0}, { 90,   0,   0},
+   { 91,   0,   0}, { 92,   0,   0}, { 92,   0,   0}, { 93,   0,   0}, { 94,   0,   0},
+   { 94,   0,   0}, { 95,   0,   0}, { 96,   0,   0}, { 96,   0,   0}, { 97,   0,   0},
+   { 98,   0,   0}, { 98,   0,   0}, { 99,   0,   0}, {100,   0,   0}, {100,   0,   0},
+   {101,   0,   0}, {102,   0,   0}, {102,   0,   0}, {103,   0,   0}, {104,   0,   0},
+   {104,   0,   0}, {105,   0,   0}, {106,   0,   0}, {106,   0,   0}, {107,   0,   0},
+   {108,   0,   0}, {108,   0,   0}, {109,   0,   0}, {110,   0,   0}, {110,   0,   0},
+   {111,   0,   0}, {112,   0,   0}, {112,   0,   0}, {113,   0,   0}, {114,   0,   0},
+   {114,   0,   0}, {115,   0,   0}, {116,   0,   0}, {116,   0,   0}, {117,   0,   0},
+   {118,   0,   0}, {119,   0,   0}, {119,   0,   0}, {120,   0,   0}, {121,   0,   0},
+   {121,   0,   0}, {122,   0,   0}, {123,   0,   0}, {123,   0,   0}, {124,   0,   0},
+   {125,   0,   0}, {125,   0,   0}, {126,   0,   0}, {127,   0,   0}, {127,   0,   0},
+   {128,   0,   0}, {129,   0,   0}, {129,   0,   0}, {130,   0,   0}, {131,   0,   0},
+   {131,   0,   0}, {132,   0,   0}, {133,   0,   0}, {133,   0,   0}, {134,   0,   0},
+   {135,   0,   0}, {135,   0,   0}, {136,   0,   0}, {137,   0,   0}, {137,   0,   0},
+   {138,   0,   0}, {139,   0,   0}, {139,   0,   0}, {140,   0,   0}, {141,   0,   0},
+   {141,   0,   0}, {142,   0,   0}, {143,   0,   0}, {143,   0,   0}, {144,   0,   0},
+   {145,   0,   0}, {145,   0,   0}, {146,   0,   0}, {147,   0,   0}, {147,   0,   0},
+   {148,   0,   0}, {149,   0,   0}, {149,   0,   0}, {150,   0,   0}, {151,   0,   0},
+   {151,   0,   0}, {152,   0,   0}, {153,   0,   0}, {153,   0,   0}, {154,   0,   0},
+   {155,   0,   0}, {155,   0,   0}, {156,   0,   0}, {157,   0,   0}, {157,   0,   0},
+   {158,   0,   0}, {159,   0,   0}, {159,   0,   0}, {160,   0,   0}, {161,   0,   0},
+   {161,   0,   0}, {162,   0,   0}, {163,   0,   0}, {163,   0,   0}, {164,   0,   0},
+   {165,   0,   0}, {165,   0,   0}, {166,   0,   0}, {167,   0,   0}, {167,   0,   0},
+   {168,   0,   0}, {169,   0,   0}, {169,   0,   0}, {170,   0,   0}, {171,   0,   0},
+   {171,   0,   0}, {172,   0,   0}, {173,   0,   0}, {173,   0,   0}, {174,   0,   0},
+   {175,   0,   0}, {175,   0,   0}, {176,   0,   0}, {177,   0,   0}, {177,   0,   0},
+   {178,   0,   0}, {179,   0,   0}, {179,   0,   0}, {180,   0,   0}, {181,   0,   0},
+   {181,   0,   0}, {182,   0,   0}, {183,   0,   0}, {183,   0,   0}, {184,   0,   0},
+   {185,   0,   0}, {186,   0,   0}, {186,   0,   0}, {187,   0,   0}, {188,   0,   0},
+   {188,   0,   0}, {189,   0,   0}, {190,   0,   0}, {190,   0,   0}, {191,   0,   0},
+   {192,   0,   0}, {192,   0,   0}, {193,   0,   0}, {194,   0,   0}, {194,   0,   0},
+   {195,   0,   0}, {196,   0,   0}, {196,   0,   0}, {197,   0,   0}, {198,   0,   0},
+   {198,   0,   0}, {199,   0,   0}, {200,   0,   0}, {200,   0,   0}, {201,   0,   0},
+   {202,   0,   0}, {202,   0,   0}, {203,   0,   0}, {204,   0,   0}, {204,   0,   0},
+   {205,   0,   0}, {206,   0,   0}, {206,   0,   0}, {207,   0,   0}, {208,   0,   0},
+   {208,   0,   0}, {209,   0,   0}, {210,   0,   0}, {210,   0,   0}, {211,   0,   0},
+   {212,   0,   0}, {212,   0,   0}, {213,   0,   0}, {214,   0,   0}, {214,   0,   0},
+   {215,   0,   0}, {216,   0,   0}, {216,   0,   0}, {217,   0,   0}, {218,   0,   0},
+   {218,   0,   0}, {219,   0,   0}, {220,   0,   0}, {220,   0,   0}, {221,   0,   0},
+   {222,   0,   0}, {222,   0,   0}, {223,   0,   0}, {224,   0,   0}, {224,   0,   0},
+   {225,   0,   0}, {226,   0,   0}, {226,   0,   0}, {227,   0,   0}, {228,   0,   0},
+   {228,   0,   0}, {229,   0,   0}, {230,   0,   0}, {230,   0,   0}, {231,   0,   0},
+   {232,   0,   0}, {232,   0,   0}, {233,   0,   0}, {234,   0,   0}, {234,   0,   0},
+   {235,   0,   0}, {236,   0,   0}, {236,   0,   0}, {237,   0,   0}, {238,   0,   0},
+   {238,   0,   0}, {239,   0,   0}, {240,   0,   0}, {240,   0,   0}, {241,   0,   0},
+   {242,   0,   0}, {242,   0,   0}, {243,   0,   0}, {244,   0,   0}, {244,   0,   0},
+   {245,   0,   0}, {246,   0,   0}, {246,   0,   0}, {247,   0,   0}, {248,   0,   0},
+   {248,   0,   0}, {249,   0,   0}, {250,   0,   0}, {251,   0,   0}, {251,   0,   0},
+   {252,   0,   0}, {253,   0,   0}, {253,   0,   0}, {254,   0,   0}, {255,   0,   0},
+   {255,   0,   0}, {255,   1,   0}, {255,   2,   0}, {255,   2,   0}, {255,   3,   0},
+   {255,   4,   0}, {255,   4,   0}, {255,   5,   0}, {255,   6,   0}, {255,   6,   0},
+   {255,   7,   0}, {255,   8,   0}, {255,   8,   0}, {255,   9,   0}, {255,  10,   0},
+   {255,  10,   0}, {255,  11,   0}, {255,  12,   0}, {255,  12,   0}, {255,  13,   0},
+   {255,  14,   0}, {255,  14,   0}, {255,  15,   0}, {255,  16,   0}, {255,  16,   0},
+   {255,  17,   0}, {255,  18,   0}, {255,  18,   0}, {255,  19,   0}, {255,  20,   0},
+   {255,  20,   0}, {255,  21,   0}, {255,  22,   0}, {255,  22,   0}, {255,  23,   0},
+   {255,  24,   0}, {255,  24,   0}, {255,  25,   0}, {255,  26,   0}, {255,  26,   0},
+   {255,  27,   0}, {255,  28,   0}, {255,  28,   0}, {255,  29,   0}, {255,  30,   0},
+   {255,  30,   0}, {255,  31,   0}, {255,  32,   0}, {255,  32,   0}, {255,  33,   0},
+   {255,  34,   0}, {255,  34,   0}, {255,  35,   0}, {255,  36,   0}, {255,  36,   0},
+   {255,  37,   0}, {255,  38,   0}, {255,  38,   0}, {255,  39,   0}, {255,  40,   0},
+   {255,  40,   0}, {255,  41,   0}, {255,  42,   0}, {255,  42,   0}, {255,  43,   0},
+   {255,  44,   0}, {255,  44,   0}, {255,  45,   0}, {255,  46,   0}, {255,  46,   0},
+   {255,  47,   0}, {255,  48,   0}, {255,  48,   0}, {255,  49,   0}, {255,  50,   0},
+   {255,  50,   0}, {255,  51,   0}, {255,  52,   0}, {255,  52,   0}, {255,  53,   0},
+   {255,  54,   0}, {255,  54,   0}, {255,  55,   0}, {255,  56,   0}, {255,  56,   0},
+   {255,  57,   0}, {255,  58,   0}, {255,  58,   0}, {255,  59,   0}, {255,  60,   0},
+   {255,  60,   0}, {255,  61,   0}, {255,  62,   0}, {255,  63,   0}, {255,  63,   0},
+   {255,  64,   0}, {255,  65,   0}, {255,  65,   0}, {255,  66,   0}, {255,  67,   0},
+   {255,  67,   0}, {255,  68,   0}, {255,  69,   0}, {255,  69,   0}, {255,  70,   0},
+   {255,  71,   0}, {255,  71,   0}, {255,  72,   0}, {255,  73,   0}, {255,  73,   0},
+   {255,  74,   0}, {255,  75,   0}, {255,  75,   0}, {255,  76,   0}, {255,  77,   0},
+   {255,  77,   0}, {255,  78,   0}, {255,  79,   0}, {255,  79,   0}, {255,  80,   0},
+   {255,  81,   0}, {255,  81,   0}, {255,  82,   0}, {255,  83,   0}, {255,  83,   0},
+   {255,  84,   0}, {255,  85,   0}, {255,  85,   0}, {255,  86,   0}, {255,  87,   0},
+   {255,  87,   0}, {255,  88,   0}, {255,  89,   0}, {255,  89,   0}, {255,  90,   0},
+   {255,  91,   0}, {255,  91,   0}, {255,  92,   0}, {255,  93,   0}, {255,  93,   0},
+   {255,  94,   0}, {255,  95,   0}, {255,  95,   0}, {255,  96,   0}, {255,  97,   0},
+   {255,  97,   0}, {255,  98,   0}, {255,  99,   0}, {255,  99,   0}, {255, 100,   0},
+   {255, 101,   0}, {255, 101,   0}, {255, 102,   0}, {255, 103,   0}, {255, 103,   0},
+   {255, 104,   0}, {255, 105,   0}, {255, 105,   0}, {255, 106,   0}, {255, 107,   0},
+   {255, 107,   0}, {255, 108,   0}, {255, 109,   0}, {255, 109,   0}, {255, 110,   0},
+   {255, 111,   0}, {255, 111,   0}, {255, 112,   0}, {255, 113,   0}, {255, 113,   0},
+   {255, 114,   0}, {255, 115,   0}, {255, 115,   0}, {255, 116,   0}, {255, 117,   0},
+   {255, 117,   0}, {255, 118,   0}, {255, 119,   0}, {255, 119,   0}, {255, 120,   0},
+   {255, 121,   0}, {255, 121,   0}, {255, 122,   0}, {255, 123,   0}, {255, 123,   0},
+   {255, 124,   0}, {255, 125,   0}, {255, 125,   0}, {255, 126,   0}, {255, 127,   0},
+   {255, 128,   0}, {255, 128,   0}, {255, 129,   0}, {255, 130,   0}, {255, 130,   0},
+   {255, 131,   0}, {255, 132,   0}, {255, 132,   0}, {255, 133,   0}, {255, 134,   0},
+   {255, 134,   0}, {255, 135,   0}, {255, 136,   0}, {255, 136,   0}, {255, 137,   0},
+   {255, 138,   0}, {255, 138,   0}, {255, 139,   0}, {255, 140,   0}, {255, 140,   0},
+   {255, 141,   0}, {255, 142,   0}, {255, 142,   0}, {255, 143,   0}, {255, 144,   0},
+   {255, 144,   0}, {255, 145,   0}, {255, 146,   0}, {255, 146,   0}, {255, 147,   0},
+   {255, 148,   0}, {255, 148,   0}, {255, 149,   0}, {255, 150,   0}, {255, 150,   0},
+   {255, 151,   0}, {255, 152,   0}, {255, 152,   0}, {255, 153,   0}, {255, 154,   0},
+   {255, 154,   0}, {255, 155,   0}, {255, 156,   0}, {255, 156,   0}, {255, 157,   0},
+   {255, 158,   0}, {255, 158,   0}, {255, 159,   0}, {255, 160,   0}, {255, 160,   0},
+   {255, 161,   0}, {255, 162,   0}, {255, 162,   0}, {255, 163,   0}, {255, 164,   0},
+   {255, 164,   0}, {255, 165,   0}, {255, 166,   0}, {255, 166,   0}, {255, 167,   0},
+   {255, 168,   0}, {255, 168,   0}, {255, 169,   0}, {255, 170,   0}, {255, 170,   0},
+   {255, 171,   0}, {255, 172,   0}, {255, 172,   0}, {255, 173,   0}, {255, 174,   0},
+   {255, 174,   0}, {255, 175,   0}, {255, 176,   0}, {255, 176,   0}, {255, 177,   0},
+   {255, 178,   0}, {255, 178,   0}, {255, 179,   0}, {255, 180,   0}, {255, 180,   0},
+   {255, 181,   0}, {255, 182,   0}, {255, 182,   0}, {255, 183,   0}, {255, 184,   0},
+   {255, 184,   0}, {255, 185,   0}, {255, 186,   0}, {255, 186,   0}, {255, 187,   0},
+   {255, 188,   0}, {255, 188,   0}, {255, 189,   0}, {255, 190,   0}, {255, 190,   0},
+   {255, 191,   0}, {255, 192,   0}, {255, 192,   0}, {255, 193,   0}, {255, 194,   0},
+   {255, 195,   0}, {255, 195,   0}, {255, 196,   0}, {255, 197,   0}, {255, 197,   0},
+   {255, 198,   0}, {255, 199,   0}, {255, 199,   0}, {255, 200,   0}, {255, 201,   0},
+   {255, 201,   0}, {255, 202,   0}, {255, 203,   0}, {255, 203,   0}, {255, 204,   0},
+   {255, 205,   0}, {255, 205,   0}, {255, 206,   0}, {255, 207,   0}, {255, 207,   0},
+   {255, 208,   0}, {255, 209,   0}, {255, 209,   0}, {255, 210,   0}, {255, 211,   0},
+   {255, 211,   0}, {255, 212,   0}, {255, 213,   0}, {255, 213,   0}, {255, 214,   0},
+   {255, 215,   0}, {255, 215,   0}, {255, 216,   0}, {255, 217,   0}, {255, 217,   0},
+   {255, 218,   0}, {255, 219,   0}, {255, 219,   0}, {255, 220,   0}, {255, 221,   0},
+   {255, 221,   0}, {255, 222,   0}, {255, 223,   0}, {255, 223,   0}, {255, 224,   0},
+   {255, 225,   0}, {255, 225,   0}, {255, 226,   0}, {255, 227,   0}, {255, 227,   0},
+   {255, 228,   0}, {255, 229,   0}, {255, 229,   0}, {255, 230,   0}, {255, 231,   0},
+   {255, 231,   0}, {255, 232,   0}, {255, 233,   0}, {255, 233,   0}, {255, 234,   0},
+   {255, 235,   0}, {255, 235,   0}, {255, 236,   0}, {255, 237,   0}, {255, 237,   0},
+   {255, 238,   0}, {255, 239,   0}, {255, 239,   0}, {255, 240,   0}, {255, 241,   0},
+   {255, 241,   0}, {255, 242,   0}, {255, 243,   0}, {255, 243,   0}, {255, 244,   0},
+   {255, 245,   0}, {255, 245,   0}, {255, 246,   0}, {255, 247,   0}, {255, 247,   0},
+   {255, 248,   0}, {255, 249,   0}, {255, 249,   0}, {255, 250,   0}, {255, 251,   0},
+   {255, 251,   0}, {255, 252,   0}, {255, 253,   0}, {255, 253,   0}, {255, 254,   0},
+   {255, 255,   0}, {255, 255,   1}, {255, 255,   2}, {255, 255,   3}, {255, 255,   4},
+   {255, 255,   5}, {255, 255,   6}, {255, 255,   7}, {255, 255,   8}, {255, 255,   9},
+   {255, 255,  10}, {255, 255,  11}, {255, 255,  12}, {255, 255,  13}, {255, 255,  14},
+   {255, 255,  15}, {255, 255,  16}, {255, 255,  17}, {255, 255,  18}, {255, 255,  19},
+   {255, 255,  20}, {255, 255,  21}, {255, 255,  22}, {255, 255,  23}, {255, 255,  24},
+   {255, 255,  25}, {255, 255,  26}, {255, 255,  27}, {255, 255,  28}, {255, 255,  29},
+   {255, 255,  30}, {255, 255,  31}, {255, 255,  32}, {255, 255,  33}, {255, 255,  34},
+   {255, 255,  35}, {255, 255,  36}, {255, 255,  37}, {255, 255,  38}, {255, 255,  39},
+   {255, 255,  40}, {255, 255,  41}, {255, 255,  42}, {255, 255,  43}, {255, 255,  44},
+   {255, 255,  45}, {255, 255,  46}, {255, 255,  47}, {255, 255,  48}, {255, 255,  49},
+   {255, 255,  50}, {255, 255,  51}, {255, 255,  52}, {255, 255,  53}, {255, 255,  54},
+   {255, 255,  55}, {255, 255,  56}, {255, 255,  57}, {255, 255,  58}, {255, 255,  59},
+   {255, 255,  60}, {255, 255,  61}, {255, 255,  62}, {255, 255,  63}, {255, 255,  64},
+   {255, 255,  65}, {255, 255,  66}, {255, 255,  67}, {255, 255,  68}, {255, 255,  69},
+   {255, 255,  70}, {255, 255,  71}, {255, 255,  72}, {255, 255,  73}, {255, 255,  74},
+   {255, 255,  75}, {255, 255,  76}, {255, 255,  77}, {255, 255,  78}, {255, 255,  79},
+   {255, 255,  80}, {255, 255,  81}, {255, 255,  82}, {255, 255,  83}, {255, 255,  84},
+   {255, 255,  85}, {255, 255,  86}, {255, 255,  87}, {255, 255,  88}, {255, 255,  89},
+   {255, 255,  90}, {255, 255,  91}, {255, 255,  92}, {255, 255,  93}, {255, 255,  94},
+   {255, 255,  95}, {255, 255,  96}, {255, 255,  97}, {255, 255,  98}, {255, 255,  99},
+   {255, 255, 100}, {255, 255, 101}, {255, 255, 102}, {255, 255, 103}, {255, 255, 104},
+   {255, 255, 105}, {255, 255, 106}, {255, 255, 107}, {255, 255, 108}, {255, 255, 109},
+   {255, 255, 110}, {255, 255, 111}, {255, 255, 112}, {255, 255, 113}, {255, 255, 114},
+   {255, 255, 115}, {255, 255, 116}, {255, 255, 117}, {255, 255, 118}, {255, 255, 119},
+   {255, 255, 120}, {255, 255, 121}, {255, 255, 122}, {255, 255, 123}, {255, 255, 124},
+   {255, 255, 125}, {255, 255, 126}, {255, 255, 127}, {255, 255, 128}, {255, 255, 129},
+   {255, 255, 130}, {255, 255, 131}, {255, 255, 132}, {255, 255, 133}, {255, 255, 134},
+   {255, 255, 135}, {255, 255, 136}, {255, 255, 137}, {255, 255, 138}, {255, 255, 139},
+   {255, 255, 140}, {255, 255, 141}, {255, 255, 142}, {255, 255, 143}, {255, 255, 144},
+   {255, 255, 145}, {255, 255, 146}, {255, 255, 147}, {255, 255, 148}, {255, 255, 149},
+   {255, 255, 150}, {255, 255, 151}, {255, 255, 152}, {255, 255, 153}, {255, 255, 154},
+   {255, 255, 155}, {255, 255, 157}, {255, 255, 158}, {255, 255, 159}, {255, 255, 160},
+   {255, 255, 161}, {255, 255, 162}, {255, 255, 163}, {255, 255, 164}, {255, 255, 165},
+   {255, 255, 166}, {255, 255, 167}, {255, 255, 168}, {255, 255, 169}, {255, 255, 170},
+   {255, 255, 171}, {255, 255, 172}, {255, 255, 173}, {255, 255, 174}, {255, 255, 175},
+   {255, 255, 176}, {255, 255, 177}, {255, 255, 178}, {255, 255, 179}, {255, 255, 180},
+   {255, 255, 181}, {255, 255, 182}, {255, 255, 183}, {255, 255, 184}, {255, 255, 185},
+   {255, 255, 186}, {255, 255, 187}, {255, 255, 188}, {255, 255, 189}, {255, 255, 190},
+   {255, 255, 191}, {255, 255, 192}, {255, 255, 193}, {255, 255, 194}, {255, 255, 195},
+   {255, 255, 196}, {255, 255, 197}, {255, 255, 198}, {255, 255, 199}, {255, 255, 200},
+   {255, 255, 201}, {255, 255, 202}, {255, 255, 203}, {255, 255, 204}, {255, 255, 205},
+   {255, 255, 206}, {255, 255, 207}, {255, 255, 208}, {255, 255, 209}, {255, 255, 210},
+   {255, 255, 211}, {255, 255, 212}, {255, 255, 213}, {255, 255, 214}, {255, 255, 215},
+   {255, 255, 216}, {255, 255, 217}, {255, 255, 218}, {255, 255, 219}, {255, 255, 220},
+   {255, 255, 221}, {255, 255, 222}, {255, 255, 223}, {255, 255, 224}, {255, 255, 225},
+   {255, 255, 226}, {255, 255, 227}, {255, 255, 228}, {255, 255, 229}, {255, 255, 230},
+   {255, 255, 231}, {255, 255, 232}, {255, 255, 233}, {255, 255, 234}, {255, 255, 235},
+   {255, 255, 236}, {255, 255, 237}, {255, 255, 238}, {255, 255, 239}, {255, 255, 240},
+   {255, 255, 241}, {255, 255, 242}, {255, 255, 243}, {255, 255, 244}, {255, 255, 245},
+   {255, 255, 246}, {255, 255, 247}, {255, 255, 248}, {255, 255, 249}, {255, 255, 250},
+   {255, 255, 251}, {255, 255, 252}, {255, 255, 253}, {255, 255, 254}, {255, 255, 255}
+};
+
+const rgb_t hsv_colormap[1000] = {
+   {255,   0,   0}, {255,   2,   0}, {255,   3,   0}, {255,   5,   0}, {255,   6,   0},
+   {255,   8,   0}, {255,   9,   0}, {255,  11,   0}, {255,  12,   0}, {255,  14,   0},
+   {255,  15,   0}, {255,  17,   0}, {255,  18,   0}, {255,  20,   0}, {255,  21,   0},
+   {255,  23,   0}, {255,  24,   0}, {255,  26,   0}, {255,  27,   0}, {255,  29,   0},
+   {255,  30,   0}, {255,  32,   0}, {255,  33,   0}, {255,  35,   0}, {255,  36,   0},
+   {255,  38,   0}, {255,  39,   0}, {255,  41,   0}, {255,  42,   0}, {255,  44,   0},
+   {255,  45,   0}, {255,  47,   0}, {255,  48,   0}, {255,  50,   0}, {255,  51,   0},
+   {255,  53,   0}, {255,  54,   0}, {255,  56,   0}, {255,  57,   0}, {255,  59,   0},
+   {255,  60,   0}, {255,  62,   0}, {255,  63,   0}, {255,  65,   0}, {255,  66,   0},
+   {255,  68,   0}, {255,  69,   0}, {255,  71,   0}, {255,  72,   0}, {255,  74,   0},
+   {255,  75,   0}, {255,  77,   0}, {255,  78,   0}, {255,  80,   0}, {255,  81,   0},
+   {255,  83,   0}, {255,  84,   0}, {255,  86,   0}, {255,  87,   0}, {255,  89,   0},
+   {255,  90,   0}, {255,  92,   0}, {255,  93,   0}, {255,  95,   0}, {255,  96,   0},
+   {255,  98,   0}, {255, 100,   0}, {255, 101,   0}, {255, 103,   0}, {255, 104,   0},
+   {255, 106,   0}, {255, 107,   0}, {255, 109,   0}, {255, 110,   0}, {255, 112,   0},
+   {255, 113,   0}, {255, 115,   0}, {255, 116,   0}, {255, 118,   0}, {255, 119,   0},
+   {255, 121,   0}, {255, 122,   0}, {255, 124,   0}, {255, 125,   0}, {255, 127,   0},
+   {255, 128,   0}, {255, 130,   0}, {255, 131,   0}, {255, 133,   0}, {255, 134,   0},
+   {255, 136,   0}, {255, 137,   0}, {255, 139,   0}, {255, 140,   0}, {255, 142,   0},
+   {255, 143,   0}, {255, 145,   0}, {255, 146,   0}, {255, 148,   0}, {255, 149,   0},
+   {255, 151,   0}, {255, 152,   0}, {255, 154,   0}, {255, 155,   0}, {255, 157,   0},
+   {255, 158,   0}, {255, 160,   0}, {255, 161,   0}, {255, 163,   0}, {255, 164,   0},
+   {255, 166,   0}, {255, 167,   0}, {255, 169,   0}, {255, 170,   0}, {255, 172,   0},
+   {255, 173,   0}, {255, 175,   0}, {255, 176,   0}, {255, 178,   0}, {255, 179,   0},
+   {255, 181,   0}, {255, 182,   0}, {255, 184,   0}, {255, 185,   0}, {255, 187,   0},
+   {255, 188,   0}, {255, 190,   0}, {255, 191,   0}, {255, 193,   0}, {255, 194,   0},
+   {255, 196,   0}, {255, 197,   0}, {255, 199,   0}, {255, 201,   0}, {255, 202,   0},
+   {255, 204,   0}, {255, 205,   0}, {255, 207,   0}, {255, 208,   0}, {255, 210,   0},
+   {255, 211,   0}, {255, 213,   0}, {255, 214,   0}, {255, 216,   0}, {255, 217,   0},
+   {255, 219,   0}, {255, 220,   0}, {255, 222,   0}, {255, 223,   0}, {255, 225,   0},
+   {255, 226,   0}, {255, 228,   0}, {255, 229,   0}, {255, 231,   0}, {255, 232,   0},
+   {255, 234,   0}, {255, 235,   0}, {255, 237,   0}, {255, 238,   0}, {255, 239,   0},
+   {254, 240,   0}, {254, 242,   0}, {253, 243,   0}, {253, 244,   0}, {252, 245,   0},
+   {252, 246,   0}, {251, 247,   0}, {251, 248,   0}, {250, 249,   0}, {250, 250,   0},
+   {249, 251,   0}, {249, 252,   0}, {248, 253,   0}, {248, 254,   0}, {247, 255,   0},
+   {246, 255,   0}, {245, 255,   0}, {243, 255,   0}, {242, 255,   0}, {240, 255,   0},
+   {239, 255,   0}, {237, 255,   0}, {236, 255,   0}, {234, 255,   0}, {233, 255,   0},
+   {231, 255,   0}, {230, 255,   0}, {228, 255,   0}, {227, 255,   0}, {225, 255,   0},
+   {224, 255,   0}, {222, 255,   0}, {221, 255,   0}, {219, 255,   0}, {218, 255,   0},
+   {216, 255,   0}, {215, 255,   0}, {213, 255,   0}, {211, 255,   0}, {210, 255,   0},
+   {208, 255,   0}, {207, 255,   0}, {205, 255,   0}, {204, 255,   0}, {202, 255,   0},
+   {201, 255,   0}, {199, 255,   0}, {198, 255,   0}, {196, 255,   0}, {195, 255,   0},
+   {193, 255,   0}, {192, 255,   0}, {190, 255,   0}, {189, 255,   0}, {187, 255,   0},
+   {186, 255,   0}, {184, 255,   0}, {183, 255,   0}, {181, 255,   0}, {180, 255,   0},
+   {178, 255,   0}, {177, 255,   0}, {175, 255,   0}, {174, 255,   0}, {172, 255,   0},
+   {171, 255,   0}, {169, 255,   0}, {168, 255,   0}, {166, 255,   0}, {165, 255,   0},
+   {163, 255,   0}, {162, 255,   0}, {160, 255,   0}, {159, 255,   0}, {157, 255,   0},
+   {156, 255,   0}, {154, 255,   0}, {153, 255,   0}, {151, 255,   0}, {150, 255,   0},
+   {148, 255,   0}, {147, 255,   0}, {145, 255,   0}, {144, 255,   0}, {142, 255,   0},
+   {141, 255,   0}, {139, 255,   0}, {138, 255,   0}, {136, 255,   0}, {135, 255,   0},
+   {133, 255,   0}, {132, 255,   0}, {130, 255,   0}, {129, 255,   0}, {127, 255,   0},
+   {126, 255,   0}, {124, 255,   0}, {123, 255,   0}, {121, 255,   0}, {120, 255,   0},
+   {118, 255,   0}, {117, 255,   0}, {115, 255,   0}, {114, 255,   0}, {112, 255,   0},
+   {110, 255,   0}, {109, 255,   0}, {107, 255,   0}, {106, 255,   0}, {104, 255,   0},
+   {103, 255,   0}, {101, 255,   0}, {100, 255,   0}, { 98, 255,   0}, { 97, 255,   0},
+   { 95, 255,   0}, { 94, 255,   0}, { 92, 255,   0}, { 91, 255,   0}, { 89, 255,   0},
+   { 88, 255,   0}, { 86, 255,   0}, { 85, 255,   0}, { 83, 255,   0}, { 82, 255,   0},
+   { 80, 255,   0}, { 79, 255,   0}, { 77, 255,   0}, { 76, 255,   0}, { 74, 255,   0},
+   { 73, 255,   0}, { 71, 255,   0}, { 70, 255,   0}, { 68, 255,   0}, { 67, 255,   0},
+   { 65, 255,   0}, { 64, 255,   0}, { 62, 255,   0}, { 61, 255,   0}, { 59, 255,   0},
+   { 58, 255,   0}, { 56, 255,   0}, { 55, 255,   0}, { 53, 255,   0}, { 52, 255,   0},
+   { 50, 255,   0}, { 49, 255,   0}, { 47, 255,   0}, { 46, 255,   0}, { 44, 255,   0},
+   { 43, 255,   0}, { 41, 255,   0}, { 40, 255,   0}, { 38, 255,   0}, { 37, 255,   0},
+   { 35, 255,   0}, { 34, 255,   0}, { 32, 255,   0}, { 31, 255,   0}, { 29, 255,   0},
+   { 28, 255,   0}, { 26, 255,   0}, { 25, 255,   0}, { 23, 255,   0}, { 22, 255,   0},
+   { 20, 255,   0}, { 19, 255,   0}, { 17, 255,   0}, { 16, 255,   0}, { 14, 255,   0},
+   { 12, 255,   0}, { 11, 255,   0}, {  9, 255,   0}, {  8, 255,   0}, {  7, 255,   1},
+   {  7, 255,   2}, {  6, 255,   3}, {  6, 255,   4}, {  5, 255,   5}, {  5, 255,   6},
+   {  4, 255,   7}, {  4, 255,   8}, {  3, 255,   9}, {  3, 255,  10}, {  2, 255,  11},
+   {  2, 255,  12}, {  1, 255,  13}, {  1, 255,  14}, {  0, 255,  15}, {  0, 255,  16},
+   {  0, 255,  18}, {  0, 255,  19}, {  0, 255,  21}, {  0, 255,  22}, {  0, 255,  24},
+   {  0, 255,  25}, {  0, 255,  27}, {  0, 255,  28}, {  0, 255,  30}, {  0, 255,  31},
+   {  0, 255,  33}, {  0, 255,  34}, {  0, 255,  36}, {  0, 255,  37}, {  0, 255,  39},
+   {  0, 255,  40}, {  0, 255,  42}, {  0, 255,  43}, {  0, 255,  45}, {  0, 255,  46},
+   {  0, 255,  48}, {  0, 255,  49}, {  0, 255,  51}, {  0, 255,  52}, {  0, 255,  54},
+   {  0, 255,  55}, {  0, 255,  57}, {  0, 255,  58}, {  0, 255,  60}, {  0, 255,  61},
+   {  0, 255,  63}, {  0, 255,  64}, {  0, 255,  66}, {  0, 255,  67}, {  0, 255,  69},
+   {  0, 255,  70}, {  0, 255,  72}, {  0, 255,  73}, {  0, 255,  75}, {  0, 255,  76},
+   {  0, 255,  78}, {  0, 255,  79}, {  0, 255,  81}, {  0, 255,  82}, {  0, 255,  84},
+   {  0, 255,  86}, {  0, 255,  87}, {  0, 255,  89}, {  0, 255,  90}, {  0, 255,  92},
+   {  0, 255,  93}, {  0, 255,  95}, {  0, 255,  96}, {  0, 255,  98}, {  0, 255,  99},
+   {  0, 255, 101}, {  0, 255, 102}, {  0, 255, 104}, {  0, 255, 105}, {  0, 255, 107},
+   {  0, 255, 108}, {  0, 255, 110}, {  0, 255, 111}, {  0, 255, 113}, {  0, 255, 114},
+   {  0, 255, 116}, {  0, 255, 117}, {  0, 255, 119}, {  0, 255, 120}, {  0, 255, 122},
+   {  0, 255, 123}, {  0, 255, 125}, {  0, 255, 126}, {  0, 255, 128}, {  0, 255, 129},
+   {  0, 255, 131}, {  0, 255, 132}, {  0, 255, 134}, {  0, 255, 135}, {  0, 255, 137},
+   {  0, 255, 138}, {  0, 255, 140}, {  0, 255, 141}, {  0, 255, 143}, {  0, 255, 144},
+   {  0, 255, 146}, {  0, 255, 147}, {  0, 255, 149}, {  0, 255, 150}, {  0, 255, 152},
+   {  0, 255, 153}, {  0, 255, 155}, {  0, 255, 156}, {  0, 255, 158}, {  0, 255, 159},
+   {  0, 255, 161}, {  0, 255, 162}, {  0, 255, 164}, {  0, 255, 165}, {  0, 255, 167},
+   {  0, 255, 168}, {  0, 255, 170}, {  0, 255, 171}, {  0, 255, 173}, {  0, 255, 174},
+   {  0, 255, 176}, {  0, 255, 177}, {  0, 255, 179}, {  0, 255, 180}, {  0, 255, 182},
+   {  0, 255, 183}, {  0, 255, 185}, {  0, 255, 187}, {  0, 255, 188}, {  0, 255, 190},
+   {  0, 255, 191}, {  0, 255, 193}, {  0, 255, 194}, {  0, 255, 196}, {  0, 255, 197},
+   {  0, 255, 199}, {  0, 255, 200}, {  0, 255, 202}, {  0, 255, 203}, {  0, 255, 205},
+   {  0, 255, 206}, {  0, 255, 208}, {  0, 255, 209}, {  0, 255, 211}, {  0, 255, 212},
+   {  0, 255, 214}, {  0, 255, 215}, {  0, 255, 217}, {  0, 255, 218}, {  0, 255, 220},
+   {  0, 255, 221}, {  0, 255, 223}, {  0, 255, 224}, {  0, 255, 226}, {  0, 255, 227},
+   {  0, 255, 229}, {  0, 255, 230}, {  0, 255, 232}, {  0, 255, 233}, {  0, 255, 235},
+   {  0, 255, 236}, {  0, 255, 238}, {  0, 255, 239}, {  0, 255, 241}, {  0, 255, 242},
+   {  0, 255, 244}, {  0, 255, 245}, {  0, 255, 247}, {  0, 255, 248}, {  0, 255, 250},
+   {  0, 255, 251}, {  0, 255, 253}, {  0, 255, 254}, {  0, 254, 255}, {  0, 253, 255},
+   {  0, 251, 255}, {  0, 250, 255}, {  0, 248, 255}, {  0, 247, 255}, {  0, 245, 255},
+   {  0, 244, 255}, {  0, 242, 255}, {  0, 241, 255}, {  0, 239, 255}, {  0, 238, 255},
+   {  0, 236, 255}, {  0, 235, 255}, {  0, 233, 255}, {  0, 232, 255}, {  0, 230, 255},
+   {  0, 229, 255}, {  0, 227, 255}, {  0, 225, 255}, {  0, 224, 255}, {  0, 222, 255},
+   {  0, 221, 255}, {  0, 219, 255}, {  0, 218, 255}, {  0, 216, 255}, {  0, 215, 255},
+   {  0, 213, 255}, {  0, 212, 255}, {  0, 210, 255}, {  0, 209, 255}, {  0, 207, 255},
+   {  0, 206, 255}, {  0, 204, 255}, {  0, 203, 255}, {  0, 201, 255}, {  0, 200, 255},
+   {  0, 198, 255}, {  0, 197, 255}, {  0, 195, 255}, {  0, 194, 255}, {  0, 192, 255},
+   {  0, 191, 255}, {  0, 189, 255}, {  0, 188, 255}, {  0, 186, 255}, {  0, 185, 255},
+   {  0, 183, 255}, {  0, 182, 255}, {  0, 180, 255}, {  0, 179, 255}, {  0, 177, 255},
+   {  0, 176, 255}, {  0, 174, 255}, {  0, 173, 255}, {  0, 171, 255}, {  0, 170, 255},
+   {  0, 168, 255}, {  0, 167, 255}, {  0, 165, 255}, {  0, 164, 255}, {  0, 162, 255},
+   {  0, 161, 255}, {  0, 159, 255}, {  0, 158, 255}, {  0, 156, 255}, {  0, 155, 255},
+   {  0, 153, 255}, {  0, 152, 255}, {  0, 150, 255}, {  0, 149, 255}, {  0, 147, 255},
+   {  0, 146, 255}, {  0, 144, 255}, {  0, 143, 255}, {  0, 141, 255}, {  0, 140, 255},
+   {  0, 138, 255}, {  0, 137, 255}, {  0, 135, 255}, {  0, 134, 255}, {  0, 132, 255},
+   {  0, 131, 255}, {  0, 129, 255}, {  0, 128, 255}, {  0, 126, 255}, {  0, 124, 255},
+   {  0, 123, 255}, {  0, 121, 255}, {  0, 120, 255}, {  0, 118, 255}, {  0, 117, 255},
+   {  0, 115, 255}, {  0, 114, 255}, {  0, 112, 255}, {  0, 111, 255}, {  0, 109, 255},
+   {  0, 108, 255}, {  0, 106, 255}, {  0, 105, 255}, {  0, 103, 255}, {  0, 102, 255},
+   {  0, 100, 255}, {  0,  99, 255}, {  0,  97, 255}, {  0,  96, 255}, {  0,  94, 255},
+   {  0,  93, 255}, {  0,  91, 255}, {  0,  90, 255}, {  0,  88, 255}, {  0,  87, 255},
+   {  0,  85, 255}, {  0,  84, 255}, {  0,  82, 255}, {  0,  81, 255}, {  0,  79, 255},
+   {  0,  78, 255}, {  0,  76, 255}, {  0,  75, 255}, {  0,  73, 255}, {  0,  72, 255},
+   {  0,  70, 255}, {  0,  69, 255}, {  0,  67, 255}, {  0,  66, 255}, {  0,  64, 255},
+   {  0,  63, 255}, {  0,  61, 255}, {  0,  60, 255}, {  0,  58, 255}, {  0,  57, 255},
+   {  0,  55, 255}, {  0,  54, 255}, {  0,  52, 255}, {  0,  51, 255}, {  0,  49, 255},
+   {  0,  48, 255}, {  0,  46, 255}, {  0,  45, 255}, {  0,  43, 255}, {  0,  42, 255},
+   {  0,  40, 255}, {  0,  39, 255}, {  0,  37, 255}, {  0,  36, 255}, {  0,  34, 255},
+   {  0,  33, 255}, {  0,  31, 255}, {  0,  30, 255}, {  0,  28, 255}, {  0,  26, 255},
+   {  0,  25, 255}, {  0,  23, 255}, {  0,  22, 255}, {  0,  20, 255}, {  0,  19, 255},
+   {  0,  17, 255}, {  0,  16, 255}, {  1,  15, 255}, {  1,  14, 255}, {  2,  13, 255},
+   {  2,  12, 255}, {  3,  11, 255}, {  3,  10, 255}, {  4,   9, 255}, {  4,   8, 255},
+   {  5,   7, 255}, {  5,   6, 255}, {  6,   5, 255}, {  6,   4, 255}, {  7,   3, 255},
+   {  7,   2, 255}, {  8,   1, 255}, {  8,   0, 255}, { 10,   0, 255}, { 11,   0, 255},
+   { 13,   0, 255}, { 14,   0, 255}, { 16,   0, 255}, { 17,   0, 255}, { 19,   0, 255},
+   { 20,   0, 255}, { 22,   0, 255}, { 23,   0, 255}, { 25,   0, 255}, { 26,   0, 255},
+   { 28,   0, 255}, { 29,   0, 255}, { 31,   0, 255}, { 32,   0, 255}, { 34,   0, 255},
+   { 35,   0, 255}, { 37,   0, 255}, { 38,   0, 255}, { 40,   0, 255}, { 41,   0, 255},
+   { 43,   0, 255}, { 44,   0, 255}, { 46,   0, 255}, { 47,   0, 255}, { 49,   0, 255},
+   { 50,   0, 255}, { 52,   0, 255}, { 53,   0, 255}, { 55,   0, 255}, { 56,   0, 255},
+   { 58,   0, 255}, { 59,   0, 255}, { 61,   0, 255}, { 62,   0, 255}, { 64,   0, 255},
+   { 65,   0, 255}, { 67,   0, 255}, { 68,   0, 255}, { 70,   0, 255}, { 72,   0, 255},
+   { 73,   0, 255}, { 75,   0, 255}, { 76,   0, 255}, { 78,   0, 255}, { 79,   0, 255},
+   { 81,   0, 255}, { 82,   0, 255}, { 84,   0, 255}, { 85,   0, 255}, { 87,   0, 255},
+   { 88,   0, 255}, { 90,   0, 255}, { 91,   0, 255}, { 93,   0, 255}, { 94,   0, 255},
+   { 96,   0, 255}, { 97,   0, 255}, { 99,   0, 255}, {100,   0, 255}, {102,   0, 255},
+   {103,   0, 255}, {105,   0, 255}, {106,   0, 255}, {108,   0, 255}, {109,   0, 255},
+   {111,   0, 255}, {112,   0, 255}, {114,   0, 255}, {115,   0, 255}, {117,   0, 255},
+   {118,   0, 255}, {120,   0, 255}, {121,   0, 255}, {123,   0, 255}, {124,   0, 255},
+   {126,   0, 255}, {127,   0, 255}, {129,   0, 255}, {130,   0, 255}, {132,   0, 255},
+   {133,   0, 255}, {135,   0, 255}, {136,   0, 255}, {138,   0, 255}, {139,   0, 255},
+   {141,   0, 255}, {142,   0, 255}, {144,   0, 255}, {145,   0, 255}, {147,   0, 255},
+   {148,   0, 255}, {150,   0, 255}, {151,   0, 255}, {153,   0, 255}, {154,   0, 255},
+   {156,   0, 255}, {157,   0, 255}, {159,   0, 255}, {160,   0, 255}, {162,   0, 255},
+   {163,   0, 255}, {165,   0, 255}, {166,   0, 255}, {168,   0, 255}, {169,   0, 255},
+   {171,   0, 255}, {173,   0, 255}, {174,   0, 255}, {176,   0, 255}, {177,   0, 255},
+   {179,   0, 255}, {180,   0, 255}, {182,   0, 255}, {183,   0, 255}, {185,   0, 255},
+   {186,   0, 255}, {188,   0, 255}, {189,   0, 255}, {191,   0, 255}, {192,   0, 255},
+   {194,   0, 255}, {195,   0, 255}, {197,   0, 255}, {198,   0, 255}, {200,   0, 255},
+   {201,   0, 255}, {203,   0, 255}, {204,   0, 255}, {206,   0, 255}, {207,   0, 255},
+   {209,   0, 255}, {210,   0, 255}, {212,   0, 255}, {213,   0, 255}, {215,   0, 255},
+   {216,   0, 255}, {218,   0, 255}, {219,   0, 255}, {221,   0, 255}, {222,   0, 255},
+   {224,   0, 255}, {225,   0, 255}, {227,   0, 255}, {228,   0, 255}, {230,   0, 255},
+   {231,   0, 255}, {233,   0, 255}, {234,   0, 255}, {236,   0, 255}, {237,   0, 255},
+   {239,   0, 255}, {240,   0, 255}, {242,   0, 255}, {243,   0, 255}, {245,   0, 255},
+   {246,   0, 255}, {247,   0, 254}, {248,   0, 253}, {248,   0, 252}, {249,   0, 251},
+   {249,   0, 250}, {250,   0, 249}, {250,   0, 248}, {251,   0, 247}, {251,   0, 246},
+   {252,   0, 245}, {252,   0, 244}, {253,   0, 243}, {253,   0, 242}, {254,   0, 241},
+   {254,   0, 240}, {255,   0, 239}, {255,   0, 238}, {255,   0, 236}, {255,   0, 235},
+   {255,   0, 233}, {255,   0, 232}, {255,   0, 230}, {255,   0, 229}, {255,   0, 227},
+   {255,   0, 226}, {255,   0, 224}, {255,   0, 223}, {255,   0, 221}, {255,   0, 220},
+   {255,   0, 218}, {255,   0, 217}, {255,   0, 215}, {255,   0, 214}, {255,   0, 212},
+   {255,   0, 211}, {255,   0, 209}, {255,   0, 208}, {255,   0, 206}, {255,   0, 205},
+   {255,   0, 203}, {255,   0, 202}, {255,   0, 200}, {255,   0, 199}, {255,   0, 197},
+   {255,   0, 196}, {255,   0, 194}, {255,   0, 193}, {255,   0, 191}, {255,   0, 190},
+   {255,   0, 188}, {255,   0, 187}, {255,   0, 185}, {255,   0, 184}, {255,   0, 182},
+   {255,   0, 181}, {255,   0, 179}, {255,   0, 178}, {255,   0, 176}, {255,   0, 175},
+   {255,   0, 173}, {255,   0, 172}, {255,   0, 170}, {255,   0, 169}, {255,   0, 167},
+   {255,   0, 166}, {255,   0, 164}, {255,   0, 163}, {255,   0, 161}, {255,   0, 160},
+   {255,   0, 158}, {255,   0, 157}, {255,   0, 155}, {255,   0, 154}, {255,   0, 152},
+   {255,   0, 151}, {255,   0, 149}, {255,   0, 148}, {255,   0, 146}, {255,   0, 145},
+   {255,   0, 143}, {255,   0, 141}, {255,   0, 140}, {255,   0, 138}, {255,   0, 137},
+   {255,   0, 135}, {255,   0, 134}, {255,   0, 132}, {255,   0, 131}, {255,   0, 129},
+   {255,   0, 128}, {255,   0, 126}, {255,   0, 125}, {255,   0, 123}, {255,   0, 122},
+   {255,   0, 120}, {255,   0, 119}, {255,   0, 117}, {255,   0, 116}, {255,   0, 114},
+   {255,   0, 113}, {255,   0, 111}, {255,   0, 110}, {255,   0, 108}, {255,   0, 107},
+   {255,   0, 105}, {255,   0, 104}, {255,   0, 102}, {255,   0, 101}, {255,   0,  99},
+   {255,   0,  98}, {255,   0,  96}, {255,   0,  95}, {255,   0,  93}, {255,   0,  92},
+   {255,   0,  90}, {255,   0,  89}, {255,   0,  87}, {255,   0,  86}, {255,   0,  84},
+   {255,   0,  83}, {255,   0,  81}, {255,   0,  80}, {255,   0,  78}, {255,   0,  77},
+   {255,   0,  75}, {255,   0,  74}, {255,   0,  72}, {255,   0,  71}, {255,   0,  69},
+   {255,   0,  68}, {255,   0,  66}, {255,   0,  65}, {255,   0,  63}, {255,   0,  62},
+   {255,   0,  60}, {255,   0,  59}, {255,   0,  57}, {255,   0,  56}, {255,   0,  54},
+   {255,   0,  53}, {255,   0,  51}, {255,   0,  50}, {255,   0,  48}, {255,   0,  47},
+   {255,   0,  45}, {255,   0,  44}, {255,   0,  42}, {255,   0,  40}, {255,   0,  39},
+   {255,   0,  37}, {255,   0,  36}, {255,   0,  34}, {255,   0,  33}, {255,   0,  31},
+   {255,   0,  30}, {255,   0,  28}, {255,   0,  27}, {255,   0,  25}, {255,   0,  24}
+};
+
+const rgb_t jet_colormap[1000] = {
+   { 29,   0, 102}, { 23,   0, 107}, { 17,   0, 112}, { 12,   0, 117}, {  6,   0, 122},
+   {  0,   0, 127}, {  0,   0, 128}, {  0,   0, 129}, {  0,   0, 129}, {  0,   0, 130},
+   {  0,   0, 131}, {  0,   0, 132}, {  0,   0, 133}, {  0,   0, 133}, {  0,   0, 134},
+   {  0,   0, 135}, {  0,   0, 136}, {  0,   0, 137}, {  0,   0, 138}, {  0,   0, 140},
+   {  0,   0, 141}, {  0,   0, 142}, {  0,   0, 143}, {  0,   0, 145}, {  0,   0, 146},
+   {  0,   0, 147}, {  0,   0, 148}, {  0,   0, 150}, {  0,   0, 151}, {  0,   0, 152},
+   {  0,   0, 153}, {  0,   0, 154}, {  0,   0, 156}, {  0,   0, 157}, {  0,   0, 158},
+   {  0,   0, 159}, {  0,   0, 160}, {  0,   0, 161}, {  0,   0, 163}, {  0,   0, 164},
+   {  0,   0, 165}, {  0,   0, 166}, {  0,   0, 168}, {  0,   0, 169}, {  0,   0, 170},
+   {  0,   0, 171}, {  0,   0, 173}, {  0,   0, 174}, {  0,   0, 175}, {  0,   0, 176},
+   {  0,   0, 178}, {  0,   0, 179}, {  0,   0, 180}, {  0,   0, 181}, {  0,   0, 183},
+   {  0,   0, 184}, {  0,   0, 185}, {  0,   0, 186}, {  0,   0, 188}, {  0,   0, 189},
+   {  0,   0, 190}, {  0,   0, 191}, {  0,   0, 193}, {  0,   0, 194}, {  0,   0, 195},
+   {  0,   0, 196}, {  0,   0, 197}, {  0,   0, 198}, {  0,   0, 200}, {  0,   0, 201},
+   {  0,   0, 202}, {  0,   0, 203}, {  0,   0, 204}, {  0,   0, 206}, {  0,   0, 207},
+   {  0,   0, 208}, {  0,   0, 209}, {  0,   0, 211}, {  0,   0, 212}, {  0,   0, 213},
+   {  0,   0, 214}, {  0,   0, 216}, {  0,   0, 217}, {  0,   0, 218}, {  0,   0, 219},
+   {  0,   0, 221}, {  0,   0, 222}, {  0,   0, 223}, {  0,   0, 225}, {  0,   0, 226},
+   {  0,   0, 227}, {  0,   0, 228}, {  0,   0, 230}, {  0,   0, 231}, {  0,   0, 232},
+   {  0,   0, 233}, {  0,   0, 234}, {  0,   0, 234}, {  0,   0, 235}, {  0,   0, 236},
+   {  0,   0, 237}, {  0,   0, 238}, {  0,   0, 239}, {  0,   0, 239}, {  0,   0, 240},
+   {  0,   0, 241}, {  0,   0, 242}, {  0,   0, 243}, {  0,   0, 244}, {  0,   0, 246},
+   {  0,   0, 247}, {  0,   0, 248}, {  0,   0, 249}, {  0,   0, 250}, {  0,   0, 251},
+   {  0,   0, 253}, {  0,   0, 254}, {  0,   0, 254}, {  0,   0, 254}, {  0,   0, 254},
+   {  0,   0, 254}, {  0,   0, 254}, {  0,   0, 255}, {  0,   0, 255}, {  0,   0, 255},
+   {  0,   0, 255}, {  0,   0, 255}, {  0,   0, 255}, {  0,   1, 255}, {  0,   1, 255},
+   {  0,   2, 255}, {  0,   3, 255}, {  0,   3, 255}, {  0,   4, 255}, {  0,   5, 255},
+   {  0,   6, 255}, {  0,   6, 255}, {  0,   7, 255}, {  0,   8, 255}, {  0,   9, 255},
+   {  0,  10, 255}, {  0,  11, 255}, {  0,  12, 255}, {  0,  13, 255}, {  0,  14, 255},
+   {  0,  15, 255}, {  0,  16, 255}, {  0,  17, 255}, {  0,  18, 255}, {  0,  19, 255},
+   {  0,  21, 255}, {  0,  22, 255}, {  0,  23, 255}, {  0,  24, 255}, {  0,  25, 255},
+   {  0,  26, 255}, {  0,  27, 255}, {  0,  28, 255}, {  0,  29, 255}, {  0,  30, 255},
+   {  0,  31, 255}, {  0,  32, 255}, {  0,  34, 255}, {  0,  35, 255}, {  0,  36, 255},
+   {  0,  37, 255}, {  0,  38, 255}, {  0,  39, 255}, {  0,  40, 255}, {  0,  41, 255},
+   {  0,  42, 255}, {  0,  43, 255}, {  0,  44, 255}, {  0,  45, 255}, {  0,  46, 255},
+   {  0,  48, 255}, {  0,  49, 255}, {  0,  50, 255}, {  0,  51, 255}, {  0,  52, 255},
+   {  0,  53, 255}, {  0,  54, 255}, {  0,  55, 255}, {  0,  56, 255}, {  0,  57, 255},
+   {  0,  58, 255}, {  0,  58, 255}, {  0,  59, 255}, {  0,  60, 255}, {  0,  60, 255},
+   {  0,  61, 255}, {  0,  62, 255}, {  0,  63, 255}, {  0,  63, 255}, {  0,  64, 255},
+   {  0,  65, 255}, {  0,  66, 255}, {  0,  67, 255}, {  0,  68, 255}, {  0,  69, 255},
+   {  0,  71, 255}, {  0,  72, 255}, {  0,  73, 255}, {  0,  74, 255}, {  0,  75, 255},
+   {  0,  76, 255}, {  0,  77, 255}, {  0,  78, 255}, {  0,  79, 255}, {  0,  80, 255},
+   {  0,  81, 255}, {  0,  82, 255}, {  0,  84, 255}, {  0,  85, 255}, {  0,  86, 255},
+   {  0,  87, 255}, {  0,  88, 255}, {  0,  89, 255}, {  0,  90, 255}, {  0,  91, 255},
+   {  0,  92, 255}, {  0,  93, 255}, {  0,  94, 255}, {  0,  95, 255}, {  0,  96, 255},
+   {  0,  98, 255}, {  0,  99, 255}, {  0, 100, 255}, {  0, 101, 255}, {  0, 102, 255},
+   {  0, 103, 255}, {  0, 104, 255}, {  0, 105, 255}, {  0, 106, 255}, {  0, 107, 255},
+   {  0, 108, 255}, {  0, 109, 255}, {  0, 111, 255}, {  0, 112, 255}, {  0, 113, 255},
+   {  0, 114, 255}, {  0, 115, 255}, {  0, 116, 255}, {  0, 117, 255}, {  0, 118, 255},
+   {  0, 119, 255}, {  0, 120, 255}, {  0, 121, 255}, {  0, 122, 255}, {  0, 123, 255},
+   {  0, 125, 255}, {  0, 126, 255}, {  0, 127, 255}, {  0, 128, 255}, {  0, 129, 255},
+   {  0, 130, 255}, {  0, 131, 255}, {  0, 132, 255}, {  0, 133, 255}, {  0, 134, 255},
+   {  0, 135, 255}, {  0, 136, 255}, {  0, 138, 255}, {  0, 139, 255}, {  0, 140, 255},
+   {  0, 141, 255}, {  0, 142, 255}, {  0, 143, 255}, {  0, 144, 255}, {  0, 145, 255},
+   {  0, 146, 255}, {  0, 147, 255}, {  0, 148, 255}, {  0, 149, 255}, {  0, 150, 255},
+   {  0, 150, 255}, {  0, 151, 255}, {  0, 152, 255}, {  0, 153, 255}, {  0, 153, 255},
+   {  0, 154, 255}, {  0, 155, 255}, {  0, 155, 255}, {  0, 156, 255}, {  0, 157, 255},
+   {  0, 158, 255}, {  0, 159, 255}, {  0, 161, 255}, {  0, 162, 255}, {  0, 163, 255},
+   {  0, 164, 255}, {  0, 165, 255}, {  0, 166, 255}, {  0, 167, 255}, {  0, 168, 255},
+   {  0, 169, 255}, {  0, 170, 255}, {  0, 171, 255}, {  0, 172, 255}, {  0, 173, 255},
+   {  0, 175, 255}, {  0, 176, 255}, {  0, 177, 255}, {  0, 178, 255}, {  0, 179, 255},
+   {  0, 180, 255}, {  0, 181, 255}, {  0, 182, 255}, {  0, 183, 255}, {  0, 184, 255},
+   {  0, 185, 255}, {  0, 186, 255}, {  0, 188, 255}, {  0, 189, 255}, {  0, 190, 255},
+   {  0, 191, 255}, {  0, 192, 255}, {  0, 193, 255}, {  0, 194, 255}, {  0, 195, 255},
+   {  0, 196, 255}, {  0, 197, 255}, {  0, 198, 255}, {  0, 199, 255}, {  0, 200, 255},
+   {  0, 202, 255}, {  0, 203, 255}, {  0, 204, 255}, {  0, 205, 255}, {  0, 206, 255},
+   {  0, 207, 255}, {  0, 208, 255}, {  0, 209, 255}, {  0, 210, 255}, {  0, 211, 255},
+   {  0, 212, 255}, {  0, 213, 255}, {  0, 215, 255}, {  0, 216, 255}, {  0, 217, 255},
+   {  0, 218, 254}, {  0, 219, 253}, {  0, 220, 252}, {  0, 221, 252}, {  0, 222, 251},
+   {  0, 223, 250}, {  0, 224, 250}, {  0, 225, 249}, {  0, 226, 248}, {  0, 227, 247},
+   {  0, 229, 247}, {  1, 230, 246}, {  2, 231, 245}, {  3, 232, 244}, {  3, 233, 243},
+   {  4, 234, 242}, {  5, 235, 241}, {  5, 236, 240}, {  6, 237, 239}, {  7, 238, 238},
+   {  8, 239, 238}, {  8, 240, 237}, {  9, 241, 236}, { 10, 242, 236}, { 10, 242, 235},
+   { 11, 243, 235}, { 11, 244, 234}, { 12, 245, 234}, { 13, 245, 233}, { 13, 246, 232},
+   { 14, 247, 232}, { 15, 247, 231}, { 15, 248, 231}, { 16, 249, 230}, { 17, 249, 229},
+   { 18, 250, 228}, { 18, 251, 227}, { 19, 251, 226}, { 20, 252, 225}, { 21, 253, 224},
+   { 22, 253, 224}, { 23, 254, 223}, { 23, 254, 222}, { 24, 255, 221}, { 25, 255, 220},
+   { 26, 255, 219}, { 27, 255, 218}, { 28, 255, 218}, { 29, 255, 217}, { 30, 255, 216},
+   { 30, 255, 215}, { 31, 255, 214}, { 32, 255, 214}, { 33, 255, 213}, { 34, 255, 212},
+   { 35, 255, 211}, { 36, 255, 210}, { 37, 255, 209}, { 38, 255, 208}, { 39, 255, 207},
+   { 39, 255, 207}, { 40, 255, 206}, { 41, 255, 205}, { 42, 255, 204}, { 43, 255, 203},
+   { 44, 255, 202}, { 45, 255, 201}, { 46, 255, 200}, { 47, 255, 199}, { 48, 255, 198},
+   { 48, 255, 198}, { 49, 255, 197}, { 50, 255, 196}, { 51, 255, 195}, { 52, 255, 194},
+   { 53, 255, 193}, { 54, 255, 192}, { 55, 255, 191}, { 55, 255, 191}, { 56, 255, 190},
+   { 57, 255, 189}, { 58, 255, 188}, { 59, 255, 187}, { 60, 255, 186}, { 60, 255, 186},
+   { 61, 255, 185}, { 62, 255, 184}, { 63, 255, 183}, { 64, 255, 182}, { 65, 255, 181},
+   { 65, 255, 181}, { 66, 255, 180}, { 67, 255, 179}, { 68, 255, 178}, { 69, 255, 177},
+   { 70, 255, 176}, { 71, 255, 175}, { 72, 255, 174}, { 73, 255, 173}, { 74, 255, 172},
+   { 74, 255, 172}, { 75, 255, 171}, { 76, 255, 170}, { 77, 255, 169}, { 78, 255, 168},
+   { 79, 255, 167}, { 80, 255, 166}, { 81, 255, 165}, { 82, 255, 164}, { 83, 255, 163},
+   { 83, 255, 163}, { 84, 255, 162}, { 84, 255, 162}, { 85, 255, 161}, { 85, 255, 161},
+   { 86, 255, 160}, { 87, 255, 159}, { 87, 255, 159}, { 88, 255, 158}, { 88, 255, 158},
+   { 89, 255, 157}, { 89, 255, 157}, { 90, 255, 156}, { 91, 255, 155}, { 92, 255, 154},
+   { 93, 255, 153}, { 94, 255, 152}, { 95, 255, 151}, { 96, 255, 150}, { 97, 255, 149},
+   { 97, 255, 149}, { 98, 255, 148}, { 99, 255, 147}, {100, 255, 146}, {101, 255, 145},
+   {102, 255, 144}, {102, 255, 143}, {103, 255, 142}, {104, 255, 141}, {105, 255, 140},
+   {106, 255, 140}, {107, 255, 139}, {107, 255, 138}, {108, 255, 137}, {109, 255, 136},
+   {110, 255, 135}, {111, 255, 134}, {112, 255, 134}, {113, 255, 133}, {114, 255, 132},
+   {114, 255, 131}, {115, 255, 130}, {116, 255, 130}, {117, 255, 129}, {118, 255, 128},
+   {119, 255, 127}, {120, 255, 126}, {121, 255, 125}, {122, 255, 124}, {123, 255, 123},
+   {123, 255, 123}, {124, 255, 122}, {125, 255, 121}, {126, 255, 120}, {127, 255, 119},
+   {128, 255, 118}, {129, 255, 117}, {130, 255, 116}, {130, 255, 115}, {131, 255, 114},
+   {132, 255, 114}, {133, 255, 113}, {134, 255, 112}, {134, 255, 111}, {135, 255, 110},
+   {136, 255, 109}, {137, 255, 108}, {138, 255, 107}, {139, 255, 107}, {140, 255, 106},
+   {140, 255, 105}, {141, 255, 104}, {142, 255, 103}, {143, 255, 102}, {144, 255, 102},
+   {145, 255, 101}, {146, 255, 100}, {147, 255,  99}, {148, 255,  98}, {149, 255,  97},
+   {149, 255,  97}, {150, 255,  96}, {151, 255,  95}, {152, 255,  94}, {153, 255,  93},
+   {154, 255,  92}, {155, 255,  91}, {156, 255,  90}, {157, 255,  89}, {157, 255,  89},
+   {158, 255,  88}, {158, 255,  88}, {159, 255,  87}, {159, 255,  87}, {160, 255,  86},
+   {161, 255,  85}, {161, 255,  85}, {162, 255,  84}, {162, 255,  84}, {163, 255,  83},
+   {163, 255,  83}, {164, 255,  82}, {165, 255,  81}, {166, 255,  80}, {167, 255,  79},
+   {168, 255,  78}, {169, 255,  77}, {170, 255,  76}, {171, 255,  75}, {172, 255,  74},
+   {172, 255,  74}, {173, 255,  73}, {174, 255,  72}, {175, 255,  71}, {176, 255,  70},
+   {177, 255,  69}, {178, 255,  68}, {179, 255,  67}, {180, 255,  66}, {181, 255,  65},
+   {181, 255,  65}, {182, 255,  64}, {183, 255,  63}, {184, 255,  62}, {185, 255,  61},
+   {186, 255,  60}, {186, 255,  60}, {187, 255,  59}, {188, 255,  58}, {189, 255,  57},
+   {190, 255,  56}, {191, 255,  55}, {191, 255,  55}, {192, 255,  54}, {193, 255,  53},
+   {194, 255,  52}, {195, 255,  51}, {196, 255,  50}, {197, 255,  49}, {198, 255,  48},
+   {198, 255,  48}, {199, 255,  47}, {200, 255,  46}, {201, 255,  45}, {202, 255,  44},
+   {203, 255,  43}, {204, 255,  42}, {205, 255,  41}, {206, 255,  40}, {207, 255,  39},
+   {207, 255,  39}, {208, 255,  38}, {209, 255,  37}, {210, 255,  36}, {211, 255,  35},
+   {212, 255,  34}, {213, 255,  33}, {214, 255,  32}, {214, 255,  31}, {215, 255,  30},
+   {216, 255,  30}, {217, 255,  29}, {218, 255,  28}, {218, 255,  27}, {219, 255,  26},
+   {220, 255,  25}, {221, 255,  24}, {222, 255,  23}, {223, 255,  23}, {224, 255,  22},
+   {224, 255,  21}, {225, 255,  20}, {226, 255,  19}, {227, 255,  18}, {228, 255,  18},
+   {229, 255,  17}, {230, 255,  16}, {231, 255,  15}, {231, 255,  15}, {232, 255,  14},
+   {232, 255,  13}, {233, 255,  13}, {234, 255,  12}, {234, 255,  11}, {235, 255,  11},
+   {235, 255,  10}, {236, 255,  10}, {236, 255,   9}, {237, 255,   8}, {238, 254,   8},
+   {238, 253,   7}, {239, 252,   6}, {240, 251,   5}, {241, 250,   5}, {242, 249,   4},
+   {243, 248,   3}, {244, 247,   3}, {245, 246,   2}, {246, 246,   1}, {247, 245,   0},
+   {247, 243,   0}, {248, 242,   0}, {249, 242,   0}, {250, 241,   0}, {250, 240,   0},
+   {251, 239,   0}, {252, 238,   0}, {252, 237,   0}, {253, 236,   0}, {254, 235,   0},
+   {255, 234,   0}, {255, 233,   0}, {255, 232,   0}, {255, 231,   0}, {255, 230,   0},
+   {255, 229,   0}, {255, 228,   0}, {255, 227,   0}, {255, 226,   0}, {255, 225,   0},
+   {255, 224,   0}, {255, 223,   0}, {255, 222,   0}, {255, 221,   0}, {255, 220,   0},
+   {255, 219,   0}, {255, 218,   0}, {255, 217,   0}, {255, 216,   0}, {255, 215,   0},
+   {255, 214,   0}, {255, 213,   0}, {255, 212,   0}, {255, 211,   0}, {255, 210,   0},
+   {255, 209,   0}, {255, 208,   0}, {255, 207,   0}, {255, 206,   0}, {255, 205,   0},
+   {255, 204,   0}, {255, 203,   0}, {255, 202,   0}, {255, 201,   0}, {255, 200,   0},
+   {255, 199,   0}, {255, 198,   0}, {255, 197,   0}, {255, 196,   0}, {255, 195,   0},
+   {255, 194,   0}, {255, 193,   0}, {255, 192,   0}, {255, 191,   0}, {255, 190,   0},
+   {255, 189,   0}, {255, 188,   0}, {255, 187,   0}, {255, 186,   0}, {255, 185,   0},
+   {255, 184,   0}, {255, 183,   0}, {255, 182,   0}, {255, 180,   0}, {255, 179,   0},
+   {255, 178,   0}, {255, 177,   0}, {255, 176,   0}, {255, 176,   0}, {255, 175,   0},
+   {255, 175,   0}, {255, 174,   0}, {255, 173,   0}, {255, 173,   0}, {255, 172,   0},
+   {255, 171,   0}, {255, 171,   0}, {255, 170,   0}, {255, 169,   0}, {255, 168,   0},
+   {255, 167,   0}, {255, 166,   0}, {255, 165,   0}, {255, 164,   0}, {255, 163,   0},
+   {255, 162,   0}, {255, 161,   0}, {255, 160,   0}, {255, 159,   0}, {255, 158,   0},
+   {255, 157,   0}, {255, 156,   0}, {255, 155,   0}, {255, 154,   0}, {255, 153,   0},
+   {255, 152,   0}, {255, 151,   0}, {255, 150,   0}, {255, 150,   0}, {255, 149,   0},
+   {255, 147,   0}, {255, 146,   0}, {255, 146,   0}, {255, 145,   0}, {255, 144,   0},
+   {255, 143,   0}, {255, 142,   0}, {255, 141,   0}, {255, 140,   0}, {255, 139,   0},
+   {255, 138,   0}, {255, 137,   0}, {255, 136,   0}, {255, 135,   0}, {255, 134,   0},
+   {255, 133,   0}, {255, 132,   0}, {255, 131,   0}, {255, 130,   0}, {255, 129,   0},
+   {255, 128,   0}, {255, 127,   0}, {255, 126,   0}, {255, 125,   0}, {255, 124,   0},
+   {255, 123,   0}, {255, 122,   0}, {255, 121,   0}, {255, 120,   0}, {255, 119,   0},
+   {255, 118,   0}, {255, 117,   0}, {255, 116,   0}, {255, 115,   0}, {255, 114,   0},
+   {255, 113,   0}, {255, 112,   0}, {255, 111,   0}, {255, 109,   0}, {255, 108,   0},
+   {255, 107,   0}, {255, 106,   0}, {255, 105,   0}, {255, 104,   0}, {255, 103,   0},
+   {255, 102,   0}, {255, 101,   0}, {255, 100,   0}, {255,  99,   0}, {255,  98,   0},
+   {255,  97,   0}, {255,  96,   0}, {255,  95,   0}, {255,  94,   0}, {255,  93,   0},
+   {255,  92,   0}, {255,  91,   0}, {255,  91,   0}, {255,  90,   0}, {255,  90,   0},
+   {255,  89,   0}, {255,  88,   0}, {255,  88,   0}, {255,  87,   0}, {255,  86,   0},
+   {255,  86,   0}, {255,  85,   0}, {255,  84,   0}, {255,  83,   0}, {255,  82,   0},
+   {255,  81,   0}, {255,  80,   0}, {255,  79,   0}, {255,  78,   0}, {255,  77,   0},
+   {255,  76,   0}, {255,  75,   0}, {255,  74,   0}, {255,  73,   0}, {255,  72,   0},
+   {255,  71,   0}, {255,  70,   0}, {255,  69,   0}, {255,  68,   0}, {255,  67,   0},
+   {255,  66,   0}, {255,  65,   0}, {255,  64,   0}, {255,  63,   0}, {255,  62,   0},
+   {255,  61,   0}, {255,  60,   0}, {255,  59,   0}, {255,  58,   0}, {255,  57,   0},
+   {255,  56,   0}, {255,  55,   0}, {255,  54,   0}, {255,  54,   0}, {255,  53,   0},
+   {255,  51,   0}, {255,  50,   0}, {255,  49,   0}, {255,  48,   0}, {255,  47,   0},
+   {255,  46,   0}, {255,  45,   0}, {255,  44,   0}, {255,  43,   0}, {255,  42,   0},
+   {255,  41,   0}, {255,  40,   0}, {255,  39,   0}, {255,  38,   0}, {255,  37,   0},
+   {255,  36,   0}, {255,  35,   0}, {255,  34,   0}, {255,  33,   0}, {255,  32,   0},
+   {255,  31,   0}, {255,  30,   0}, {255,  29,   0}, {255,  28,   0}, {255,  27,   0},
+   {255,  26,   0}, {255,  25,   0}, {255,  24,   0}, {254,  23,   0}, {254,  22,   0},
+   {254,  21,   0}, {254,  20,   0}, {254,  19,   0}, {254,  18,   0}, {253,  17,   0},
+   {251,  16,   0}, {250,  15,   0}, {249,  14,   0}, {248,  13,   0}, {247,  12,   0},
+   {246,  11,   0}, {244,  10,   0}, {243,   9,   0}, {242,   8,   0}, {241,   7,   0},
+   {240,   6,   0}, {239,   6,   0}, {239,   5,   0}, {238,   4,   0}, {237,   4,   0},
+   {236,   3,   0}, {235,   3,   0}, {234,   2,   0}, {234,   1,   0}, {233,   1,   0},
+   {232,   0,   0}, {231,   0,   0}, {230,   0,   0}, {228,   0,   0}, {227,   0,   0},
+   {226,   0,   0}, {225,   0,   0}, {223,   0,   0}, {222,   0,   0}, {221,   0,   0},
+   {219,   0,   0}, {218,   0,   0}, {217,   0,   0}, {216,   0,   0}, {214,   0,   0},
+   {213,   0,   0}, {212,   0,   0}, {211,   0,   0}, {209,   0,   0}, {208,   0,   0},
+   {207,   0,   0}, {206,   0,   0}, {204,   0,   0}, {203,   0,   0}, {202,   0,   0},
+   {201,   0,   0}, {200,   0,   0}, {198,   0,   0}, {197,   0,   0}, {196,   0,   0},
+   {195,   0,   0}, {194,   0,   0}, {193,   0,   0}, {191,   0,   0}, {190,   0,   0},
+   {189,   0,   0}, {188,   0,   0}, {186,   0,   0}, {185,   0,   0}, {184,   0,   0},
+   {183,   0,   0}, {181,   0,   0}, {180,   0,   0}, {179,   0,   0}, {178,   0,   0},
+   {176,   0,   0}, {175,   0,   0}, {174,   0,   0}, {173,   0,   0}, {171,   0,   0},
+   {170,   0,   0}, {169,   0,   0}, {168,   0,   0}, {166,   0,   0}, {165,   0,   0},
+   {164,   0,   0}, {163,   0,   0}, {161,   0,   0}, {160,   0,   0}, {159,   0,   0},
+   {158,   0,   0}, {157,   0,   0}, {156,   0,   0}, {154,   0,   0}, {153,   0,   0},
+   {152,   0,   0}, {151,   0,   0}, {150,   0,   0}, {148,   0,   0}, {147,   0,   0},
+   {146,   0,   0}, {145,   0,   0}, {143,   0,   0}, {142,   0,   0}, {141,   0,   0},
+   {140,   0,   0}, {138,   0,   0}, {137,   0,   0}, {136,   0,   0}, {135,   0,   0},
+   {134,   0,   0}, {133,   0,   0}, {133,   0,   0}, {132,   0,   0}, {131,   0,   0},
+   {130,   0,   0}, {129,   0,   0}, {129,   0,   0}, {128,   0,   0}, {127,   0,   0},
+   {122,   0,   9}, {117,   0,  18}, {112,   0,  27}, {107,   0,  36}, {102,   0,  45}
+};
+
+const rgb_t prism_colormap[1000] = {
+   {255,   0,   0}, {255,   2,   0}, {255,   4,   0}, {255,   6,   0}, {255,   8,   0},
+   {255,  10,   0}, {255,  11,   0}, {255,  13,   0}, {255,  15,   0}, {255,  17,   0},
+   {255,  19,   0}, {255,  21,   0}, {255,  23,   0}, {255,  25,   0}, {255,  27,   0},
+   {255,  29,   0}, {255,  31,   0}, {255,  33,   0}, {255,  34,   0}, {255,  36,   0},
+   {255,  38,   0}, {255,  40,   0}, {255,  42,   0}, {255,  44,   0}, {255,  46,   0},
+   {255,  48,   0}, {255,  50,   0}, {255,  52,   0}, {255,  54,   0}, {255,  56,   0},
+   {255,  57,   0}, {255,  59,   0}, {255,  61,   0}, {255,  63,   0}, {255,  65,   0},
+   {255,  67,   0}, {255,  69,   0}, {255,  71,   0}, {255,  73,   0}, {255,  75,   0},
+   {255,  77,   0}, {255,  78,   0}, {255,  80,   0}, {255,  82,   0}, {255,  84,   0},
+   {255,  86,   0}, {255,  88,   0}, {255,  90,   0}, {255,  92,   0}, {255,  94,   0},
+   {255,  96,   0}, {255,  98,   0}, {255, 100,   0}, {255, 101,   0}, {255, 103,   0},
+   {255, 105,   0}, {255, 107,   0}, {255, 109,   0}, {255, 111,   0}, {255, 113,   0},
+   {255, 115,   0}, {255, 117,   0}, {255, 119,   0}, {255, 121,   0}, {255, 123,   0},
+   {255, 124,   0}, {255, 126,   0}, {255, 128,   0}, {255, 130,   0}, {255, 132,   0},
+   {255, 134,   0}, {255, 136,   0}, {255, 138,   0}, {255, 140,   0}, {255, 142,   0},
+   {255, 144,   0}, {255, 145,   0}, {255, 147,   0}, {255, 149,   0}, {255, 151,   0},
+   {255, 153,   0}, {255, 155,   0}, {255, 157,   0}, {255, 159,   0}, {255, 161,   0},
+   {255, 163,   0}, {255, 165,   0}, {255, 167,   0}, {255, 168,   0}, {255, 170,   0},
+   {255, 172,   0}, {255, 174,   0}, {255, 176,   0}, {255, 178,   0}, {255, 180,   0},
+   {255, 182,   0}, {255, 184,   0}, {255, 186,   0}, {255, 188,   0}, {255, 190,   0},
+   {255, 191,   0}, {255, 193,   0}, {255, 195,   0}, {255, 197,   0}, {255, 199,   0},
+   {255, 201,   0}, {255, 203,   0}, {255, 205,   0}, {255, 207,   0}, {255, 209,   0},
+   {255, 211,   0}, {255, 212,   0}, {255, 214,   0}, {255, 216,   0}, {255, 218,   0},
+   {255, 220,   0}, {255, 222,   0}, {255, 224,   0}, {255, 226,   0}, {255, 228,   0},
+   {255, 230,   0}, {255, 232,   0}, {255, 234,   0}, {255, 235,   0}, {255, 237,   0},
+   {255, 239,   0}, {255, 241,   0}, {255, 243,   0}, {255, 245,   0}, {255, 247,   0},
+   {255, 249,   0}, {255, 251,   0}, {255, 253,   0}, {255, 255,   0}, {252, 255,   0},
+   {248, 255,   0}, {244, 255,   0}, {240, 255,   0}, {237, 255,   0}, {233, 255,   0},
+   {229, 255,   0}, {225, 255,   0}, {221, 255,   0}, {217, 255,   0}, {214, 255,   0},
+   {210, 255,   0}, {206, 255,   0}, {202, 255,   0}, {198, 255,   0}, {195, 255,   0},
+   {191, 255,   0}, {187, 255,   0}, {183, 255,   0}, {179, 255,   0}, {175, 255,   0},
+   {172, 255,   0}, {168, 255,   0}, {164, 255,   0}, {160, 255,   0}, {156, 255,   0},
+   {152, 255,   0}, {149, 255,   0}, {145, 255,   0}, {141, 255,   0}, {137, 255,   0},
+   {133, 255,   0}, {129, 255,   0}, {126, 255,   0}, {122, 255,   0}, {118, 255,   0},
+   {114, 255,   0}, {110, 255,   0}, {106, 255,   0}, {103, 255,   0}, { 99, 255,   0},
+   { 95, 255,   0}, { 91, 255,   0}, { 87, 255,   0}, { 83, 255,   0}, { 80, 255,   0},
+   { 76, 255,   0}, { 72, 255,   0}, { 68, 255,   0}, { 64, 255,   0}, { 60, 255,   0},
+   { 57, 255,   0}, { 53, 255,   0}, { 49, 255,   0}, { 45, 255,   0}, { 41, 255,   0},
+   { 38, 255,   0}, { 34, 255,   0}, { 30, 255,   0}, { 26, 255,   0}, { 22, 255,   0},
+   { 18, 255,   0}, { 15, 255,   0}, { 11, 255,   0}, {  7, 255,   0}, {  3, 255,   0},
+   {  0, 254,   1}, {  0, 250,   5}, {  0, 247,   8}, {  0, 243,  12}, {  0, 239,  16},
+   {  0, 235,  20}, {  0, 231,  24}, {  0, 227,  28}, {  0, 224,  31}, {  0, 220,  35},
+   {  0, 216,  39}, {  0, 212,  43}, {  0, 208,  47}, {  0, 204,  51}, {  0, 201,  54},
+   {  0, 197,  58}, {  0, 193,  62}, {  0, 189,  66}, {  0, 185,  70}, {  0, 181,  74},
+   {  0, 178,  77}, {  0, 174,  81}, {  0, 170,  85}, {  0, 166,  89}, {  0, 162,  93},
+   {  0, 159,  96}, {  0, 155, 100}, {  0, 151, 104}, {  0, 147, 108}, {  0, 143, 112},
+   {  0, 139, 116}, {  0, 136, 119}, {  0, 132, 123}, {  0, 128, 127}, {  0, 124, 131},
+   {  0, 120, 135}, {  0, 116, 139}, {  0, 113, 142}, {  0, 109, 146}, {  0, 105, 150},
+   {  0, 101, 154}, {  0,  97, 158}, {  0,  93, 162}, {  0,  90, 165}, {  0,  86, 169},
+   {  0,  82, 173}, {  0,  78, 177}, {  0,  74, 181}, {  0,  70, 185}, {  0,  67, 188},
+   {  0,  63, 192}, {  0,  59, 196}, {  0,  55, 200}, {  0,  51, 204}, {  0,  47, 208},
+   {  0,  44, 211}, {  0,  40, 215}, {  0,  36, 219}, {  0,  32, 223}, {  0,  28, 227},
+   {  0,  25, 230}, {  0,  21, 234}, {  0,  17, 238}, {  0,  13, 242}, {  0,   9, 246},
+   {  0,   5, 250}, {  0,   2, 253}, {  2,   0, 255}, {  4,   0, 255}, {  7,   0, 255},
+   {  9,   0, 255}, { 12,   0, 255}, { 14,   0, 255}, { 17,   0, 255}, { 19,   0, 255},
+   { 22,   0, 255}, { 25,   0, 255}, { 27,   0, 255}, { 30,   0, 255}, { 32,   0, 255},
+   { 35,   0, 255}, { 37,   0, 255}, { 40,   0, 255}, { 42,   0, 255}, { 45,   0, 255},
+   { 47,   0, 255}, { 50,   0, 255}, { 53,   0, 255}, { 55,   0, 255}, { 58,   0, 255},
+   { 60,   0, 255}, { 63,   0, 255}, { 65,   0, 255}, { 68,   0, 255}, { 70,   0, 255},
+   { 73,   0, 255}, { 76,   0, 255}, { 78,   0, 255}, { 81,   0, 255}, { 83,   0, 255},
+   { 86,   0, 255}, { 88,   0, 255}, { 91,   0, 255}, { 93,   0, 255}, { 96,   0, 255},
+   { 99,   0, 255}, {101,   0, 255}, {104,   0, 255}, {106,   0, 255}, {109,   0, 255},
+   {111,   0, 255}, {114,   0, 255}, {116,   0, 255}, {119,   0, 255}, {122,   0, 255},
+   {124,   0, 255}, {127,   0, 255}, {129,   0, 255}, {132,   0, 255}, {134,   0, 255},
+   {137,   0, 255}, {139,   0, 255}, {142,   0, 255}, {144,   0, 255}, {147,   0, 255},
+   {150,   0, 255}, {152,   0, 255}, {155,   0, 255}, {157,   0, 255}, {160,   0, 255},
+   {162,   0, 255}, {165,   0, 255}, {167,   0, 255}, {170,   0, 255}, {171,   0, 251},
+   {173,   0, 247}, {174,   0, 244}, {175,   0, 240}, {176,   0, 236}, {178,   0, 232},
+   {179,   0, 228}, {180,   0, 224}, {181,   0, 221}, {183,   0, 217}, {184,   0, 213},
+   {185,   0, 209}, {187,   0, 205}, {188,   0, 201}, {189,   0, 198}, {190,   0, 194},
+   {192,   0, 190}, {193,   0, 186}, {194,   0, 182}, {196,   0, 178}, {197,   0, 175},
+   {198,   0, 171}, {199,   0, 167}, {201,   0, 163}, {202,   0, 159}, {203,   0, 155},
+   {204,   0, 152}, {206,   0, 148}, {207,   0, 144}, {208,   0, 140}, {210,   0, 136},
+   {211,   0, 132}, {212,   0, 129}, {213,   0, 125}, {215,   0, 121}, {216,   0, 117},
+   {217,   0, 113}, {218,   0, 110}, {220,   0, 106}, {221,   0, 102}, {222,   0,  98},
+   {224,   0,  94}, {225,   0,  90}, {226,   0,  87}, {227,   0,  83}, {229,   0,  79},
+   {230,   0,  75}, {231,   0,  71}, {233,   0,  67}, {234,   0,  64}, {235,   0,  60},
+   {236,   0,  56}, {238,   0,  52}, {239,   0,  48}, {240,   0,  44}, {241,   0,  41},
+   {243,   0,  37}, {244,   0,  33}, {245,   0,  29}, {247,   0,  25}, {248,   0,  21},
+   {249,   0,  18}, {250,   0,  14}, {252,   0,  10}, {253,   0,   6}, {254,   0,   2},
+   {255,   1,   0}, {255,   3,   0}, {255,   5,   0}, {255,   7,   0}, {255,   8,   0},
+   {255,  10,   0}, {255,  12,   0}, {255,  14,   0}, {255,  16,   0}, {255,  18,   0},
+   {255,  20,   0}, {255,  22,   0}, {255,  24,   0}, {255,  26,   0}, {255,  28,   0},
+   {255,  29,   0}, {255,  31,   0}, {255,  33,   0}, {255,  35,   0}, {255,  37,   0},
+   {255,  39,   0}, {255,  41,   0}, {255,  43,   0}, {255,  45,   0}, {255,  47,   0},
+   {255,  49,   0}, {255,  51,   0}, {255,  52,   0}, {255,  54,   0}, {255,  56,   0},
+   {255,  58,   0}, {255,  60,   0}, {255,  62,   0}, {255,  64,   0}, {255,  66,   0},
+   {255,  68,   0}, {255,  70,   0}, {255,  72,   0}, {255,  74,   0}, {255,  75,   0},
+   {255,  77,   0}, {255,  79,   0}, {255,  81,   0}, {255,  83,   0}, {255,  85,   0},
+   {255,  87,   0}, {255,  89,   0}, {255,  91,   0}, {255,  93,   0}, {255,  95,   0},
+   {255,  96,   0}, {255,  98,   0}, {255, 100,   0}, {255, 102,   0}, {255, 104,   0},
+   {255, 106,   0}, {255, 108,   0}, {255, 110,   0}, {255, 112,   0}, {255, 114,   0},
+   {255, 116,   0}, {255, 118,   0}, {255, 119,   0}, {255, 121,   0}, {255, 123,   0},
+   {255, 125,   0}, {255, 127,   0}, {255, 129,   0}, {255, 131,   0}, {255, 133,   0},
+   {255, 135,   0}, {255, 137,   0}, {255, 139,   0}, {255, 141,   0}, {255, 142,   0},
+   {255, 144,   0}, {255, 146,   0}, {255, 148,   0}, {255, 150,   0}, {255, 152,   0},
+   {255, 154,   0}, {255, 156,   0}, {255, 158,   0}, {255, 160,   0}, {255, 162,   0},
+   {255, 163,   0}, {255, 165,   0}, {255, 167,   0}, {255, 169,   0}, {255, 171,   0},
+   {255, 173,   0}, {255, 175,   0}, {255, 177,   0}, {255, 179,   0}, {255, 181,   0},
+   {255, 183,   0}, {255, 185,   0}, {255, 186,   0}, {255, 188,   0}, {255, 190,   0},
+   {255, 192,   0}, {255, 194,   0}, {255, 196,   0}, {255, 198,   0}, {255, 200,   0},
+   {255, 202,   0}, {255, 204,   0}, {255, 206,   0}, {255, 208,   0}, {255, 209,   0},
+   {255, 211,   0}, {255, 213,   0}, {255, 215,   0}, {255, 217,   0}, {255, 219,   0},
+   {255, 221,   0}, {255, 223,   0}, {255, 225,   0}, {255, 227,   0}, {255, 229,   0},
+   {255, 230,   0}, {255, 232,   0}, {255, 234,   0}, {255, 236,   0}, {255, 238,   0},
+   {255, 240,   0}, {255, 242,   0}, {255, 244,   0}, {255, 246,   0}, {255, 248,   0},
+   {255, 250,   0}, {255, 252,   0}, {255, 253,   0}, {254, 255,   0}, {250, 255,   0},
+   {247, 255,   0}, {243, 255,   0}, {239, 255,   0}, {235, 255,   0}, {231, 255,   0},
+   {227, 255,   0}, {224, 255,   0}, {220, 255,   0}, {216, 255,   0}, {212, 255,   0},
+   {208, 255,   0}, {204, 255,   0}, {201, 255,   0}, {197, 255,   0}, {193, 255,   0},
+   {189, 255,   0}, {185, 255,   0}, {181, 255,   0}, {178, 255,   0}, {174, 255,   0},
+   {170, 255,   0}, {166, 255,   0}, {162, 255,   0}, {159, 255,   0}, {155, 255,   0},
+   {151, 255,   0}, {147, 255,   0}, {143, 255,   0}, {139, 255,   0}, {136, 255,   0},
+   {132, 255,   0}, {128, 255,   0}, {124, 255,   0}, {120, 255,   0}, {116, 255,   0},
+   {113, 255,   0}, {109, 255,   0}, {105, 255,   0}, {101, 255,   0}, { 97, 255,   0},
+   { 93, 255,   0}, { 90, 255,   0}, { 86, 255,   0}, { 82, 255,   0}, { 78, 255,   0},
+   { 74, 255,   0}, { 70, 255,   0}, { 67, 255,   0}, { 63, 255,   0}, { 59, 255,   0},
+   { 55, 255,   0}, { 51, 255,   0}, { 47, 255,   0}, { 44, 255,   0}, { 40, 255,   0},
+   { 36, 255,   0}, { 32, 255,   0}, { 28, 255,   0}, { 25, 255,   0}, { 21, 255,   0},
+   { 17, 255,   0}, { 13, 255,   0}, {  9, 255,   0}, {  5, 255,   0}, {  2, 255,   0},
+   {  0, 253,   2}, {  0, 249,   6}, {  0, 245,  10}, {  0, 241,  14}, {  0, 237,  18},
+   {  0, 234,  21}, {  0, 230,  25}, {  0, 226,  29}, {  0, 222,  33}, {  0, 218,  37},
+   {  0, 214,  41}, {  0, 211,  44}, {  0, 207,  48}, {  0, 203,  52}, {  0, 199,  56},
+   {  0, 195,  60}, {  0, 191,  64}, {  0, 188,  67}, {  0, 184,  71}, {  0, 180,  75},
+   {  0, 176,  79}, {  0, 172,  83}, {  0, 168,  87}, {  0, 165,  90}, {  0, 161,  94},
+   {  0, 157,  98}, {  0, 153, 102}, {  0, 149, 106}, {  0, 145, 110}, {  0, 142, 113},
+   {  0, 138, 117}, {  0, 134, 121}, {  0, 130, 125}, {  0, 126, 129}, {  0, 123, 132},
+   {  0, 119, 136}, {  0, 115, 140}, {  0, 111, 144}, {  0, 107, 148}, {  0, 103, 152},
+   {  0, 100, 155}, {  0,  96, 159}, {  0,  92, 163}, {  0,  88, 167}, {  0,  84, 171},
+   {  0,  80, 175}, {  0,  77, 178}, {  0,  73, 182}, {  0,  69, 186}, {  0,  65, 190},
+   {  0,  61, 194}, {  0,  57, 198}, {  0,  54, 201}, {  0,  50, 205}, {  0,  46, 209},
+   {  0,  42, 213}, {  0,  38, 217}, {  0,  34, 221}, {  0,  31, 224}, {  0,  27, 228},
+   {  0,  23, 232}, {  0,  19, 236}, {  0,  15, 240}, {  0,  11, 244}, {  0,   8, 247},
+   {  0,   4, 251}, {  0,   0, 255}, {  3,   0, 255}, {  5,   0, 255}, {  8,   0, 255},
+   { 10,   0, 255}, { 13,   0, 255}, { 15,   0, 255}, { 18,   0, 255}, { 20,   0, 255},
+   { 23,   0, 255}, { 26,   0, 255}, { 28,   0, 255}, { 31,   0, 255}, { 33,   0, 255},
+   { 36,   0, 255}, { 38,   0, 255}, { 41,   0, 255}, { 43,   0, 255}, { 46,   0, 255},
+   { 48,   0, 255}, { 51,   0, 255}, { 54,   0, 255}, { 56,   0, 255}, { 59,   0, 255},
+   { 61,   0, 255}, { 64,   0, 255}, { 66,   0, 255}, { 69,   0, 255}, { 71,   0, 255},
+   { 74,   0, 255}, { 77,   0, 255}, { 79,   0, 255}, { 82,   0, 255}, { 84,   0, 255},
+   { 87,   0, 255}, { 89,   0, 255}, { 92,   0, 255}, { 94,   0, 255}, { 97,   0, 255},
+   {100,   0, 255}, {102,   0, 255}, {105,   0, 255}, {107,   0, 255}, {110,   0, 255},
+   {112,   0, 255}, {115,   0, 255}, {117,   0, 255}, {120,   0, 255}, {123,   0, 255},
+   {125,   0, 255}, {128,   0, 255}, {130,   0, 255}, {133,   0, 255}, {135,   0, 255},
+   {138,   0, 255}, {140,   0, 255}, {143,   0, 255}, {145,   0, 255}, {148,   0, 255},
+   {151,   0, 255}, {153,   0, 255}, {156,   0, 255}, {158,   0, 255}, {161,   0, 255},
+   {163,   0, 255}, {166,   0, 255}, {168,   0, 255}, {171,   0, 253}, {172,   0, 250},
+   {173,   0, 246}, {174,   0, 242}, {176,   0, 238}, {177,   0, 234}, {178,   0, 230},
+   {179,   0, 227}, {181,   0, 223}, {182,   0, 219}, {183,   0, 215}, {185,   0, 211},
+   {186,   0, 208}, {187,   0, 204}, {188,   0, 200}, {190,   0, 196}, {191,   0, 192},
+   {192,   0, 188}, {193,   0, 185}, {195,   0, 181}, {196,   0, 177}, {197,   0, 173},
+   {199,   0, 169}, {200,   0, 165}, {201,   0, 162}, {202,   0, 158}, {204,   0, 154},
+   {205,   0, 150}, {206,   0, 146}, {208,   0, 142}, {209,   0, 139}, {210,   0, 135},
+   {211,   0, 131}, {213,   0, 127}, {214,   0, 123}, {215,   0, 119}, {216,   0, 116},
+   {218,   0, 112}, {219,   0, 108}, {220,   0, 104}, {222,   0, 100}, {223,   0,  96},
+   {224,   0,  93}, {225,   0,  89}, {227,   0,  85}, {228,   0,  81}, {229,   0,  77},
+   {230,   0,  74}, {232,   0,  70}, {233,   0,  66}, {234,   0,  62}, {236,   0,  58},
+   {237,   0,  54}, {238,   0,  51}, {239,   0,  47}, {241,   0,  43}, {242,   0,  39},
+   {243,   0,  35}, {245,   0,  31}, {246,   0,  28}, {247,   0,  24}, {248,   0,  20},
+   {250,   0,  16}, {251,   0,  12}, {252,   0,   8}, {253,   0,   5}, {255,   0,   1},
+   {255,   2,   0}, {255,   3,   0}, {255,   5,   0}, {255,   7,   0}, {255,   9,   0},
+   {255,  11,   0}, {255,  13,   0}, {255,  15,   0}, {255,  17,   0}, {255,  19,   0},
+   {255,  21,   0}, {255,  23,   0}, {255,  25,   0}, {255,  26,   0}, {255,  28,   0},
+   {255,  30,   0}, {255,  32,   0}, {255,  34,   0}, {255,  36,   0}, {255,  38,   0},
+   {255,  40,   0}, {255,  42,   0}, {255,  44,   0}, {255,  46,   0}, {255,  47,   0},
+   {255,  49,   0}, {255,  51,   0}, {255,  53,   0}, {255,  55,   0}, {255,  57,   0},
+   {255,  59,   0}, {255,  61,   0}, {255,  63,   0}, {255,  65,   0}, {255,  67,   0},
+   {255,  69,   0}, {255,  70,   0}, {255,  72,   0}, {255,  74,   0}, {255,  76,   0},
+   {255,  78,   0}, {255,  80,   0}, {255,  82,   0}, {255,  84,   0}, {255,  86,   0},
+   {255,  88,   0}, {255,  90,   0}, {255,  92,   0}, {255,  93,   0}, {255,  95,   0},
+   {255,  97,   0}, {255,  99,   0}, {255, 101,   0}, {255, 103,   0}, {255, 105,   0},
+   {255, 107,   0}, {255, 109,   0}, {255, 111,   0}, {255, 113,   0}, {255, 114,   0},
+   {255, 116,   0}, {255, 118,   0}, {255, 120,   0}, {255, 122,   0}, {255, 124,   0},
+   {255, 126,   0}, {255, 128,   0}, {255, 130,   0}, {255, 132,   0}, {255, 134,   0},
+   {255, 136,   0}, {255, 137,   0}, {255, 139,   0}, {255, 141,   0}, {255, 143,   0},
+   {255, 145,   0}, {255, 147,   0}, {255, 149,   0}, {255, 151,   0}, {255, 153,   0},
+   {255, 155,   0}, {255, 157,   0}, {255, 159,   0}, {255, 160,   0}, {255, 162,   0},
+   {255, 164,   0}, {255, 166,   0}, {255, 168,   0}, {255, 170,   0}, {255, 172,   0},
+   {255, 174,   0}, {255, 176,   0}, {255, 178,   0}, {255, 180,   0}, {255, 181,   0},
+   {255, 183,   0}, {255, 185,   0}, {255, 187,   0}, {255, 189,   0}, {255, 191,   0},
+   {255, 193,   0}, {255, 195,   0}, {255, 197,   0}, {255, 199,   0}, {255, 201,   0},
+   {255, 203,   0}, {255, 204,   0}, {255, 206,   0}, {255, 208,   0}, {255, 210,   0},
+   {255, 212,   0}, {255, 214,   0}, {255, 216,   0}, {255, 218,   0}, {255, 220,   0},
+   {255, 222,   0}, {255, 224,   0}, {255, 226,   0}, {255, 227,   0}, {255, 229,   0},
+   {255, 231,   0}, {255, 233,   0}, {255, 235,   0}, {255, 237,   0}, {255, 239,   0},
+   {255, 241,   0}, {255, 243,   0}, {255, 245,   0}, {255, 247,   0}, {255, 248,   0},
+   {255, 250,   0}, {255, 252,   0}, {255, 254,   0}, {253, 255,   0}, {249, 255,   0},
+   {245, 255,   0}, {241, 255,   0}, {237, 255,   0}, {234, 255,   0}, {230, 255,   0},
+   {226, 255,   0}, {222, 255,   0}, {218, 255,   0}, {214, 255,   0}, {211, 255,   0},
+   {207, 255,   0}, {203, 255,   0}, {199, 255,   0}, {195, 255,   0}, {191, 255,   0},
+   {188, 255,   0}, {184, 255,   0}, {180, 255,   0}, {176, 255,   0}, {172, 255,   0},
+   {168, 255,   0}, {165, 255,   0}, {161, 255,   0}, {157, 255,   0}, {153, 255,   0},
+   {149, 255,   0}, {145, 255,   0}, {142, 255,   0}, {138, 255,   0}, {134, 255,   0},
+   {130, 255,   0}, {126, 255,   0}, {123, 255,   0}, {119, 255,   0}, {115, 255,   0},
+   {111, 255,   0}, {107, 255,   0}, {103, 255,   0}, {100, 255,   0}, { 96, 255,   0},
+   { 92, 255,   0}, { 88, 255,   0}, { 84, 255,   0}, { 80, 255,   0}, { 77, 255,   0},
+   { 73, 255,   0}, { 69, 255,   0}, { 65, 255,   0}, { 61, 255,   0}, { 57, 255,   0},
+   { 54, 255,   0}, { 50, 255,   0}, { 46, 255,   0}, { 42, 255,   0}, { 38, 255,   0},
+   { 34, 255,   0}, { 31, 255,   0}, { 27, 255,   0}, { 23, 255,   0}, { 19, 255,   0},
+   { 15, 255,   0}, { 11, 255,   0}, {  8, 255,   0}, {  4, 255,   0}, {  0, 255,   0}
+};
+
+const rgb_t vga_colormap[1000] = {
+   {255, 255, 255}, {254, 254, 254}, {253, 253, 253}, {252, 252, 252}, {251, 251, 251},
+   {250, 250, 250}, {249, 249, 249}, {248, 248, 248}, {247, 247, 247}, {246, 246, 246},
+   {245, 245, 245}, {244, 244, 244}, {244, 244, 244}, {243, 243, 243}, {242, 242, 242},
+   {241, 241, 241}, {240, 240, 240}, {239, 239, 239}, {238, 238, 238}, {237, 237, 237},
+   {236, 236, 236}, {235, 235, 235}, {234, 234, 234}, {233, 233, 233}, {232, 232, 232},
+   {231, 231, 231}, {230, 230, 230}, {229, 229, 229}, {228, 228, 228}, {227, 227, 227},
+   {226, 226, 226}, {225, 225, 225}, {224, 224, 224}, {223, 223, 223}, {222, 222, 222},
+   {221, 221, 221}, {221, 221, 221}, {220, 220, 220}, {219, 219, 219}, {218, 218, 218},
+   {217, 217, 217}, {216, 216, 216}, {215, 215, 215}, {214, 214, 214}, {213, 213, 213},
+   {212, 212, 212}, {211, 211, 211}, {210, 210, 210}, {209, 209, 209}, {208, 208, 208},
+   {207, 207, 207}, {206, 206, 206}, {205, 205, 205}, {204, 204, 204}, {203, 203, 203},
+   {202, 202, 202}, {201, 201, 201}, {200, 200, 200}, {199, 199, 199}, {199, 199, 199},
+   {198, 198, 198}, {197, 197, 197}, {196, 196, 196}, {195, 195, 195}, {194, 194, 194},
+   {193, 193, 193}, {192, 192, 192}, {192, 190, 190}, {193, 187, 187}, {194, 184, 184},
+   {195, 181, 181}, {195, 179, 179}, {196, 176, 176}, {197, 173, 173}, {198, 170, 170},
+   {199, 167, 167}, {200, 164, 164}, {201, 161, 161}, {202, 159, 159}, {203, 156, 156},
+   {204, 153, 153}, {205, 150, 150}, {206, 147, 147}, {207, 144, 144}, {208, 141, 141},
+   {209, 138, 138}, {210, 136, 136}, {211, 133, 133}, {212, 130, 130}, {213, 127, 127},
+   {214, 124, 124}, {215, 121, 121}, {216, 118, 118}, {217, 115, 115}, {217, 113, 113},
+   {218, 110, 110}, {219, 107, 107}, {220, 104, 104}, {221, 101, 101}, {222,  98,  98},
+   {223,  95,  95}, {224,  92,  92}, {225,  90,  90}, {226,  87,  87}, {227,  84,  84},
+   {228,  81,  81}, {229,  78,  78}, {230,  75,  75}, {231,  72,  72}, {232,  69,  69},
+   {233,  67,  67}, {234,  64,  64}, {235,  61,  61}, {236,  58,  58}, {237,  55,  55},
+   {238,  52,  52}, {239,  49,  49}, {239,  47,  47}, {240,  44,  44}, {241,  41,  41},
+   {242,  38,  38}, {243,  35,  35}, {244,  32,  32}, {245,  29,  29}, {246,  26,  26},
+   {247,  24,  24}, {248,  21,  21}, {249,  18,  18}, {250,  15,  15}, {251,  12,  12},
+   {252,   9,   9}, {253,   6,   6}, {254,   3,   3}, {255,   1,   1}, {255,   3,   0},
+   {255,   7,   0}, {255,  11,   0}, {255,  15,   0}, {255,  18,   0}, {255,  22,   0},
+   {255,  26,   0}, {255,  30,   0}, {255,  34,   0}, {255,  38,   0}, {255,  41,   0},
+   {255,  45,   0}, {255,  49,   0}, {255,  53,   0}, {255,  57,   0}, {255,  60,   0},
+   {255,  64,   0}, {255,  68,   0}, {255,  72,   0}, {255,  76,   0}, {255,  80,   0},
+   {255,  83,   0}, {255,  87,   0}, {255,  91,   0}, {255,  95,   0}, {255,  99,   0},
+   {255, 103,   0}, {255, 106,   0}, {255, 110,   0}, {255, 114,   0}, {255, 118,   0},
+   {255, 122,   0}, {255, 126,   0}, {255, 129,   0}, {255, 133,   0}, {255, 137,   0},
+   {255, 141,   0}, {255, 145,   0}, {255, 149,   0}, {255, 152,   0}, {255, 156,   0},
+   {255, 160,   0}, {255, 164,   0}, {255, 168,   0}, {255, 172,   0}, {255, 175,   0},
+   {255, 179,   0}, {255, 183,   0}, {255, 187,   0}, {255, 191,   0}, {255, 195,   0},
+   {255, 198,   0}, {255, 202,   0}, {255, 206,   0}, {255, 210,   0}, {255, 214,   0},
+   {255, 217,   0}, {255, 221,   0}, {255, 225,   0}, {255, 229,   0}, {255, 233,   0},
+   {255, 237,   0}, {255, 240,   0}, {255, 244,   0}, {255, 248,   0}, {255, 252,   0},
+   {254, 255,   0}, {250, 255,   0}, {247, 255,   0}, {243, 255,   0}, {239, 255,   0},
+   {235, 255,   0}, {231, 255,   0}, {227, 255,   0}, {224, 255,   0}, {220, 255,   0},
+   {216, 255,   0}, {212, 255,   0}, {208, 255,   0}, {204, 255,   0}, {201, 255,   0},
+   {197, 255,   0}, {193, 255,   0}, {189, 255,   0}, {185, 255,   0}, {181, 255,   0},
+   {178, 255,   0}, {174, 255,   0}, {170, 255,   0}, {166, 255,   0}, {162, 255,   0},
+   {159, 255,   0}, {155, 255,   0}, {151, 255,   0}, {147, 255,   0}, {143, 255,   0},
+   {139, 255,   0}, {136, 255,   0}, {132, 255,   0}, {128, 255,   0}, {124, 255,   0},
+   {120, 255,   0}, {116, 255,   0}, {113, 255,   0}, {109, 255,   0}, {105, 255,   0},
+   {101, 255,   0}, { 97, 255,   0}, { 93, 255,   0}, { 90, 255,   0}, { 86, 255,   0},
+   { 82, 255,   0}, { 78, 255,   0}, { 74, 255,   0}, { 70, 255,   0}, { 67, 255,   0},
+   { 63, 255,   0}, { 59, 255,   0}, { 55, 255,   0}, { 51, 255,   0}, { 47, 255,   0},
+   { 44, 255,   0}, { 40, 255,   0}, { 36, 255,   0}, { 32, 255,   0}, { 28, 255,   0},
+   { 25, 255,   0}, { 21, 255,   0}, { 17, 255,   0}, { 13, 255,   0}, {  9, 255,   0},
+   {  5, 255,   0}, {  2, 255,   0}, {  0, 255,   2}, {  0, 255,   6}, {  0, 255,  10},
+   {  0, 255,  14}, {  0, 255,  18}, {  0, 255,  21}, {  0, 255,  25}, {  0, 255,  29},
+   {  0, 255,  33}, {  0, 255,  37}, {  0, 255,  41}, {  0, 255,  44}, {  0, 255,  48},
+   {  0, 255,  52}, {  0, 255,  56}, {  0, 255,  60}, {  0, 255,  64}, {  0, 255,  67},
+   {  0, 255,  71}, {  0, 255,  75}, {  0, 255,  79}, {  0, 255,  83}, {  0, 255,  87},
+   {  0, 255,  90}, {  0, 255,  94}, {  0, 255,  98}, {  0, 255, 102}, {  0, 255, 106},
+   {  0, 255, 110}, {  0, 255, 113}, {  0, 255, 117}, {  0, 255, 121}, {  0, 255, 125},
+   {  0, 255, 129}, {  0, 255, 132}, {  0, 255, 136}, {  0, 255, 140}, {  0, 255, 144},
+   {  0, 255, 148}, {  0, 255, 152}, {  0, 255, 155}, {  0, 255, 159}, {  0, 255, 163},
+   {  0, 255, 167}, {  0, 255, 171}, {  0, 255, 175}, {  0, 255, 178}, {  0, 255, 182},
+   {  0, 255, 186}, {  0, 255, 190}, {  0, 255, 194}, {  0, 255, 198}, {  0, 255, 201},
+   {  0, 255, 205}, {  0, 255, 209}, {  0, 255, 213}, {  0, 255, 217}, {  0, 255, 221},
+   {  0, 255, 224}, {  0, 255, 228}, {  0, 255, 232}, {  0, 255, 236}, {  0, 255, 240},
+   {  0, 255, 244}, {  0, 255, 247}, {  0, 255, 251}, {  0, 255, 255}, {  0, 251, 255},
+   {  0, 247, 255}, {  0, 244, 255}, {  0, 240, 255}, {  0, 236, 255}, {  0, 232, 255},
+   {  0, 228, 255}, {  0, 224, 255}, {  0, 221, 255}, {  0, 217, 255}, {  0, 213, 255},
+   {  0, 209, 255}, {  0, 205, 255}, {  0, 201, 255}, {  0, 198, 255}, {  0, 194, 255},
+   {  0, 190, 255}, {  0, 186, 255}, {  0, 182, 255}, {  0, 178, 255}, {  0, 175, 255},
+   {  0, 171, 255}, {  0, 167, 255}, {  0, 163, 255}, {  0, 159, 255}, {  0, 155, 255},
+   {  0, 152, 255}, {  0, 148, 255}, {  0, 144, 255}, {  0, 140, 255}, {  0, 136, 255},
+   {  0, 132, 255}, {  0, 129, 255}, {  0, 125, 255}, {  0, 121, 255}, {  0, 117, 255},
+   {  0, 113, 255}, {  0, 110, 255}, {  0, 106, 255}, {  0, 102, 255}, {  0,  98, 255},
+   {  0,  94, 255}, {  0,  90, 255}, {  0,  87, 255}, {  0,  83, 255}, {  0,  79, 255},
+   {  0,  75, 255}, {  0,  71, 255}, {  0,  67, 255}, {  0,  64, 255}, {  0,  60, 255},
+   {  0,  56, 255}, {  0,  52, 255}, {  0,  48, 255}, {  0,  44, 255}, {  0,  41, 255},
+   {  0,  37, 255}, {  0,  33, 255}, {  0,  29, 255}, {  0,  25, 255}, {  0,  21, 255},
+   {  0,  18, 255}, {  0,  14, 255}, {  0,  10, 255}, {  0,   6, 255}, {  0,   2, 255},
+   {  2,   0, 255}, {  5,   0, 255}, {  9,   0, 255}, { 13,   0, 255}, { 17,   0, 255},
+   { 21,   0, 255}, { 25,   0, 255}, { 28,   0, 255}, { 32,   0, 255}, { 36,   0, 255},
+   { 40,   0, 255}, { 44,   0, 255}, { 47,   0, 255}, { 51,   0, 255}, { 55,   0, 255},
+   { 59,   0, 255}, { 63,   0, 255}, { 67,   0, 255}, { 70,   0, 255}, { 74,   0, 255},
+   { 78,   0, 255}, { 82,   0, 255}, { 86,   0, 255}, { 90,   0, 255}, { 93,   0, 255},
+   { 97,   0, 255}, {101,   0, 255}, {105,   0, 255}, {109,   0, 255}, {113,   0, 255},
+   {116,   0, 255}, {120,   0, 255}, {124,   0, 255}, {128,   0, 255}, {132,   0, 255},
+   {136,   0, 255}, {139,   0, 255}, {143,   0, 255}, {147,   0, 255}, {151,   0, 255},
+   {155,   0, 255}, {159,   0, 255}, {162,   0, 255}, {166,   0, 255}, {170,   0, 255},
+   {174,   0, 255}, {178,   0, 255}, {181,   0, 255}, {185,   0, 255}, {189,   0, 255},
+   {193,   0, 255}, {197,   0, 255}, {201,   0, 255}, {204,   0, 255}, {208,   0, 255},
+   {212,   0, 255}, {216,   0, 255}, {220,   0, 255}, {224,   0, 255}, {227,   0, 255},
+   {231,   0, 255}, {235,   0, 255}, {239,   0, 255}, {243,   0, 255}, {247,   0, 255},
+   {250,   0, 255}, {254,   0, 255}, {252,   0, 252}, {248,   0, 248}, {244,   0, 244},
+   {240,   0, 240}, {237,   0, 237}, {233,   0, 233}, {229,   0, 229}, {225,   0, 225},
+   {221,   0, 221}, {217,   0, 217}, {214,   0, 214}, {210,   0, 210}, {206,   0, 206},
+   {202,   0, 202}, {198,   0, 198}, {195,   0, 195}, {191,   0, 191}, {187,   0, 187},
+   {183,   0, 183}, {179,   0, 179}, {175,   0, 175}, {172,   0, 172}, {168,   0, 168},
+   {164,   0, 164}, {160,   0, 160}, {156,   0, 156}, {152,   0, 152}, {149,   0, 149},
+   {145,   0, 145}, {141,   0, 141}, {137,   0, 137}, {133,   0, 133}, {129,   0, 129},
+   {126,   0, 126}, {122,   0, 122}, {118,   0, 118}, {114,   0, 114}, {110,   0, 110},
+   {106,   0, 106}, {103,   0, 103}, { 99,   0,  99}, { 95,   0,  95}, { 91,   0,  91},
+   { 87,   0,  87}, { 83,   0,  83}, { 80,   0,  80}, { 76,   0,  76}, { 72,   0,  72},
+   { 68,   0,  68}, { 64,   0,  64}, { 60,   0,  60}, { 57,   0,  57}, { 53,   0,  53},
+   { 49,   0,  49}, { 45,   0,  45}, { 41,   0,  41}, { 38,   0,  38}, { 34,   0,  34},
+   { 30,   0,  30}, { 26,   0,  26}, { 22,   0,  22}, { 18,   0,  18}, { 15,   0,  15},
+   { 11,   0,  11}, {  7,   0,   7}, {  3,   0,   3}, {  0,   0,   0}, {  2,   2,   2},
+   {  4,   4,   4}, {  6,   6,   6}, {  8,   8,   8}, { 10,  10,  10}, { 12,  12,  12},
+   { 14,  14,  14}, { 16,  16,  16}, { 18,  18,  18}, { 20,  20,  20}, { 21,  21,  21},
+   { 23,  23,  23}, { 25,  25,  25}, { 27,  27,  27}, { 29,  29,  29}, { 31,  31,  31},
+   { 33,  33,  33}, { 35,  35,  35}, { 37,  37,  37}, { 39,  39,  39}, { 41,  41,  41},
+   { 43,  43,  43}, { 44,  44,  44}, { 46,  46,  46}, { 48,  48,  48}, { 50,  50,  50},
+   { 52,  52,  52}, { 54,  54,  54}, { 56,  56,  56}, { 58,  58,  58}, { 60,  60,  60},
+   { 62,  62,  62}, { 64,  64,  64}, { 65,  65,  65}, { 67,  67,  67}, { 69,  69,  69},
+   { 71,  71,  71}, { 73,  73,  73}, { 75,  75,  75}, { 77,  77,  77}, { 79,  79,  79},
+   { 81,  81,  81}, { 83,  83,  83}, { 85,  85,  85}, { 87,  87,  87}, { 88,  88,  88},
+   { 90,  90,  90}, { 92,  92,  92}, { 94,  94,  94}, { 96,  96,  96}, { 98,  98,  98},
+   {100, 100, 100}, {102, 102, 102}, {104, 104, 104}, {106, 106, 106}, {108, 108, 108},
+   {110, 110, 110}, {111, 111, 111}, {113, 113, 113}, {115, 115, 115}, {117, 117, 117},
+   {119, 119, 119}, {121, 121, 121}, {123, 123, 123}, {125, 125, 125}, {127, 127, 127},
+   {128, 126, 126}, {128, 124, 124}, {128, 123, 123}, {128, 121, 121}, {128, 119, 119},
+   {128, 117, 117}, {128, 115, 115}, {128, 113, 113}, {128, 111, 111}, {128, 109, 109},
+   {128, 107, 107}, {128, 105, 105}, {128, 103, 103}, {128, 101, 101}, {128, 100, 100},
+   {128,  98,  98}, {128,  96,  96}, {128,  94,  94}, {128,  92,  92}, {128,  90,  90},
+   {128,  88,  88}, {128,  86,  86}, {128,  84,  84}, {128,  82,  82}, {128,  80,  80},
+   {128,  78,  78}, {128,  77,  77}, {128,  75,  75}, {128,  73,  73}, {128,  71,  71},
+   {128,  69,  69}, {128,  67,  67}, {128,  65,  65}, {128,  63,  63}, {128,  61,  61},
+   {128,  59,  59}, {128,  57,  57}, {128,  56,  56}, {128,  54,  54}, {128,  52,  52},
+   {128,  50,  50}, {128,  48,  48}, {128,  46,  46}, {128,  44,  44}, {128,  42,  42},
+   {128,  40,  40}, {128,  38,  38}, {128,  36,  36}, {128,  34,  34}, {128,  33,  33},
+   {128,  31,  31}, {128,  29,  29}, {128,  27,  27}, {128,  25,  25}, {128,  23,  23},
+   {128,  21,  21}, {128,  19,  19}, {128,  17,  17}, {128,  15,  15}, {128,  13,  13},
+   {128,  11,  11}, {128,  10,  10}, {128,   8,   8}, {128,   6,   6}, {128,   4,   4},
+   {128,   2,   2}, {128,   0,   0}, {128,   2,   0}, {128,   4,   0}, {128,   6,   0},
+   {128,   8,   0}, {128,  10,   0}, {128,  11,   0}, {128,  13,   0}, {128,  15,   0},
+   {128,  17,   0}, {128,  19,   0}, {128,  21,   0}, {128,  23,   0}, {128,  25,   0},
+   {128,  27,   0}, {128,  29,   0}, {128,  31,   0}, {128,  33,   0}, {128,  34,   0},
+   {128,  36,   0}, {128,  38,   0}, {128,  40,   0}, {128,  42,   0}, {128,  44,   0},
+   {128,  46,   0}, {128,  48,   0}, {128,  50,   0}, {128,  52,   0}, {128,  54,   0},
+   {128,  56,   0}, {128,  57,   0}, {128,  59,   0}, {128,  61,   0}, {128,  63,   0},
+   {128,  65,   0}, {128,  67,   0}, {128,  69,   0}, {128,  71,   0}, {128,  73,   0},
+   {128,  75,   0}, {128,  77,   0}, {128,  78,   0}, {128,  80,   0}, {128,  82,   0},
+   {128,  84,   0}, {128,  86,   0}, {128,  88,   0}, {128,  90,   0}, {128,  92,   0},
+   {128,  94,   0}, {128,  96,   0}, {128,  98,   0}, {128, 100,   0}, {128, 101,   0},
+   {128, 103,   0}, {128, 105,   0}, {128, 107,   0}, {128, 109,   0}, {128, 111,   0},
+   {128, 113,   0}, {128, 115,   0}, {128, 117,   0}, {128, 119,   0}, {128, 121,   0},
+   {128, 123,   0}, {128, 124,   0}, {128, 126,   0}, {127, 128,   0}, {125, 128,   0},
+   {123, 128,   0}, {121, 128,   0}, {119, 128,   0}, {117, 128,   0}, {115, 128,   0},
+   {113, 128,   0}, {111, 128,   0}, {110, 128,   0}, {108, 128,   0}, {106, 128,   0},
+   {104, 128,   0}, {102, 128,   0}, {100, 128,   0}, { 98, 128,   0}, { 96, 128,   0},
+   { 94, 128,   0}, { 92, 128,   0}, { 90, 128,   0}, { 88, 128,   0}, { 87, 128,   0},
+   { 85, 128,   0}, { 83, 128,   0}, { 81, 128,   0}, { 79, 128,   0}, { 77, 128,   0},
+   { 75, 128,   0}, { 73, 128,   0}, { 71, 128,   0}, { 69, 128,   0}, { 67, 128,   0},
+   { 65, 128,   0}, { 64, 128,   0}, { 62, 128,   0}, { 60, 128,   0}, { 58, 128,   0},
+   { 56, 128,   0}, { 54, 128,   0}, { 52, 128,   0}, { 50, 128,   0}, { 48, 128,   0},
+   { 46, 128,   0}, { 44, 128,   0}, { 43, 128,   0}, { 41, 128,   0}, { 39, 128,   0},
+   { 37, 128,   0}, { 35, 128,   0}, { 33, 128,   0}, { 31, 128,   0}, { 29, 128,   0},
+   { 27, 128,   0}, { 25, 128,   0}, { 23, 128,   0}, { 21, 128,   0}, { 20, 128,   0},
+   { 18, 128,   0}, { 16, 128,   0}, { 14, 128,   0}, { 12, 128,   0}, { 10, 128,   0},
+   {  8, 128,   0}, {  6, 128,   0}, {  4, 128,   0}, {  2, 128,   0}, {  0, 128,   0},
+   {  0, 128,   2}, {  0, 128,   3}, {  0, 128,   5}, {  0, 128,   7}, {  0, 128,   9},
+   {  0, 128,  11}, {  0, 128,  13}, {  0, 128,  15}, {  0, 128,  17}, {  0, 128,  19},
+   {  0, 128,  21}, {  0, 128,  23}, {  0, 128,  25}, {  0, 128,  26}, {  0, 128,  28},
+   {  0, 128,  30}, {  0, 128,  32}, {  0, 128,  34}, {  0, 128,  36}, {  0, 128,  38},
+   {  0, 128,  40}, {  0, 128,  42}, {  0, 128,  44}, {  0, 128,  46}, {  0, 128,  47},
+   {  0, 128,  49}, {  0, 128,  51}, {  0, 128,  53}, {  0, 128,  55}, {  0, 128,  57},
+   {  0, 128,  59}, {  0, 128,  61}, {  0, 128,  63}, {  0, 128,  65}, {  0, 128,  67},
+   {  0, 128,  69}, {  0, 128,  70}, {  0, 128,  72}, {  0, 128,  74}, {  0, 128,  76},
+   {  0, 128,  78}, {  0, 128,  80}, {  0, 128,  82}, {  0, 128,  84}, {  0, 128,  86},
+   {  0, 128,  88}, {  0, 128,  90}, {  0, 128,  92}, {  0, 128,  93}, {  0, 128,  95},
+   {  0, 128,  97}, {  0, 128,  99}, {  0, 128, 101}, {  0, 128, 103}, {  0, 128, 105},
+   {  0, 128, 107}, {  0, 128, 109}, {  0, 128, 111}, {  0, 128, 113}, {  0, 128, 114},
+   {  0, 128, 116}, {  0, 128, 118}, {  0, 128, 120}, {  0, 128, 122}, {  0, 128, 124},
+   {  0, 128, 126}, {  0, 127, 128}, {  0, 125, 128}, {  0, 123, 128}, {  0, 121, 128},
+   {  0, 119, 128}, {  0, 118, 128}, {  0, 116, 128}, {  0, 114, 128}, {  0, 112, 128},
+   {  0, 110, 128}, {  0, 108, 128}, {  0, 106, 128}, {  0, 104, 128}, {  0, 102, 128},
+   {  0, 100, 128}, {  0,  98, 128}, {  0,  96, 128}, {  0,  95, 128}, {  0,  93, 128},
+   {  0,  91, 128}, {  0,  89, 128}, {  0,  87, 128}, {  0,  85, 128}, {  0,  83, 128},
+   {  0,  81, 128}, {  0,  79, 128}, {  0,  77, 128}, {  0,  75, 128}, {  0,  74, 128},
+   {  0,  72, 128}, {  0,  70, 128}, {  0,  68, 128}, {  0,  66, 128}, {  0,  64, 128},
+   {  0,  62, 128}, {  0,  60, 128}, {  0,  58, 128}, {  0,  56, 128}, {  0,  54, 128},
+   {  0,  52, 128}, {  0,  51, 128}, {  0,  49, 128}, {  0,  47, 128}, {  0,  45, 128},
+   {  0,  43, 128}, {  0,  41, 128}, {  0,  39, 128}, {  0,  37, 128}, {  0,  35, 128},
+   {  0,  33, 128}, {  0,  31, 128}, {  0,  29, 128}, {  0,  28, 128}, {  0,  26, 128},
+   {  0,  24, 128}, {  0,  22, 128}, {  0,  20, 128}, {  0,  18, 128}, {  0,  16, 128},
+   {  0,  14, 128}, {  0,  12, 128}, {  0,  10, 128}, {  0,   8, 128}, {  0,   7, 128},
+   {  0,   5, 128}, {  0,   3, 128}, {  0,   1, 128}, {  1,   0, 128}, {  3,   0, 128},
+   {  5,   0, 128}, {  7,   0, 128}, {  9,   0, 128}, { 11,   0, 128}, { 13,   0, 128},
+   { 15,   0, 128}, { 16,   0, 128}, { 18,   0, 128}, { 20,   0, 128}, { 22,   0, 128},
+   { 24,   0, 128}, { 26,   0, 128}, { 28,   0, 128}, { 30,   0, 128}, { 32,   0, 128},
+   { 34,   0, 128}, { 36,   0, 128}, { 38,   0, 128}, { 39,   0, 128}, { 41,   0, 128},
+   { 43,   0, 128}, { 45,   0, 128}, { 47,   0, 128}, { 49,   0, 128}, { 51,   0, 128},
+   { 53,   0, 128}, { 55,   0, 128}, { 57,   0, 128}, { 59,   0, 128}, { 60,   0, 128},
+   { 62,   0, 128}, { 64,   0, 128}, { 66,   0, 128}, { 68,   0, 128}, { 70,   0, 128},
+   { 72,   0, 128}, { 74,   0, 128}, { 76,   0, 128}, { 78,   0, 128}, { 80,   0, 128},
+   { 82,   0, 128}, { 83,   0, 128}, { 85,   0, 128}, { 87,   0, 128}, { 89,   0, 128},
+   { 91,   0, 128}, { 93,   0, 128}, { 95,   0, 128}, { 97,   0, 128}, { 99,   0, 128},
+   {101,   0, 128}, {103,   0, 128}, {105,   0, 128}, {106,   0, 128}, {108,   0, 128},
+   {110,   0, 128}, {112,   0, 128}, {114,   0, 128}, {116,   0, 128}, {118,   0, 128},
+   {120,   0, 128}, {122,   0, 128}, {124,   0, 128}, {126,   0, 128}, {128,   0, 128}
+};
+
+const rgb_t yarg_colormap[1000] = {
+   {  0,   0,   0}, {  0,   0,   0}, {  1,   1,   1}, {  1,   1,   1}, {  1,   1,   1},
+   {  1,   1,   1}, {  2,   2,   2}, {  2,   2,   2}, {  2,   2,   2}, {  2,   2,   2},
+   {  3,   3,   3}, {  3,   3,   3}, {  3,   3,   3}, {  3,   3,   3}, {  4,   4,   4},
+   {  4,   4,   4}, {  4,   4,   4}, {  4,   4,   4}, {  5,   5,   5}, {  5,   5,   5},
+   {  5,   5,   5}, {  5,   5,   5}, {  6,   6,   6}, {  6,   6,   6}, {  6,   6,   6},
+   {  6,   6,   6}, {  7,   7,   7}, {  7,   7,   7}, {  7,   7,   7}, {  7,   7,   7},
+   {  8,   8,   8}, {  8,   8,   8}, {  8,   8,   8}, {  8,   8,   8}, {  9,   9,   9},
+   {  9,   9,   9}, {  9,   9,   9}, {  9,   9,   9}, { 10,  10,  10}, { 10,  10,  10},
+   { 10,  10,  10}, { 10,  10,  10}, { 11,  11,  11}, { 11,  11,  11}, { 11,  11,  11},
+   { 11,  11,  11}, { 12,  12,  12}, { 12,  12,  12}, { 12,  12,  12}, { 13,  13,  13},
+   { 13,  13,  13}, { 13,  13,  13}, { 13,  13,  13}, { 14,  14,  14}, { 14,  14,  14},
+   { 14,  14,  14}, { 14,  14,  14}, { 15,  15,  15}, { 15,  15,  15}, { 15,  15,  15},
+   { 15,  15,  15}, { 16,  16,  16}, { 16,  16,  16}, { 16,  16,  16}, { 16,  16,  16},
+   { 17,  17,  17}, { 17,  17,  17}, { 17,  17,  17}, { 17,  17,  17}, { 18,  18,  18},
+   { 18,  18,  18}, { 18,  18,  18}, { 18,  18,  18}, { 19,  19,  19}, { 19,  19,  19},
+   { 19,  19,  19}, { 19,  19,  19}, { 20,  20,  20}, { 20,  20,  20}, { 20,  20,  20},
+   { 20,  20,  20}, { 21,  21,  21}, { 21,  21,  21}, { 21,  21,  21}, { 21,  21,  21},
+   { 22,  22,  22}, { 22,  22,  22}, { 22,  22,  22}, { 22,  22,  22}, { 23,  23,  23},
+   { 23,  23,  23}, { 23,  23,  23}, { 23,  23,  23}, { 24,  24,  24}, { 24,  24,  24},
+   { 24,  24,  24}, { 25,  25,  25}, { 25,  25,  25}, { 25,  25,  25}, { 25,  25,  25},
+   { 26,  26,  26}, { 26,  26,  26}, { 26,  26,  26}, { 26,  26,  26}, { 27,  27,  27},
+   { 27,  27,  27}, { 27,  27,  27}, { 27,  27,  27}, { 28,  28,  28}, { 28,  28,  28},
+   { 28,  28,  28}, { 28,  28,  28}, { 29,  29,  29}, { 29,  29,  29}, { 29,  29,  29},
+   { 29,  29,  29}, { 30,  30,  30}, { 30,  30,  30}, { 30,  30,  30}, { 30,  30,  30},
+   { 31,  31,  31}, { 31,  31,  31}, { 31,  31,  31}, { 31,  31,  31}, { 32,  32,  32},
+   { 32,  32,  32}, { 32,  32,  32}, { 32,  32,  32}, { 33,  33,  33}, { 33,  33,  33},
+   { 33,  33,  33}, { 33,  33,  33}, { 34,  34,  34}, { 34,  34,  34}, { 34,  34,  34},
+   { 34,  34,  34}, { 35,  35,  35}, { 35,  35,  35}, { 35,  35,  35}, { 35,  35,  35},
+   { 36,  36,  36}, { 36,  36,  36}, { 36,  36,  36}, { 37,  37,  37}, { 37,  37,  37},
+   { 37,  37,  37}, { 37,  37,  37}, { 38,  38,  38}, { 38,  38,  38}, { 38,  38,  38},
+   { 38,  38,  38}, { 39,  39,  39}, { 39,  39,  39}, { 39,  39,  39}, { 39,  39,  39},
+   { 40,  40,  40}, { 40,  40,  40}, { 40,  40,  40}, { 40,  40,  40}, { 41,  41,  41},
+   { 41,  41,  41}, { 41,  41,  41}, { 41,  41,  41}, { 42,  42,  42}, { 42,  42,  42},
+   { 42,  42,  42}, { 42,  42,  42}, { 43,  43,  43}, { 43,  43,  43}, { 43,  43,  43},
+   { 43,  43,  43}, { 44,  44,  44}, { 44,  44,  44}, { 44,  44,  44}, { 44,  44,  44},
+   { 45,  45,  45}, { 45,  45,  45}, { 45,  45,  45}, { 45,  45,  45}, { 46,  46,  46},
+   { 46,  46,  46}, { 46,  46,  46}, { 46,  46,  46}, { 47,  47,  47}, { 47,  47,  47},
+   { 47,  47,  47}, { 47,  47,  47}, { 48,  48,  48}, { 48,  48,  48}, { 48,  48,  48},
+   { 48,  48,  48}, { 49,  49,  49}, { 49,  49,  49}, { 49,  49,  49}, { 50,  50,  50},
+   { 50,  50,  50}, { 50,  50,  50}, { 50,  50,  50}, { 51,  51,  51}, { 51,  51,  51},
+   { 51,  51,  51}, { 51,  51,  51}, { 52,  52,  52}, { 52,  52,  52}, { 52,  52,  52},
+   { 52,  52,  52}, { 53,  53,  53}, { 53,  53,  53}, { 53,  53,  53}, { 53,  53,  53},
+   { 54,  54,  54}, { 54,  54,  54}, { 54,  54,  54}, { 54,  54,  54}, { 55,  55,  55},
+   { 55,  55,  55}, { 55,  55,  55}, { 55,  55,  55}, { 56,  56,  56}, { 56,  56,  56},
+   { 56,  56,  56}, { 56,  56,  56}, { 57,  57,  57}, { 57,  57,  57}, { 57,  57,  57},
+   { 57,  57,  57}, { 58,  58,  58}, { 58,  58,  58}, { 58,  58,  58}, { 58,  58,  58},
+   { 59,  59,  59}, { 59,  59,  59}, { 59,  59,  59}, { 59,  59,  59}, { 60,  60,  60},
+   { 60,  60,  60}, { 60,  60,  60}, { 60,  60,  60}, { 61,  61,  61}, { 61,  61,  61},
+   { 61,  61,  61}, { 62,  62,  62}, { 62,  62,  62}, { 62,  62,  62}, { 62,  62,  62},
+   { 63,  63,  63}, { 63,  63,  63}, { 63,  63,  63}, { 63,  63,  63}, { 64,  64,  64},
+   { 64,  64,  64}, { 64,  64,  64}, { 64,  64,  64}, { 65,  65,  65}, { 65,  65,  65},
+   { 65,  65,  65}, { 65,  65,  65}, { 66,  66,  66}, { 66,  66,  66}, { 66,  66,  66},
+   { 66,  66,  66}, { 67,  67,  67}, { 67,  67,  67}, { 67,  67,  67}, { 67,  67,  67},
+   { 68,  68,  68}, { 68,  68,  68}, { 68,  68,  68}, { 68,  68,  68}, { 69,  69,  69},
+   { 69,  69,  69}, { 69,  69,  69}, { 69,  69,  69}, { 70,  70,  70}, { 70,  70,  70},
+   { 70,  70,  70}, { 70,  70,  70}, { 71,  71,  71}, { 71,  71,  71}, { 71,  71,  71},
+   { 71,  71,  71}, { 72,  72,  72}, { 72,  72,  72}, { 72,  72,  72}, { 72,  72,  72},
+   { 73,  73,  73}, { 73,  73,  73}, { 73,  73,  73}, { 74,  74,  74}, { 74,  74,  74},
+   { 74,  74,  74}, { 74,  74,  74}, { 75,  75,  75}, { 75,  75,  75}, { 75,  75,  75},
+   { 75,  75,  75}, { 76,  76,  76}, { 76,  76,  76}, { 76,  76,  76}, { 76,  76,  76},
+   { 77,  77,  77}, { 77,  77,  77}, { 77,  77,  77}, { 77,  77,  77}, { 78,  78,  78},
+   { 78,  78,  78}, { 78,  78,  78}, { 78,  78,  78}, { 79,  79,  79}, { 79,  79,  79},
+   { 79,  79,  79}, { 79,  79,  79}, { 80,  80,  80}, { 80,  80,  80}, { 80,  80,  80},
+   { 80,  80,  80}, { 81,  81,  81}, { 81,  81,  81}, { 81,  81,  81}, { 81,  81,  81},
+   { 82,  82,  82}, { 82,  82,  82}, { 82,  82,  82}, { 82,  82,  82}, { 83,  83,  83},
+   { 83,  83,  83}, { 83,  83,  83}, { 83,  83,  83}, { 84,  84,  84}, { 84,  84,  84},
+   { 84,  84,  84}, { 84,  84,  84}, { 85,  85,  85}, { 85,  85,  85}, { 85,  85,  85},
+   { 86,  86,  86}, { 86,  86,  86}, { 86,  86,  86}, { 86,  86,  86}, { 87,  87,  87},
+   { 87,  87,  87}, { 87,  87,  87}, { 87,  87,  87}, { 88,  88,  88}, { 88,  88,  88},
+   { 88,  88,  88}, { 88,  88,  88}, { 89,  89,  89}, { 89,  89,  89}, { 89,  89,  89},
+   { 89,  89,  89}, { 90,  90,  90}, { 90,  90,  90}, { 90,  90,  90}, { 90,  90,  90},
+   { 91,  91,  91}, { 91,  91,  91}, { 91,  91,  91}, { 91,  91,  91}, { 92,  92,  92},
+   { 92,  92,  92}, { 92,  92,  92}, { 92,  92,  92}, { 93,  93,  93}, { 93,  93,  93},
+   { 93,  93,  93}, { 93,  93,  93}, { 94,  94,  94}, { 94,  94,  94}, { 94,  94,  94},
+   { 94,  94,  94}, { 95,  95,  95}, { 95,  95,  95}, { 95,  95,  95}, { 95,  95,  95},
+   { 96,  96,  96}, { 96,  96,  96}, { 96,  96,  96}, { 96,  96,  96}, { 97,  97,  97},
+   { 97,  97,  97}, { 97,  97,  97}, { 98,  98,  98}, { 98,  98,  98}, { 98,  98,  98},
+   { 98,  98,  98}, { 99,  99,  99}, { 99,  99,  99}, { 99,  99,  99}, { 99,  99,  99},
+   {100, 100, 100}, {100, 100, 100}, {100, 100, 100}, {100, 100, 100}, {101, 101, 101},
+   {101, 101, 101}, {101, 101, 101}, {101, 101, 101}, {102, 102, 102}, {102, 102, 102},
+   {102, 102, 102}, {102, 102, 102}, {103, 103, 103}, {103, 103, 103}, {103, 103, 103},
+   {103, 103, 103}, {104, 104, 104}, {104, 104, 104}, {104, 104, 104}, {104, 104, 104},
+   {105, 105, 105}, {105, 105, 105}, {105, 105, 105}, {105, 105, 105}, {106, 106, 106},
+   {106, 106, 106}, {106, 106, 106}, {106, 106, 106}, {107, 107, 107}, {107, 107, 107},
+   {107, 107, 107}, {107, 107, 107}, {108, 108, 108}, {108, 108, 108}, {108, 108, 108},
+   {108, 108, 108}, {109, 109, 109}, {109, 109, 109}, {109, 109, 109}, {110, 110, 110},
+   {110, 110, 110}, {110, 110, 110}, {110, 110, 110}, {111, 111, 111}, {111, 111, 111},
+   {111, 111, 111}, {111, 111, 111}, {112, 112, 112}, {112, 112, 112}, {112, 112, 112},
+   {112, 112, 112}, {113, 113, 113}, {113, 113, 113}, {113, 113, 113}, {113, 113, 113},
+   {114, 114, 114}, {114, 114, 114}, {114, 114, 114}, {114, 114, 114}, {115, 115, 115},
+   {115, 115, 115}, {115, 115, 115}, {115, 115, 115}, {116, 116, 116}, {116, 116, 116},
+   {116, 116, 116}, {116, 116, 116}, {117, 117, 117}, {117, 117, 117}, {117, 117, 117},
+   {117, 117, 117}, {118, 118, 118}, {118, 118, 118}, {118, 118, 118}, {118, 118, 118},
+   {119, 119, 119}, {119, 119, 119}, {119, 119, 119}, {119, 119, 119}, {120, 120, 120},
+   {120, 120, 120}, {120, 120, 120}, {120, 120, 120}, {121, 121, 121}, {121, 121, 121},
+   {121, 121, 121}, {122, 122, 122}, {122, 122, 122}, {122, 122, 122}, {122, 122, 122},
+   {123, 123, 123}, {123, 123, 123}, {123, 123, 123}, {123, 123, 123}, {124, 124, 124},
+   {124, 124, 124}, {124, 124, 124}, {124, 124, 124}, {125, 125, 125}, {125, 125, 125},
+   {125, 125, 125}, {125, 125, 125}, {126, 126, 126}, {126, 126, 126}, {126, 126, 126},
+   {126, 126, 126}, {127, 127, 127}, {127, 127, 127}, {127, 127, 127}, {127, 127, 127},
+   {128, 128, 128}, {128, 128, 128}, {128, 128, 128}, {128, 128, 128}, {129, 129, 129},
+   {129, 129, 129}, {129, 129, 129}, {129, 129, 129}, {130, 130, 130}, {130, 130, 130},
+   {130, 130, 130}, {130, 130, 130}, {131, 131, 131}, {131, 131, 131}, {131, 131, 131},
+   {131, 131, 131}, {132, 132, 132}, {132, 132, 132}, {132, 132, 132}, {132, 132, 132},
+   {133, 133, 133}, {133, 133, 133}, {133, 133, 133}, {133, 133, 133}, {134, 134, 134},
+   {134, 134, 134}, {134, 134, 134}, {135, 135, 135}, {135, 135, 135}, {135, 135, 135},
+   {135, 135, 135}, {136, 136, 136}, {136, 136, 136}, {136, 136, 136}, {136, 136, 136},
+   {137, 137, 137}, {137, 137, 137}, {137, 137, 137}, {137, 137, 137}, {138, 138, 138},
+   {138, 138, 138}, {138, 138, 138}, {138, 138, 138}, {139, 139, 139}, {139, 139, 139},
+   {139, 139, 139}, {139, 139, 139}, {140, 140, 140}, {140, 140, 140}, {140, 140, 140},
+   {140, 140, 140}, {141, 141, 141}, {141, 141, 141}, {141, 141, 141}, {141, 141, 141},
+   {142, 142, 142}, {142, 142, 142}, {142, 142, 142}, {142, 142, 142}, {143, 143, 143},
+   {143, 143, 143}, {143, 143, 143}, {143, 143, 143}, {144, 144, 144}, {144, 144, 144},
+   {144, 144, 144}, {144, 144, 144}, {145, 145, 145}, {145, 145, 145}, {145, 145, 145},
+   {145, 145, 145}, {146, 146, 146}, {146, 146, 146}, {146, 146, 146}, {147, 147, 147},
+   {147, 147, 147}, {147, 147, 147}, {147, 147, 147}, {148, 148, 148}, {148, 148, 148},
+   {148, 148, 148}, {148, 148, 148}, {149, 149, 149}, {149, 149, 149}, {149, 149, 149},
+   {149, 149, 149}, {150, 150, 150}, {150, 150, 150}, {150, 150, 150}, {150, 150, 150},
+   {151, 151, 151}, {151, 151, 151}, {151, 151, 151}, {151, 151, 151}, {152, 152, 152},
+   {152, 152, 152}, {152, 152, 152}, {152, 152, 152}, {153, 153, 153}, {153, 153, 153},
+   {153, 153, 153}, {153, 153, 153}, {154, 154, 154}, {154, 154, 154}, {154, 154, 154},
+   {154, 154, 154}, {155, 155, 155}, {155, 155, 155}, {155, 155, 155}, {155, 155, 155},
+   {156, 156, 156}, {156, 156, 156}, {156, 156, 156}, {156, 156, 156}, {157, 157, 157},
+   {157, 157, 157}, {157, 157, 157}, {157, 157, 157}, {158, 158, 158}, {158, 158, 158},
+   {158, 158, 158}, {159, 159, 159}, {159, 159, 159}, {159, 159, 159}, {159, 159, 159},
+   {160, 160, 160}, {160, 160, 160}, {160, 160, 160}, {160, 160, 160}, {161, 161, 161},
+   {161, 161, 161}, {161, 161, 161}, {161, 161, 161}, {162, 162, 162}, {162, 162, 162},
+   {162, 162, 162}, {162, 162, 162}, {163, 163, 163}, {163, 163, 163}, {163, 163, 163},
+   {163, 163, 163}, {164, 164, 164}, {164, 164, 164}, {164, 164, 164}, {164, 164, 164},
+   {165, 165, 165}, {165, 165, 165}, {165, 165, 165}, {165, 165, 165}, {166, 166, 166},
+   {166, 166, 166}, {166, 166, 166}, {166, 166, 166}, {167, 167, 167}, {167, 167, 167},
+   {167, 167, 167}, {167, 167, 167}, {168, 168, 168}, {168, 168, 168}, {168, 168, 168},
+   {168, 168, 168}, {169, 169, 169}, {169, 169, 169}, {169, 169, 169}, {169, 169, 169},
+   {170, 170, 170}, {170, 170, 170}, {170, 170, 170}, {171, 171, 171}, {171, 171, 171},
+   {171, 171, 171}, {171, 171, 171}, {172, 172, 172}, {172, 172, 172}, {172, 172, 172},
+   {172, 172, 172}, {173, 173, 173}, {173, 173, 173}, {173, 173, 173}, {173, 173, 173},
+   {174, 174, 174}, {174, 174, 174}, {174, 174, 174}, {174, 174, 174}, {175, 175, 175},
+   {175, 175, 175}, {175, 175, 175}, {175, 175, 175}, {176, 176, 176}, {176, 176, 176},
+   {176, 176, 176}, {176, 176, 176}, {177, 177, 177}, {177, 177, 177}, {177, 177, 177},
+   {177, 177, 177}, {178, 178, 178}, {178, 178, 178}, {178, 178, 178}, {178, 178, 178},
+   {179, 179, 179}, {179, 179, 179}, {179, 179, 179}, {179, 179, 179}, {180, 180, 180},
+   {180, 180, 180}, {180, 180, 180}, {180, 180, 180}, {181, 181, 181}, {181, 181, 181},
+   {181, 181, 181}, {181, 181, 181}, {182, 182, 182}, {182, 182, 182}, {182, 182, 182},
+   {183, 183, 183}, {183, 183, 183}, {183, 183, 183}, {183, 183, 183}, {184, 184, 184},
+   {184, 184, 184}, {184, 184, 184}, {184, 184, 184}, {185, 185, 185}, {185, 185, 185},
+   {185, 185, 185}, {185, 185, 185}, {186, 186, 186}, {186, 186, 186}, {186, 186, 186},
+   {186, 186, 186}, {187, 187, 187}, {187, 187, 187}, {187, 187, 187}, {187, 187, 187},
+   {188, 188, 188}, {188, 188, 188}, {188, 188, 188}, {188, 188, 188}, {189, 189, 189},
+   {189, 189, 189}, {189, 189, 189}, {189, 189, 189}, {190, 190, 190}, {190, 190, 190},
+   {190, 190, 190}, {190, 190, 190}, {191, 191, 191}, {191, 191, 191}, {191, 191, 191},
+   {191, 191, 191}, {192, 192, 192}, {192, 192, 192}, {192, 192, 192}, {192, 192, 192},
+   {193, 193, 193}, {193, 193, 193}, {193, 193, 193}, {193, 193, 193}, {194, 194, 194},
+   {194, 194, 194}, {194, 194, 194}, {195, 195, 195}, {195, 195, 195}, {195, 195, 195},
+   {195, 195, 195}, {196, 196, 196}, {196, 196, 196}, {196, 196, 196}, {196, 196, 196},
+   {197, 197, 197}, {197, 197, 197}, {197, 197, 197}, {197, 197, 197}, {198, 198, 198},
+   {198, 198, 198}, {198, 198, 198}, {198, 198, 198}, {199, 199, 199}, {199, 199, 199},
+   {199, 199, 199}, {199, 199, 199}, {200, 200, 200}, {200, 200, 200}, {200, 200, 200},
+   {200, 200, 200}, {201, 201, 201}, {201, 201, 201}, {201, 201, 201}, {201, 201, 201},
+   {202, 202, 202}, {202, 202, 202}, {202, 202, 202}, {202, 202, 202}, {203, 203, 203},
+   {203, 203, 203}, {203, 203, 203}, {203, 203, 203}, {204, 204, 204}, {204, 204, 204},
+   {204, 204, 204}, {204, 204, 204}, {205, 205, 205}, {205, 205, 205}, {205, 205, 205},
+   {205, 205, 205}, {206, 206, 206}, {206, 206, 206}, {206, 206, 206}, {207, 207, 207},
+   {207, 207, 207}, {207, 207, 207}, {207, 207, 207}, {208, 208, 208}, {208, 208, 208},
+   {208, 208, 208}, {208, 208, 208}, {209, 209, 209}, {209, 209, 209}, {209, 209, 209},
+   {209, 209, 209}, {210, 210, 210}, {210, 210, 210}, {210, 210, 210}, {210, 210, 210},
+   {211, 211, 211}, {211, 211, 211}, {211, 211, 211}, {211, 211, 211}, {212, 212, 212},
+   {212, 212, 212}, {212, 212, 212}, {212, 212, 212}, {213, 213, 213}, {213, 213, 213},
+   {213, 213, 213}, {213, 213, 213}, {214, 214, 214}, {214, 214, 214}, {214, 214, 214},
+   {214, 214, 214}, {215, 215, 215}, {215, 215, 215}, {215, 215, 215}, {215, 215, 215},
+   {216, 216, 216}, {216, 216, 216}, {216, 216, 216}, {216, 216, 216}, {217, 217, 217},
+   {217, 217, 217}, {217, 217, 217}, {217, 217, 217}, {218, 218, 218}, {218, 218, 218},
+   {218, 218, 218}, {218, 218, 218}, {219, 219, 219}, {219, 219, 219}, {219, 219, 219},
+   {220, 220, 220}, {220, 220, 220}, {220, 220, 220}, {220, 220, 220}, {221, 221, 221},
+   {221, 221, 221}, {221, 221, 221}, {221, 221, 221}, {222, 222, 222}, {222, 222, 222},
+   {222, 222, 222}, {222, 222, 222}, {223, 223, 223}, {223, 223, 223}, {223, 223, 223},
+   {223, 223, 223}, {224, 224, 224}, {224, 224, 224}, {224, 224, 224}, {224, 224, 224},
+   {225, 225, 225}, {225, 225, 225}, {225, 225, 225}, {225, 225, 225}, {226, 226, 226},
+   {226, 226, 226}, {226, 226, 226}, {226, 226, 226}, {227, 227, 227}, {227, 227, 227},
+   {227, 227, 227}, {227, 227, 227}, {228, 228, 228}, {228, 228, 228}, {228, 228, 228},
+   {228, 228, 228}, {229, 229, 229}, {229, 229, 229}, {229, 229, 229}, {229, 229, 229},
+   {230, 230, 230}, {230, 230, 230}, {230, 230, 230}, {230, 230, 230}, {231, 231, 231},
+   {231, 231, 231}, {231, 231, 231}, {232, 232, 232}, {232, 232, 232}, {232, 232, 232},
+   {232, 232, 232}, {233, 233, 233}, {233, 233, 233}, {233, 233, 233}, {233, 233, 233},
+   {234, 234, 234}, {234, 234, 234}, {234, 234, 234}, {234, 234, 234}, {235, 235, 235},
+   {235, 235, 235}, {235, 235, 235}, {235, 235, 235}, {236, 236, 236}, {236, 236, 236},
+   {236, 236, 236}, {236, 236, 236}, {237, 237, 237}, {237, 237, 237}, {237, 237, 237},
+   {237, 237, 237}, {238, 238, 238}, {238, 238, 238}, {238, 238, 238}, {238, 238, 238},
+   {239, 239, 239}, {239, 239, 239}, {239, 239, 239}, {239, 239, 239}, {240, 240, 240},
+   {240, 240, 240}, {240, 240, 240}, {240, 240, 240}, {241, 241, 241}, {241, 241, 241},
+   {241, 241, 241}, {241, 241, 241}, {242, 242, 242}, {242, 242, 242}, {242, 242, 242},
+   {242, 242, 242}, {243, 243, 243}, {243, 243, 243}, {243, 243, 243}, {244, 244, 244},
+   {244, 244, 244}, {244, 244, 244}, {244, 244, 244}, {245, 245, 245}, {245, 245, 245},
+   {245, 245, 245}, {245, 245, 245}, {246, 246, 246}, {246, 246, 246}, {246, 246, 246},
+   {246, 246, 246}, {247, 247, 247}, {247, 247, 247}, {247, 247, 247}, {247, 247, 247},
+   {248, 248, 248}, {248, 248, 248}, {248, 248, 248}, {248, 248, 248}, {249, 249, 249},
+   {249, 249, 249}, {249, 249, 249}, {249, 249, 249}, {250, 250, 250}, {250, 250, 250},
+   {250, 250, 250}, {250, 250, 250}, {251, 251, 251}, {251, 251, 251}, {251, 251, 251},
+   {251, 251, 251}, {252, 252, 252}, {252, 252, 252}, {252, 252, 252}, {252, 252, 252},
+   {253, 253, 253}, {253, 253, 253}, {253, 253, 253}, {253, 253, 253}, {254, 254, 254},
+   {254, 254, 254}, {254, 254, 254}, {254, 254, 254}, {255, 255, 255}, {255, 255, 255}
+};
+
+#endif

+ 11 - 0
code/lib/jomjol_image_proc/make_stb.cpp

@@ -0,0 +1,11 @@
+#include <stdint.h>
+#include <string>
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h"
+
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+#include "stb_image_write.h"
+
+#define STB_IMAGE_RESIZE_IMPLEMENTATION
+#include "stb_image_resize.h"

+ 7657 - 0
code/lib/jomjol_image_proc/stb_image.h

@@ -0,0 +1,7657 @@
+/* stb_image - v2.25 - public domain image loader - http://nothings.org/stb
+                                  no warranty implied; use at your own risk
+
+   Do this:
+      #define STB_IMAGE_IMPLEMENTATION
+   before you include this file in *one* C or C++ file to create the implementation.
+
+   // i.e. it should look like this:
+   #include ...
+   #include ...
+   #include ...
+   #define STB_IMAGE_IMPLEMENTATION
+   #include "stb_image.h"
+
+   You can #define STBI_ASSERT(x) before the #include to avoid using assert.h.
+   And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free
+
+
+   QUICK NOTES:
+      Primarily of interest to game developers and other people who can
+          avoid problematic images and only need the trivial interface
+
+      JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib)
+      PNG 1/2/4/8/16-bit-per-channel
+
+      TGA (not sure what subset, if a subset)
+      BMP non-1bpp, non-RLE
+      PSD (composited view only, no extra channels, 8/16 bit-per-channel)
+
+      GIF (*comp always reports as 4-channel)
+      HDR (radiance rgbE format)
+      PIC (Softimage PIC)
+      PNM (PPM and PGM binary only)
+
+      Animated GIF still needs a proper API, but here's one way to do it:
+          http://gist.github.com/urraka/685d9a6340b26b830d49
+
+      - decode from memory or through FILE (define STBI_NO_STDIO to remove code)
+      - decode from arbitrary I/O callbacks
+      - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON)
+
+   Full documentation under "DOCUMENTATION" below.
+
+
+LICENSE
+
+  See end of file for license information.
+
+RECENT REVISION HISTORY:
+
+      2.25  (2020-02-02) fix warnings
+      2.24  (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically
+      2.23  (2019-08-11) fix clang static analysis warning
+      2.22  (2019-03-04) gif fixes, fix warnings
+      2.21  (2019-02-25) fix typo in comment
+      2.20  (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs
+      2.19  (2018-02-11) fix warning
+      2.18  (2018-01-30) fix warnings
+      2.17  (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings
+      2.16  (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes
+      2.15  (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC
+      2.14  (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs
+      2.13  (2016-12-04) experimental 16-bit API, only for PNG so far; fixes
+      2.12  (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
+      2.11  (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64
+                         RGB-format JPEG; remove white matting in PSD;
+                         allocate large structures on the stack;
+                         correct channel count for PNG & BMP
+      2.10  (2016-01-22) avoid warning introduced in 2.09
+      2.09  (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED
+
+   See end of file for full revision history.
+
+
+ ============================    Contributors    =========================
+
+ Image formats                          Extensions, features
+    Sean Barrett (jpeg, png, bmp)          Jetro Lauha (stbi_info)
+    Nicolas Schulz (hdr, psd)              Martin "SpartanJ" Golini (stbi_info)
+    Jonathan Dummer (tga)                  James "moose2000" Brown (iPhone PNG)
+    Jean-Marc Lienher (gif)                Ben "Disch" Wenger (io callbacks)
+    Tom Seddon (pic)                       Omar Cornut (1/2/4-bit PNG)
+    Thatcher Ulrich (psd)                  Nicolas Guillemot (vertical flip)
+    Ken Miller (pgm, ppm)                  Richard Mitton (16-bit PSD)
+    github:urraka (animated gif)           Junggon Kim (PNM comments)
+    Christopher Forseth (animated gif)     Daniel Gibson (16-bit TGA)
+                                           socks-the-fox (16-bit PNG)
+                                           Jeremy Sawicki (handle all ImageNet JPGs)
+ Optimizations & bugfixes                  Mikhail Morozov (1-bit BMP)
+    Fabian "ryg" Giesen                    Anael Seghezzi (is-16-bit query)
+    Arseny Kapoulkine
+    John-Mark Allen
+    Carmelo J Fdez-Aguera
+
+ Bug & warning fixes
+    Marc LeBlanc            David Woo          Guillaume George   Martins Mozeiko
+    Christpher Lloyd        Jerry Jansson      Joseph Thomson     Phil Jordan
+    Dave Moore              Roy Eltham         Hayaki Saito       Nathan Reed
+    Won Chun                Luke Graham        Johan Duparc       Nick Verigakis
+    the Horde3D community   Thomas Ruf         Ronny Chevalier    github:rlyeh
+    Janez Zemva             John Bartholomew   Michal Cichon      github:romigrou
+    Jonathan Blow           Ken Hamada         Tero Hanninen      github:svdijk
+    Laurent Gomila          Cort Stratton      Sergio Gonzalez    github:snagar
+    Aruelien Pocheville     Thibault Reuille   Cass Everitt       github:Zelex
+    Ryamond Barbiero        Paul Du Bois       Engin Manap        github:grim210
+    Aldo Culquicondor       Philipp Wiesemann  Dale Weiler        github:sammyhw
+    Oriol Ferrer Mesia      Josh Tobin         Matthew Gregan     github:phprus
+    Julian Raschke          Gregory Mullen     Baldur Karlsson    github:poppolopoppo
+    Christian Floisand      Kevin Schmidt      JR Smith           github:darealshinji
+    Brad Weinberger         Matvey Cherevko                       github:Michaelangel007
+    Blazej Dariusz Roszkowski                  Alexander Veselov
+*/
+
+
+#ifndef STBI_INCLUDE_STB_IMAGE_H
+#define STBI_INCLUDE_STB_IMAGE_H
+
+// DOCUMENTATION
+//
+// Limitations:
+//    - no 12-bit-per-channel JPEG
+//    - no JPEGs with arithmetic coding
+//    - GIF always returns *comp=4
+//
+// Basic usage (see HDR discussion below for HDR usage):
+//    int x,y,n;
+//    unsigned char *data = stbi_load(filename, &x, &y, &n, 0);
+//    // ... process data if not NULL ...
+//    // ... x = width, y = height, n = # 8-bit components per pixel ...
+//    // ... replace '0' with '1'..'4' to force that many components per pixel
+//    // ... but 'n' will always be the number that it would have been if you said 0
+//    stbi_image_free(data)
+//
+// Standard parameters:
+//    int *x                 -- outputs image width in pixels
+//    int *y                 -- outputs image height in pixels
+//    int *channels_in_file  -- outputs # of image components in image file
+//    int desired_channels   -- if non-zero, # of image components requested in result
+//
+// The return value from an image loader is an 'unsigned char *' which points
+// to the pixel data, or NULL on an allocation failure or if the image is
+// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels,
+// with each pixel consisting of N interleaved 8-bit components; the first
+// pixel pointed to is top-left-most in the image. There is no padding between
+// image scanlines or between pixels, regardless of format. The number of
+// components N is 'desired_channels' if desired_channels is non-zero, or
+// *channels_in_file otherwise. If desired_channels is non-zero,
+// *channels_in_file has the number of components that _would_ have been
+// output otherwise. E.g. if you set desired_channels to 4, you will always
+// get RGBA output, but you can check *channels_in_file to see if it's trivially
+// opaque because e.g. there were only 3 channels in the source image.
+//
+// An output image with N components has the following components interleaved
+// in this order in each pixel:
+//
+//     N=#comp     components
+//       1           grey
+//       2           grey, alpha
+//       3           red, green, blue
+//       4           red, green, blue, alpha
+//
+// If image loading fails for any reason, the return value will be NULL,
+// and *x, *y, *channels_in_file will be unchanged. The function
+// stbi_failure_reason() can be queried for an extremely brief, end-user
+// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS
+// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly
+// more user-friendly ones.
+//
+// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized.
+//
+// ===========================================================================
+//
+// UNICODE:
+//
+//   If compiling for Windows and you wish to use Unicode filenames, compile
+//   with
+//       #define STBI_WINDOWS_UTF8
+//   and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert
+//   Windows wchar_t filenames to utf8.
+//
+// ===========================================================================
+//
+// Philosophy
+//
+// stb libraries are designed with the following priorities:
+//
+//    1. easy to use
+//    2. easy to maintain
+//    3. good performance
+//
+// Sometimes I let "good performance" creep up in priority over "easy to maintain",
+// and for best performance I may provide less-easy-to-use APIs that give higher
+// performance, in addition to the easy-to-use ones. Nevertheless, it's important
+// to keep in mind that from the standpoint of you, a client of this library,
+// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all.
+//
+// Some secondary priorities arise directly from the first two, some of which
+// provide more explicit reasons why performance can't be emphasized.
+//
+//    - Portable ("ease of use")
+//    - Small source code footprint ("easy to maintain")
+//    - No dependencies ("ease of use")
+//
+// ===========================================================================
+//
+// I/O callbacks
+//
+// I/O callbacks allow you to read from arbitrary sources, like packaged
+// files or some other source. Data read from callbacks are processed
+// through a small internal buffer (currently 128 bytes) to try to reduce
+// overhead.
+//
+// The three functions you must define are "read" (reads some bytes of data),
+// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end).
+//
+// ===========================================================================
+//
+// SIMD support
+//
+// The JPEG decoder will try to automatically use SIMD kernels on x86 when
+// supported by the compiler. For ARM Neon support, you must explicitly
+// request it.
+//
+// (The old do-it-yourself SIMD API is no longer supported in the current
+// code.)
+//
+// On x86, SSE2 will automatically be used when available based on a run-time
+// test; if not, the generic C versions are used as a fall-back. On ARM targets,
+// the typical path is to have separate builds for NEON and non-NEON devices
+// (at least this is true for iOS and Android). Therefore, the NEON support is
+// toggled by a build flag: define STBI_NEON to get NEON loops.
+//
+// If for some reason you do not want to use any of SIMD code, or if
+// you have issues compiling it, you can disable it entirely by
+// defining STBI_NO_SIMD.
+//
+// ===========================================================================
+//
+// HDR image support   (disable by defining STBI_NO_HDR)
+//
+// stb_image supports loading HDR images in general, and currently the Radiance
+// .HDR file format specifically. You can still load any file through the existing
+// interface; if you attempt to load an HDR file, it will be automatically remapped
+// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1;
+// both of these constants can be reconfigured through this interface:
+//
+//     stbi_hdr_to_ldr_gamma(2.2f);
+//     stbi_hdr_to_ldr_scale(1.0f);
+//
+// (note, do not use _inverse_ constants; stbi_image will invert them
+// appropriately).
+//
+// Additionally, there is a new, parallel interface for loading files as
+// (linear) floats to preserve the full dynamic range:
+//
+//    float *data = stbi_loadf(filename, &x, &y, &n, 0);
+//
+// If you load LDR images through this interface, those images will
+// be promoted to floating point values, run through the inverse of
+// constants corresponding to the above:
+//
+//     stbi_ldr_to_hdr_scale(1.0f);
+//     stbi_ldr_to_hdr_gamma(2.2f);
+//
+// Finally, given a filename (or an open file or memory block--see header
+// file for details) containing image data, you can query for the "most
+// appropriate" interface to use (that is, whether the image is HDR or
+// not), using:
+//
+//     stbi_is_hdr(char *filename);
+//
+// ===========================================================================
+//
+// iPhone PNG support:
+//
+// By default we convert iphone-formatted PNGs back to RGB, even though
+// they are internally encoded differently. You can disable this conversion
+// by calling stbi_convert_iphone_png_to_rgb(0), in which case
+// you will always just get the native iphone "format" through (which
+// is BGR stored in RGB).
+//
+// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per
+// pixel to remove any premultiplied alpha *only* if the image file explicitly
+// says there's premultiplied data (currently only happens in iPhone images,
+// and only if iPhone convert-to-rgb processing is on).
+//
+// ===========================================================================
+//
+// ADDITIONAL CONFIGURATION
+//
+//  - You can suppress implementation of any of the decoders to reduce
+//    your code footprint by #defining one or more of the following
+//    symbols before creating the implementation.
+//
+//        STBI_NO_JPEG
+//        STBI_NO_PNG
+//        STBI_NO_BMP
+//        STBI_NO_PSD
+//        STBI_NO_TGA
+//        STBI_NO_GIF
+//        STBI_NO_HDR
+//        STBI_NO_PIC
+//        STBI_NO_PNM   (.ppm and .pgm)
+//
+//  - You can request *only* certain decoders and suppress all other ones
+//    (this will be more forward-compatible, as addition of new decoders
+//    doesn't require you to disable them explicitly):
+//
+//        STBI_ONLY_JPEG
+//        STBI_ONLY_PNG
+//        STBI_ONLY_BMP
+//        STBI_ONLY_PSD
+//        STBI_ONLY_TGA
+//        STBI_ONLY_GIF
+//        STBI_ONLY_HDR
+//        STBI_ONLY_PIC
+//        STBI_ONLY_PNM   (.ppm and .pgm)
+//
+//   - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still
+//     want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB
+//
+
+
+#ifndef STBI_NO_STDIO
+#include <stdio.h>
+#endif // STBI_NO_STDIO
+
+#define STBI_VERSION 1
+
+enum
+{
+   STBI_default = 0, // only used for desired_channels
+
+   STBI_grey       = 1,
+   STBI_grey_alpha = 2,
+   STBI_rgb        = 3,
+   STBI_rgb_alpha  = 4
+};
+
+#include <stdlib.h>
+typedef unsigned char stbi_uc;
+typedef unsigned short stbi_us;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef STBIDEF
+#ifdef STB_IMAGE_STATIC
+#define STBIDEF static
+#else
+#define STBIDEF extern
+#endif
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// PRIMARY API - works on images of any type
+//
+
+//
+// load image by filename, open file, or memory buffer
+//
+
+typedef struct
+{
+   int      (*read)  (void *user,char *data,int size);   // fill 'data' with 'size' bytes.  return number of bytes actually read
+   void     (*skip)  (void *user,int n);                 // skip the next 'n' bytes, or 'unget' the last -n bytes if negative
+   int      (*eof)   (void *user);                       // returns nonzero if we are at end of file/data
+} stbi_io_callbacks;
+
+////////////////////////////////////
+//
+// 8-bits-per-channel interface
+//
+
+STBIDEF stbi_uc *stbi_load_from_memory   (stbi_uc           const *buffer, int len   , int *x, int *y, int *channels_in_file, int desired_channels);
+STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk  , void *user, int *x, int *y, int *channels_in_file, int desired_channels);
+
+#ifndef STBI_NO_STDIO
+STBIDEF stbi_uc *stbi_load            (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
+STBIDEF stbi_uc *stbi_load_from_file  (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
+// for stbi_load_from_file, file pointer is left pointing immediately after image
+#endif
+
+#ifndef STBI_NO_GIF
+STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp);
+#endif
+
+#ifdef STBI_WINDOWS_UTF8
+STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input);
+#endif
+
+////////////////////////////////////
+//
+// 16-bits-per-channel interface
+//
+
+STBIDEF stbi_us *stbi_load_16_from_memory   (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);
+STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels);
+
+#ifndef STBI_NO_STDIO
+STBIDEF stbi_us *stbi_load_16          (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
+STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
+#endif
+
+////////////////////////////////////
+//
+// float-per-channel interface
+//
+#ifndef STBI_NO_LINEAR
+   STBIDEF float *stbi_loadf_from_memory     (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels);
+   STBIDEF float *stbi_loadf_from_callbacks  (stbi_io_callbacks const *clbk, void *user, int *x, int *y,  int *channels_in_file, int desired_channels);
+
+   #ifndef STBI_NO_STDIO
+   STBIDEF float *stbi_loadf            (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels);
+   STBIDEF float *stbi_loadf_from_file  (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels);
+   #endif
+#endif
+
+#ifndef STBI_NO_HDR
+   STBIDEF void   stbi_hdr_to_ldr_gamma(float gamma);
+   STBIDEF void   stbi_hdr_to_ldr_scale(float scale);
+#endif // STBI_NO_HDR
+
+#ifndef STBI_NO_LINEAR
+   STBIDEF void   stbi_ldr_to_hdr_gamma(float gamma);
+   STBIDEF void   stbi_ldr_to_hdr_scale(float scale);
+#endif // STBI_NO_LINEAR
+
+// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR
+STBIDEF int    stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user);
+STBIDEF int    stbi_is_hdr_from_memory(stbi_uc const *buffer, int len);
+#ifndef STBI_NO_STDIO
+STBIDEF int      stbi_is_hdr          (char const *filename);
+STBIDEF int      stbi_is_hdr_from_file(FILE *f);
+#endif // STBI_NO_STDIO
+
+
+// get a VERY brief reason for failure
+// on most compilers (and ALL modern mainstream compilers) this is threadsafe
+STBIDEF const char *stbi_failure_reason  (void);
+
+// free the loaded image -- this is just free()
+STBIDEF void     stbi_image_free      (void *retval_from_stbi_load);
+
+// get image dimensions & components without fully decoding
+STBIDEF int      stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp);
+STBIDEF int      stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp);
+STBIDEF int      stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len);
+STBIDEF int      stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user);
+
+#ifndef STBI_NO_STDIO
+STBIDEF int      stbi_info               (char const *filename,     int *x, int *y, int *comp);
+STBIDEF int      stbi_info_from_file     (FILE *f,                  int *x, int *y, int *comp);
+STBIDEF int      stbi_is_16_bit          (char const *filename);
+STBIDEF int      stbi_is_16_bit_from_file(FILE *f);
+#endif
+
+
+
+// for image formats that explicitly notate that they have premultiplied alpha,
+// we just return the colors as stored in the file. set this flag to force
+// unpremultiplication. results are undefined if the unpremultiply overflow.
+STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply);
+
+// indicate whether we should process iphone images back to canonical format,
+// or just pass them through "as-is"
+STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert);
+
+// flip the image vertically, so the first pixel in the output array is the bottom left
+STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip);
+
+// as above, but only applies to images loaded on the thread that calls the function
+// this function is only available if your compiler supports thread-local variables;
+// calling it will fail to link if your compiler doesn't
+STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip);
+
+// ZLIB client - used by PNG, available for other purposes
+
+STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen);
+STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header);
+STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen);
+STBIDEF int   stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
+
+STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen);
+STBIDEF int   stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+//
+//
+////   end header file   /////////////////////////////////////////////////////
+#endif // STBI_INCLUDE_STB_IMAGE_H
+
+#ifdef STB_IMAGE_IMPLEMENTATION
+
+#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \
+  || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \
+  || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \
+  || defined(STBI_ONLY_ZLIB)
+   #ifndef STBI_ONLY_JPEG
+   #define STBI_NO_JPEG
+   #endif
+   #ifndef STBI_ONLY_PNG
+   #define STBI_NO_PNG
+   #endif
+   #ifndef STBI_ONLY_BMP
+   #define STBI_NO_BMP
+   #endif
+   #ifndef STBI_ONLY_PSD
+   #define STBI_NO_PSD
+   #endif
+   #ifndef STBI_ONLY_TGA
+   #define STBI_NO_TGA
+   #endif
+   #ifndef STBI_ONLY_GIF
+   #define STBI_NO_GIF
+   #endif
+   #ifndef STBI_ONLY_HDR
+   #define STBI_NO_HDR
+   #endif
+   #ifndef STBI_ONLY_PIC
+   #define STBI_NO_PIC
+   #endif
+   #ifndef STBI_ONLY_PNM
+   #define STBI_NO_PNM
+   #endif
+#endif
+
+#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB)
+#define STBI_NO_ZLIB
+#endif
+
+
+#include <stdarg.h>
+#include <stddef.h> // ptrdiff_t on osx
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)
+#include <math.h>  // ldexp, pow
+#endif
+
+#ifndef STBI_NO_STDIO
+#include <stdio.h>
+#endif
+
+#ifndef STBI_ASSERT
+#include <assert.h>
+#define STBI_ASSERT(x) assert(x)
+#endif
+
+#ifdef __cplusplus
+#define STBI_EXTERN extern "C"
+#else
+#define STBI_EXTERN extern
+#endif
+
+
+#ifndef _MSC_VER
+   #ifdef __cplusplus
+   #define stbi_inline inline
+   #else
+   #define stbi_inline
+   #endif
+#else
+   #define stbi_inline __forceinline
+#endif
+
+#ifndef STBI_NO_THREAD_LOCALS
+   #if defined(__cplusplus) &&  __cplusplus >= 201103L
+      #define STBI_THREAD_LOCAL       thread_local
+   #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
+      #define STBI_THREAD_LOCAL       _Thread_local
+   #elif defined(__GNUC__)
+      #define STBI_THREAD_LOCAL       __thread
+   #elif defined(_MSC_VER)
+      #define STBI_THREAD_LOCAL       __declspec(thread)
+#endif
+#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) || defined(STBI_REALLOC_SIZED))
+// ok
+#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED)
+// ok
+#else
+#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)."
+#endif
+
+#ifndef STBI_MALLOC
+#define STBI_MALLOC(sz)           malloc(sz)
+#define STBI_REALLOC(p,newsz)     realloc(p,newsz)
+#define STBI_FREE(p)              free(p)
+#endif
+
+#ifndef STBI_REALLOC_SIZED
+#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz)
+#endif
+
+// x86/x64 detection
+#if defined(__x86_64__) || defined(_M_X64)
+#define STBI__X64_TARGET
+#elif defined(__i386) || defined(_M_IX86)
+#define STBI__X86_TARGET
+#endif
+
+#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD)
+// gcc doesn't support sse2 intrinsics unless you compile with -msse2,
+// which in turn means it gets to use SSE2 everywhere. This is unfortunate,
+// but previous attempts to provide the SSE2 functions with runtime
+// detection caused numerous issues. The way architecture extensions are
+// exposed in GCC/Clang is, sadly, not really suited for one-file libs.
+// New behavior: if compiled with -msse2, we use SSE2 without any
+// detection; if not, we don't use it at all.
+#define STBI_NO_SIMD
+#endif
+
+#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD)
+// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET
+//
+// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the
+// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant.
+// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not
+// simultaneously enabling "-mstackrealign".
+//
+// See https://github.com/nothings/stb/issues/81 for more information.
+//
+// So default to no SSE2 on 32-bit MinGW. If you've read this far and added
+// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2.
+#define STBI_NO_SIMD
+#endif
+
+#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET))
+#define STBI_SSE2
+#include <emmintrin.h>
+
+#ifdef _MSC_VER
+
+#if _MSC_VER >= 1400  // not VC6
+#include <intrin.h> // __cpuid
+static int stbi__cpuid3(void)
+{
+   int info[4];
+   __cpuid(info,1);
+   return info[3];
+}
+#else
+static int stbi__cpuid3(void)
+{
+   int res;
+   __asm {
+      mov  eax,1
+      cpuid
+      mov  res,edx
+   }
+   return res;
+}
+#endif
+
+#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name
+
+#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)
+static int stbi__sse2_available(void)
+{
+   int info3 = stbi__cpuid3();
+   return ((info3 >> 26) & 1) != 0;
+}
+#endif
+
+#else // assume GCC-style if not VC++
+#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16)))
+
+#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2)
+static int stbi__sse2_available(void)
+{
+   // If we're even attempting to compile this on GCC/Clang, that means
+   // -msse2 is on, which means the compiler is allowed to use SSE2
+   // instructions at will, and so are we.
+   return 1;
+}
+#endif
+
+#endif
+#endif
+
+// ARM NEON
+#if defined(STBI_NO_SIMD) && defined(STBI_NEON)
+#undef STBI_NEON
+#endif
+
+#ifdef STBI_NEON
+#include <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;
+
+
+static void stbi__refill_buffer(stbi__context *s);
+
+// initialize a memory-decode context
+static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len)
+{
+   s->io.read = NULL;
+   s->read_from_callbacks = 0;
+   s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer;
+   s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len;
+}
+
+// initialize a callback-based context
+static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user)
+{
+   s->io = *c;
+   s->io_user_data = user;
+   s->buflen = sizeof(s->buffer_start);
+   s->read_from_callbacks = 1;
+   s->img_buffer_original = s->buffer_start;
+   stbi__refill_buffer(s);
+   s->img_buffer_original_end = s->img_buffer_end;
+}
+
+#ifndef STBI_NO_STDIO
+
+static int stbi__stdio_read(void *user, char *data, int size)
+{
+   return (int) fread(data,1,size,(FILE*) user);
+}
+
+static void stbi__stdio_skip(void *user, int n)
+{
+   fseek((FILE*) user, n, SEEK_CUR);
+}
+
+static int stbi__stdio_eof(void *user)
+{
+   return feof((FILE*) user);
+}
+
+static stbi_io_callbacks stbi__stdio_callbacks =
+{
+   stbi__stdio_read,
+   stbi__stdio_skip,
+   stbi__stdio_eof,
+};
+
+static void stbi__start_file(stbi__context *s, FILE *f)
+{
+   stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f);
+}
+
+//static void stop_file(stbi__context *s) { }
+
+#endif // !STBI_NO_STDIO
+
+static void stbi__rewind(stbi__context *s)
+{
+   // conceptually rewind SHOULD rewind to the beginning of the stream,
+   // but we just rewind to the beginning of the initial buffer, because
+   // we only use it after doing 'test', which only ever looks at at most 92 bytes
+   s->img_buffer = s->img_buffer_original;
+   s->img_buffer_end = s->img_buffer_original_end;
+}
+
+enum
+{
+   STBI_ORDER_RGB,
+   STBI_ORDER_BGR
+};
+
+typedef struct
+{
+   int bits_per_channel;
+   int num_channels;
+   int channel_order;
+} stbi__result_info;
+
+#ifndef STBI_NO_JPEG
+static int      stbi__jpeg_test(stbi__context *s);
+static void    *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PNG
+static int      stbi__png_test(stbi__context *s);
+static void    *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__png_info(stbi__context *s, int *x, int *y, int *comp);
+static int      stbi__png_is16(stbi__context *s);
+#endif
+
+#ifndef STBI_NO_BMP
+static int      stbi__bmp_test(stbi__context *s);
+static void    *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_TGA
+static int      stbi__tga_test(stbi__context *s);
+static void    *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__tga_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PSD
+static int      stbi__psd_test(stbi__context *s);
+static void    *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc);
+static int      stbi__psd_info(stbi__context *s, int *x, int *y, int *comp);
+static int      stbi__psd_is16(stbi__context *s);
+#endif
+
+#ifndef STBI_NO_HDR
+static int      stbi__hdr_test(stbi__context *s);
+static float   *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PIC
+static int      stbi__pic_test(stbi__context *s);
+static void    *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__pic_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_GIF
+static int      stbi__gif_test(stbi__context *s);
+static void    *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static void    *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp);
+static int      stbi__gif_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+#ifndef STBI_NO_PNM
+static int      stbi__pnm_test(stbi__context *s);
+static void    *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri);
+static int      stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp);
+#endif
+
+static
+#ifdef STBI_THREAD_LOCAL
+STBI_THREAD_LOCAL
+#endif
+const char *stbi__g_failure_reason;
+
+STBIDEF const char *stbi_failure_reason(void)
+{
+   return stbi__g_failure_reason;
+}
+
+#ifndef STBI_NO_FAILURE_STRINGS
+static int stbi__err(const char *str)
+{
+   stbi__g_failure_reason = str;
+   return 0;
+}
+#endif
+
+static void *stbi__malloc(size_t size)
+{
+    return STBI_MALLOC(size);
+}
+
+// stb_image uses ints pervasively, including for offset calculations.
+// therefore the largest decoded image size we can support with the
+// current code, even on 64-bit targets, is INT_MAX. this is not a
+// significant limitation for the intended use case.
+//
+// we do, however, need to make sure our size calculations don't
+// overflow. hence a few helper functions for size calculations that
+// multiply integers together, making sure that they're non-negative
+// and no overflow occurs.
+
+// return 1 if the sum is valid, 0 on overflow.
+// negative terms are considered invalid.
+static int stbi__addsizes_valid(int a, int b)
+{
+   if (b < 0) return 0;
+   // now 0 <= b <= INT_MAX, hence also
+   // 0 <= INT_MAX - b <= INTMAX.
+   // And "a + b <= INT_MAX" (which might overflow) is the
+   // same as a <= INT_MAX - b (no overflow)
+   return a <= INT_MAX - b;
+}
+
+// returns 1 if the product is valid, 0 on overflow.
+// negative factors are considered invalid.
+static int stbi__mul2sizes_valid(int a, int b)
+{
+   if (a < 0 || b < 0) return 0;
+   if (b == 0) return 1; // mul-by-0 is always safe
+   // portable way to check for no overflows in a*b
+   return a <= INT_MAX/b;
+}
+
+#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)
+// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow
+static int stbi__mad2sizes_valid(int a, int b, int add)
+{
+   return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add);
+}
+#endif
+
+// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow
+static int stbi__mad3sizes_valid(int a, int b, int c, int add)
+{
+   return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&
+      stbi__addsizes_valid(a*b*c, add);
+}
+
+// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow
+#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)
+static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add)
+{
+   return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) &&
+      stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add);
+}
+#endif
+
+#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR)
+// mallocs with size overflow checking
+static void *stbi__malloc_mad2(int a, int b, int add)
+{
+   if (!stbi__mad2sizes_valid(a, b, add)) return NULL;
+   return stbi__malloc(a*b + add);
+}
+#endif
+
+static void *stbi__malloc_mad3(int a, int b, int c, int add)
+{
+   if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL;
+   return stbi__malloc(a*b*c + add);
+}
+
+#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)
+static void *stbi__malloc_mad4(int a, int b, int c, int d, int add)
+{
+   if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL;
+   return stbi__malloc(a*b*c*d + add);
+}
+#endif
+
+// stbi__err - error
+// stbi__errpf - error returning pointer to float
+// stbi__errpuc - error returning pointer to unsigned char
+
+#ifdef STBI_NO_FAILURE_STRINGS
+   #define stbi__err(x,y)  0
+#elif defined(STBI_FAILURE_USERMSG)
+   #define stbi__err(x,y)  stbi__err(y)
+#else
+   #define stbi__err(x,y)  stbi__err(x)
+#endif
+
+#define stbi__errpf(x,y)   ((float *)(size_t) (stbi__err(x,y)?NULL:NULL))
+#define stbi__errpuc(x,y)  ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL))
+
+STBIDEF void stbi_image_free(void *retval_from_stbi_load)
+{
+   STBI_FREE(retval_from_stbi_load);
+}
+
+#ifndef STBI_NO_LINEAR
+static float   *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp);
+#endif
+
+#ifndef STBI_NO_HDR
+static stbi_uc *stbi__hdr_to_ldr(float   *data, int x, int y, int comp);
+#endif
+
+static int stbi__vertically_flip_on_load_global = 0;
+
+STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip)
+{
+   stbi__vertically_flip_on_load_global = flag_true_if_should_flip;
+}
+
+#ifndef STBI_THREAD_LOCAL
+#define stbi__vertically_flip_on_load  stbi__vertically_flip_on_load_global
+#else
+static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set;
+
+STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip)
+{
+   stbi__vertically_flip_on_load_local = flag_true_if_should_flip;
+   stbi__vertically_flip_on_load_set = 1;
+}
+
+#define stbi__vertically_flip_on_load  (stbi__vertically_flip_on_load_set       \
+                                         ? stbi__vertically_flip_on_load_local  \
+                                         : stbi__vertically_flip_on_load_global)
+#endif // STBI_THREAD_LOCAL
+
+static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)
+{
+   memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields
+   ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed
+   ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order
+   ri->num_channels = 0;
+
+   #ifndef STBI_NO_JPEG
+   if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri);
+   #endif
+   #ifndef STBI_NO_PNG
+   if (stbi__png_test(s))  return stbi__png_load(s,x,y,comp,req_comp, ri);
+   #endif
+   #ifndef STBI_NO_BMP
+   if (stbi__bmp_test(s))  return stbi__bmp_load(s,x,y,comp,req_comp, ri);
+   #endif
+   #ifndef STBI_NO_GIF
+   if (stbi__gif_test(s))  return stbi__gif_load(s,x,y,comp,req_comp, ri);
+   #endif
+   #ifndef STBI_NO_PSD
+   if (stbi__psd_test(s))  return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc);
+   #else
+   STBI_NOTUSED(bpc);
+   #endif
+   #ifndef STBI_NO_PIC
+   if (stbi__pic_test(s))  return stbi__pic_load(s,x,y,comp,req_comp, ri);
+   #endif
+   #ifndef STBI_NO_PNM
+   if (stbi__pnm_test(s))  return stbi__pnm_load(s,x,y,comp,req_comp, ri);
+   #endif
+
+   #ifndef STBI_NO_HDR
+   if (stbi__hdr_test(s)) {
+      float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri);
+      return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp);
+   }
+   #endif
+
+   #ifndef STBI_NO_TGA
+   // test tga last because it's a crappy test!
+   if (stbi__tga_test(s))
+      return stbi__tga_load(s,x,y,comp,req_comp, ri);
+   #endif
+
+   return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt");
+}
+
+static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels)
+{
+   int i;
+   int img_len = w * h * channels;
+   stbi_uc *reduced;
+
+   reduced = (stbi_uc *) stbi__malloc(img_len);
+   if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory");
+
+   for (i = 0; i < img_len; ++i)
+      reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling
+
+   STBI_FREE(orig);
+   return reduced;
+}
+
+static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels)
+{
+   int i;
+   int img_len = w * h * channels;
+   stbi__uint16 *enlarged;
+
+   enlarged = (stbi__uint16 *) stbi__malloc(img_len*2);
+   if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory");
+
+   for (i = 0; i < img_len; ++i)
+      enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff
+
+   STBI_FREE(orig);
+   return enlarged;
+}
+
+static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel)
+{
+   int row;
+   size_t bytes_per_row = (size_t)w * bytes_per_pixel;
+   stbi_uc temp[2048];
+   stbi_uc *bytes = (stbi_uc *)image;
+
+   for (row = 0; row < (h>>1); row++) {
+      stbi_uc *row0 = bytes + row*bytes_per_row;
+      stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row;
+      // swap row0 with row1
+      size_t bytes_left = bytes_per_row;
+      while (bytes_left) {
+         size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp);
+         memcpy(temp, row0, bytes_copy);
+         memcpy(row0, row1, bytes_copy);
+         memcpy(row1, temp, bytes_copy);
+         row0 += bytes_copy;
+         row1 += bytes_copy;
+         bytes_left -= bytes_copy;
+      }
+   }
+}
+
+#ifndef STBI_NO_GIF
+static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel)
+{
+   int slice;
+   int slice_size = w * h * bytes_per_pixel;
+
+   stbi_uc *bytes = (stbi_uc *)image;
+   for (slice = 0; slice < z; ++slice) {
+      stbi__vertical_flip(bytes, w, h, bytes_per_pixel);
+      bytes += slice_size;
+   }
+}
+#endif
+
+static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__result_info ri;
+   void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8);
+
+   if (result == NULL)
+      return NULL;
+
+   if (ri.bits_per_channel != 8) {
+      STBI_ASSERT(ri.bits_per_channel == 16);
+      result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp);
+      ri.bits_per_channel = 8;
+   }
+
+   // @TODO: move stbi__convert_format to here
+
+   if (stbi__vertically_flip_on_load) {
+      int channels = req_comp ? req_comp : *comp;
+      stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc));
+   }
+
+   return (unsigned char *) result;
+}
+
+static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__result_info ri;
+   void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16);
+
+   if (result == NULL)
+      return NULL;
+
+   if (ri.bits_per_channel != 16) {
+      STBI_ASSERT(ri.bits_per_channel == 8);
+      result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp);
+      ri.bits_per_channel = 16;
+   }
+
+   // @TODO: move stbi__convert_format16 to here
+   // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision
+
+   if (stbi__vertically_flip_on_load) {
+      int channels = req_comp ? req_comp : *comp;
+      stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16));
+   }
+
+   return (stbi__uint16 *) result;
+}
+
+#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR)
+static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp)
+{
+   if (stbi__vertically_flip_on_load && result != NULL) {
+      int channels = req_comp ? req_comp : *comp;
+      stbi__vertical_flip(result, *x, *y, channels * sizeof(float));
+   }
+}
+#endif
+
+#ifndef STBI_NO_STDIO
+
+#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)
+STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide);
+STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default);
+#endif
+
+#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)
+STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input)
+{
+	return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL);
+}
+#endif
+
+static FILE *stbi__fopen(char const *filename, char const *mode)
+{
+   FILE *f;
+#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)
+   wchar_t wMode[64];
+   wchar_t wFilename[1024];
+	if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)))
+      return 0;
+
+	if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)))
+      return 0;
+
+#if _MSC_VER >= 1400
+	if (0 != _wfopen_s(&f, wFilename, wMode))
+		f = 0;
+#else
+   f = _wfopen(wFilename, wMode);
+#endif
+
+#elif defined(_MSC_VER) && _MSC_VER >= 1400
+   if (0 != fopen_s(&f, filename, mode))
+      f=0;
+#else
+   f = fopen(filename, mode);
+#endif
+   return f;
+}
+
+
+STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp)
+{
+   FILE *f = stbi__fopen(filename, "rb");
+   unsigned char *result;
+   if (!f) return stbi__errpuc("can't fopen", "Unable to open file");
+   result = stbi_load_from_file(f,x,y,comp,req_comp);
+   fclose(f);
+   return result;
+}
+
+STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
+{
+   unsigned char *result;
+   stbi__context s;
+   stbi__start_file(&s,f);
+   result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
+   if (result) {
+      // need to 'unget' all the characters in the IO buffer
+      fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
+   }
+   return result;
+}
+
+STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__uint16 *result;
+   stbi__context s;
+   stbi__start_file(&s,f);
+   result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp);
+   if (result) {
+      // need to 'unget' all the characters in the IO buffer
+      fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR);
+   }
+   return result;
+}
+
+STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp)
+{
+   FILE *f = stbi__fopen(filename, "rb");
+   stbi__uint16 *result;
+   if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file");
+   result = stbi_load_from_file_16(f,x,y,comp,req_comp);
+   fclose(f);
+   return result;
+}
+
+
+#endif //!STBI_NO_STDIO
+
+STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels)
+{
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+   return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);
+}
+
+STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels)
+{
+   stbi__context s;
+   stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user);
+   return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels);
+}
+
+STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+   return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
+}
+
+STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__context s;
+   stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+   return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp);
+}
+
+#ifndef STBI_NO_GIF
+STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
+{
+   unsigned char *result;
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+
+   result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp);
+   if (stbi__vertically_flip_on_load) {
+      stbi__vertical_flip_slices( result, *x, *y, *z, *comp );
+   }
+
+   return result;
+}
+#endif
+
+#ifndef STBI_NO_LINEAR
+static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp)
+{
+   unsigned char *data;
+   #ifndef STBI_NO_HDR
+   if (stbi__hdr_test(s)) {
+      stbi__result_info ri;
+      float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri);
+      if (hdr_data)
+         stbi__float_postprocess(hdr_data,x,y,comp,req_comp);
+      return hdr_data;
+   }
+   #endif
+   data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp);
+   if (data)
+      return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp);
+   return stbi__errpf("unknown image type", "Image not of any known type, or corrupt");
+}
+
+STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+   return stbi__loadf_main(&s,x,y,comp,req_comp);
+}
+
+STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__context s;
+   stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+   return stbi__loadf_main(&s,x,y,comp,req_comp);
+}
+
+#ifndef STBI_NO_STDIO
+STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp)
+{
+   float *result;
+   FILE *f = stbi__fopen(filename, "rb");
+   if (!f) return stbi__errpf("can't fopen", "Unable to open file");
+   result = stbi_loadf_from_file(f,x,y,comp,req_comp);
+   fclose(f);
+   return result;
+}
+
+STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp)
+{
+   stbi__context s;
+   stbi__start_file(&s,f);
+   return stbi__loadf_main(&s,x,y,comp,req_comp);
+}
+#endif // !STBI_NO_STDIO
+
+#endif // !STBI_NO_LINEAR
+
+// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is
+// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always
+// reports false!
+
+STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len)
+{
+   #ifndef STBI_NO_HDR
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+   return stbi__hdr_test(&s);
+   #else
+   STBI_NOTUSED(buffer);
+   STBI_NOTUSED(len);
+   return 0;
+   #endif
+}
+
+#ifndef STBI_NO_STDIO
+STBIDEF int      stbi_is_hdr          (char const *filename)
+{
+   FILE *f = stbi__fopen(filename, "rb");
+   int result=0;
+   if (f) {
+      result = stbi_is_hdr_from_file(f);
+      fclose(f);
+   }
+   return result;
+}
+
+STBIDEF int stbi_is_hdr_from_file(FILE *f)
+{
+   #ifndef STBI_NO_HDR
+   long pos = ftell(f);
+   int res;
+   stbi__context s;
+   stbi__start_file(&s,f);
+   res = stbi__hdr_test(&s);
+   fseek(f, pos, SEEK_SET);
+   return res;
+   #else
+   STBI_NOTUSED(f);
+   return 0;
+   #endif
+}
+#endif // !STBI_NO_STDIO
+
+STBIDEF int      stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user)
+{
+   #ifndef STBI_NO_HDR
+   stbi__context s;
+   stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user);
+   return stbi__hdr_test(&s);
+   #else
+   STBI_NOTUSED(clbk);
+   STBI_NOTUSED(user);
+   return 0;
+   #endif
+}
+
+#ifndef STBI_NO_LINEAR
+static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f;
+
+STBIDEF void   stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; }
+STBIDEF void   stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; }
+#endif
+
+static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f;
+
+STBIDEF void   stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; }
+STBIDEF void   stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; }
+
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Common code used by all image loaders
+//
+
+enum
+{
+   STBI__SCAN_load=0,
+   STBI__SCAN_type,
+   STBI__SCAN_header
+};
+
+static void stbi__refill_buffer(stbi__context *s)
+{
+   int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen);
+   if (n == 0) {
+      // at end of file, treat same as if from memory, but need to handle case
+      // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file
+      s->read_from_callbacks = 0;
+      s->img_buffer = s->buffer_start;
+      s->img_buffer_end = s->buffer_start+1;
+      *s->img_buffer = 0;
+   } else {
+      s->img_buffer = s->buffer_start;
+      s->img_buffer_end = s->buffer_start + n;
+   }
+}
+
+stbi_inline static stbi_uc stbi__get8(stbi__context *s)
+{
+   if (s->img_buffer < s->img_buffer_end)
+      return *s->img_buffer++;
+   if (s->read_from_callbacks) {
+      stbi__refill_buffer(s);
+      return *s->img_buffer++;
+   }
+   return 0;
+}
+
+#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)
+// nothing
+#else
+stbi_inline static int stbi__at_eof(stbi__context *s)
+{
+   if (s->io.read) {
+      if (!(s->io.eof)(s->io_user_data)) return 0;
+      // if feof() is true, check if buffer = end
+      // special case: we've only got the special 0 character at the end
+      if (s->read_from_callbacks == 0) return 1;
+   }
+
+   return s->img_buffer >= s->img_buffer_end;
+}
+#endif
+
+#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC)
+// nothing
+#else
+static void stbi__skip(stbi__context *s, int n)
+{
+   if (n < 0) {
+      s->img_buffer = s->img_buffer_end;
+      return;
+   }
+   if (s->io.read) {
+      int blen = (int) (s->img_buffer_end - s->img_buffer);
+      if (blen < n) {
+         s->img_buffer = s->img_buffer_end;
+         (s->io.skip)(s->io_user_data, n - blen);
+         return;
+      }
+   }
+   s->img_buffer += n;
+}
+#endif
+
+#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM)
+// nothing
+#else
+static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n)
+{
+   if (s->io.read) {
+      int blen = (int) (s->img_buffer_end - s->img_buffer);
+      if (blen < n) {
+         int res, count;
+
+         memcpy(buffer, s->img_buffer, blen);
+
+         count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen);
+         res = (count == (n-blen));
+         s->img_buffer = s->img_buffer_end;
+         return res;
+      }
+   }
+
+   if (s->img_buffer+n <= s->img_buffer_end) {
+      memcpy(buffer, s->img_buffer, n);
+      s->img_buffer += n;
+      return 1;
+   } else
+      return 0;
+}
+#endif
+
+#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)
+// nothing
+#else
+static int stbi__get16be(stbi__context *s)
+{
+   int z = stbi__get8(s);
+   return (z << 8) + stbi__get8(s);
+}
+#endif
+
+#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC)
+// nothing
+#else
+static stbi__uint32 stbi__get32be(stbi__context *s)
+{
+   stbi__uint32 z = stbi__get16be(s);
+   return (z << 16) + stbi__get16be(s);
+}
+#endif
+
+#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF)
+// nothing
+#else
+static int stbi__get16le(stbi__context *s)
+{
+   int z = stbi__get8(s);
+   return z + (stbi__get8(s) << 8);
+}
+#endif
+
+#ifndef STBI_NO_BMP
+static stbi__uint32 stbi__get32le(stbi__context *s)
+{
+   stbi__uint32 z = stbi__get16le(s);
+   return z + (stbi__get16le(s) << 16);
+}
+#endif
+
+#define STBI__BYTECAST(x)  ((stbi_uc) ((x) & 255))  // truncate int to byte without warnings
+
+#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)
+// nothing
+#else
+//////////////////////////////////////////////////////////////////////////////
+//
+//  generic converter from built-in img_n to req_comp
+//    individual types do this automatically as much as possible (e.g. jpeg
+//    does all cases internally since it needs to colorspace convert anyway,
+//    and it never has alpha, so very few cases ). png can automatically
+//    interleave an alpha=255 channel, but falls back to this for other cases
+//
+//  assume data buffer is malloced, so malloc a new one and free that one
+//  only failure mode is malloc failing
+
+static stbi_uc stbi__compute_y(int r, int g, int b)
+{
+   return (stbi_uc) (((r*77) + (g*150) +  (29*b)) >> 8);
+}
+#endif
+
+#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM)
+// nothing
+#else
+static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y)
+{
+   int i,j;
+   unsigned char *good;
+
+   if (req_comp == img_n) return data;
+   STBI_ASSERT(req_comp >= 1 && req_comp <= 4);
+
+   good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0);
+   if (good == NULL) {
+      STBI_FREE(data);
+      return stbi__errpuc("outofmem", "Out of memory");
+   }
+
+   for (j=0; j < (int) y; ++j) {
+      unsigned char *src  = data + j * x * img_n   ;
+      unsigned char *dest = good + j * x * req_comp;
+
+      #define STBI__COMBO(a,b)  ((a)*8+(b))
+      #define STBI__CASE(a,b)   case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)
+      // convert source image with img_n components to one with req_comp components;
+      // avoid switch per pixel, so use switch per scanline and massive macros
+      switch (STBI__COMBO(img_n, req_comp)) {
+         STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255;                                     } break;
+         STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0];                                  } break;
+         STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255;                     } break;
+         STBI__CASE(2,1) { dest[0]=src[0];                                                  } break;
+         STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0];                                  } break;
+         STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1];                  } break;
+         STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255;        } break;
+         STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]);                   } break;
+         STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255;    } break;
+         STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]);                   } break;
+         STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break;
+         STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];                    } break;
+         default: STBI_ASSERT(0);
+      }
+      #undef STBI__CASE
+   }
+
+   STBI_FREE(data);
+   return good;
+}
+#endif
+
+#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)
+// nothing
+#else
+static stbi__uint16 stbi__compute_y_16(int r, int g, int b)
+{
+   return (stbi__uint16) (((r*77) + (g*150) +  (29*b)) >> 8);
+}
+#endif
+
+#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD)
+// nothing
+#else
+static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y)
+{
+   int i,j;
+   stbi__uint16 *good;
+
+   if (req_comp == img_n) return data;
+   STBI_ASSERT(req_comp >= 1 && req_comp <= 4);
+
+   good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2);
+   if (good == NULL) {
+      STBI_FREE(data);
+      return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory");
+   }
+
+   for (j=0; j < (int) y; ++j) {
+      stbi__uint16 *src  = data + j * x * img_n   ;
+      stbi__uint16 *dest = good + j * x * req_comp;
+
+      #define STBI__COMBO(a,b)  ((a)*8+(b))
+      #define STBI__CASE(a,b)   case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b)
+      // convert source image with img_n components to one with req_comp components;
+      // avoid switch per pixel, so use switch per scanline and massive macros
+      switch (STBI__COMBO(img_n, req_comp)) {
+         STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff;                                     } break;
+         STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0];                                     } break;
+         STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff;                     } break;
+         STBI__CASE(2,1) { dest[0]=src[0];                                                     } break;
+         STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0];                                     } break;
+         STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1];                     } break;
+         STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff;        } break;
+         STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]);                   } break;
+         STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break;
+         STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]);                   } break;
+         STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break;
+         STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];                       } break;
+         default: STBI_ASSERT(0);
+      }
+      #undef STBI__CASE
+   }
+
+   STBI_FREE(data);
+   return good;
+}
+#endif
+
+#ifndef STBI_NO_LINEAR
+static float   *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp)
+{
+   int i,k,n;
+   float *output;
+   if (!data) return NULL;
+   output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0);
+   if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); }
+   // compute number of non-alpha components
+   if (comp & 1) n = comp; else n = comp-1;
+   for (i=0; i < x*y; ++i) {
+      for (k=0; k < n; ++k) {
+         output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale);
+      }
+   }
+   if (n < comp) {
+      for (i=0; i < x*y; ++i) {
+         output[i*comp + n] = data[i*comp + n]/255.0f;
+      }
+   }
+   STBI_FREE(data);
+   return output;
+}
+#endif
+
+#ifndef STBI_NO_HDR
+#define stbi__float2int(x)   ((int) (x))
+static stbi_uc *stbi__hdr_to_ldr(float   *data, int x, int y, int comp)
+{
+   int i,k,n;
+   stbi_uc *output;
+   if (!data) return NULL;
+   output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0);
+   if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); }
+   // compute number of non-alpha components
+   if (comp & 1) n = comp; else n = comp-1;
+   for (i=0; i < x*y; ++i) {
+      for (k=0; k < n; ++k) {
+         float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f;
+         if (z < 0) z = 0;
+         if (z > 255) z = 255;
+         output[i*comp + k] = (stbi_uc) stbi__float2int(z);
+      }
+      if (k < comp) {
+         float z = data[i*comp+k] * 255 + 0.5f;
+         if (z < 0) z = 0;
+         if (z > 255) z = 255;
+         output[i*comp + k] = (stbi_uc) stbi__float2int(z);
+      }
+   }
+   STBI_FREE(data);
+   return output;
+}
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+//
+//  "baseline" JPEG/JFIF decoder
+//
+//    simple implementation
+//      - doesn't support delayed output of y-dimension
+//      - simple interface (only one output format: 8-bit interleaved RGB)
+//      - doesn't try to recover corrupt jpegs
+//      - doesn't allow partial loading, loading multiple at once
+//      - still fast on x86 (copying globals into locals doesn't help x86)
+//      - allocates lots of intermediate memory (full size of all components)
+//        - non-interleaved case requires this anyway
+//        - allows good upsampling (see next)
+//    high-quality
+//      - upsampled channels are bilinearly interpolated, even across blocks
+//      - quality integer IDCT derived from IJG's 'slow'
+//    performance
+//      - fast huffman; reasonable integer IDCT
+//      - some SIMD kernels for common paths on targets with SSE2/NEON
+//      - uses a lot of intermediate memory, could cache poorly
+
+#ifndef STBI_NO_JPEG
+
+// huffman decoding acceleration
+#define FAST_BITS   9  // larger handles more cases; smaller stomps less cache
+
+typedef struct
+{
+   stbi_uc  fast[1 << FAST_BITS];
+   // weirdly, repacking this into AoS is a 10% speed loss, instead of a win
+   stbi__uint16 code[256];
+   stbi_uc  values[256];
+   stbi_uc  size[257];
+   unsigned int maxcode[18];
+   int    delta[17];   // old 'firstsymbol' - old 'firstcode'
+} stbi__huffman;
+
+typedef struct
+{
+   stbi__context *s;
+   stbi__huffman huff_dc[4];
+   stbi__huffman huff_ac[4];
+   stbi__uint16 dequant[4][64];
+   stbi__int16 fast_ac[4][1 << FAST_BITS];
+
+// sizes for components, interleaved MCUs
+   int img_h_max, img_v_max;
+   int img_mcu_x, img_mcu_y;
+   int img_mcu_w, img_mcu_h;
+
+// definition of jpeg image component
+   struct
+   {
+      int id;
+      int h,v;
+      int tq;
+      int hd,ha;
+      int dc_pred;
+
+      int x,y,w2,h2;
+      stbi_uc *data;
+      void *raw_data, *raw_coeff;
+      stbi_uc *linebuf;
+      short   *coeff;   // progressive only
+      int      coeff_w, coeff_h; // number of 8x8 coefficient blocks
+   } img_comp[4];
+
+   stbi__uint32   code_buffer; // jpeg entropy-coded buffer
+   int            code_bits;   // number of valid bits
+   unsigned char  marker;      // marker seen while filling entropy buffer
+   int            nomore;      // flag if we saw a marker so must stop
+
+   int            progressive;
+   int            spec_start;
+   int            spec_end;
+   int            succ_high;
+   int            succ_low;
+   int            eob_run;
+   int            jfif;
+   int            app14_color_transform; // Adobe APP14 tag
+   int            rgb;
+
+   int scan_n, order[4];
+   int restart_interval, todo;
+
+// kernels
+   void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]);
+   void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step);
+   stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs);
+} stbi__jpeg;
+
+static int stbi__build_huffman(stbi__huffman *h, int *count)
+{
+   int i,j,k=0;
+   unsigned int code;
+   // build size list for each symbol (from JPEG spec)
+   for (i=0; i < 16; ++i)
+      for (j=0; j < count[i]; ++j)
+         h->size[k++] = (stbi_uc) (i+1);
+   h->size[k] = 0;
+
+   // compute actual symbols (from jpeg spec)
+   code = 0;
+   k = 0;
+   for(j=1; j <= 16; ++j) {
+      // compute delta to add to code to compute symbol id
+      h->delta[j] = k - code;
+      if (h->size[k] == j) {
+         while (h->size[k] == j)
+            h->code[k++] = (stbi__uint16) (code++);
+         if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG");
+      }
+      // compute largest code + 1 for this size, preshifted as needed later
+      h->maxcode[j] = code << (16-j);
+      code <<= 1;
+   }
+   h->maxcode[j] = 0xffffffff;
+
+   // build non-spec acceleration table; 255 is flag for not-accelerated
+   memset(h->fast, 255, 1 << FAST_BITS);
+   for (i=0; i < k; ++i) {
+      int s = h->size[i];
+      if (s <= FAST_BITS) {
+         int c = h->code[i] << (FAST_BITS-s);
+         int m = 1 << (FAST_BITS-s);
+         for (j=0; j < m; ++j) {
+            h->fast[c+j] = (stbi_uc) i;
+         }
+      }
+   }
+   return 1;
+}
+
+// build a table that decodes both magnitude and value of small ACs in
+// one go.
+static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h)
+{
+   int i;
+   for (i=0; i < (1 << FAST_BITS); ++i) {
+      stbi_uc fast = h->fast[i];
+      fast_ac[i] = 0;
+      if (fast < 255) {
+         int rs = h->values[fast];
+         int run = (rs >> 4) & 15;
+         int magbits = rs & 15;
+         int len = h->size[fast];
+
+         if (magbits && len + magbits <= FAST_BITS) {
+            // magnitude code followed by receive_extend code
+            int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits);
+            int m = 1 << (magbits - 1);
+            if (k < m) k += (~0U << magbits) + 1;
+            // if the result is small enough, we can fit it in fast_ac table
+            if (k >= -128 && k <= 127)
+               fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits));
+         }
+      }
+   }
+}
+
+static void stbi__grow_buffer_unsafe(stbi__jpeg *j)
+{
+   do {
+      unsigned int b = j->nomore ? 0 : stbi__get8(j->s);
+      if (b == 0xff) {
+         int c = stbi__get8(j->s);
+         while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes
+         if (c != 0) {
+            j->marker = (unsigned char) c;
+            j->nomore = 1;
+            return;
+         }
+      }
+      j->code_buffer |= b << (24 - j->code_bits);
+      j->code_bits += 8;
+   } while (j->code_bits <= 24);
+}
+
+// (1 << n) - 1
+static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535};
+
+// decode a jpeg huffman value from the bitstream
+stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h)
+{
+   unsigned int temp;
+   int c,k;
+
+   if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+
+   // look at the top FAST_BITS and determine what symbol ID it is,
+   // if the code is <= FAST_BITS
+   c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
+   k = h->fast[c];
+   if (k < 255) {
+      int s = h->size[k];
+      if (s > j->code_bits)
+         return -1;
+      j->code_buffer <<= s;
+      j->code_bits -= s;
+      return h->values[k];
+   }
+
+   // naive test is to shift the code_buffer down so k bits are
+   // valid, then test against maxcode. To speed this up, we've
+   // preshifted maxcode left so that it has (16-k) 0s at the
+   // end; in other words, regardless of the number of bits, it
+   // wants to be compared against something shifted to have 16;
+   // that way we don't need to shift inside the loop.
+   temp = j->code_buffer >> 16;
+   for (k=FAST_BITS+1 ; ; ++k)
+      if (temp < h->maxcode[k])
+         break;
+   if (k == 17) {
+      // error! code not found
+      j->code_bits -= 16;
+      return -1;
+   }
+
+   if (k > j->code_bits)
+      return -1;
+
+   // convert the huffman code to the symbol id
+   c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k];
+   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
+static const int 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 static 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 static 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 static 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?
+static const stbi_uc stbi__jpeg_dezigzag[64+15] =
+{
+    0,  1,  8, 16,  9,  2,  3, 10,
+   17, 24, 32, 25, 18, 11,  4,  5,
+   12, 19, 26, 33, 40, 48, 41, 34,
+   27, 20, 13,  6,  7, 14, 21, 28,
+   35, 42, 49, 56, 57, 50, 43, 36,
+   29, 22, 15, 23, 30, 37, 44, 51,
+   58, 59, 52, 45, 38, 31, 39, 46,
+   53, 60, 61, 54, 47, 55, 62, 63,
+   // let corrupt input sample past end
+   63, 63, 63, 63, 63, 63, 63, 63,
+   63, 63, 63, 63, 63, 63, 63
+};
+
+// decode one 64-entry block--
+static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant)
+{
+   int diff,dc,k;
+   int t;
+
+   if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+   t = stbi__jpeg_huff_decode(j, hdc);
+   if (t < 0) 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;
+}
+
+static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b)
+{
+   int diff,dc;
+   int t;
+   if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
+
+   if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+
+   if (j->succ_high == 0) {
+      // first scan for DC coefficient, must be first
+      memset(data,0,64*sizeof(data[0])); // 0 all the ac values now
+      t = stbi__jpeg_huff_decode(j, hdc);
+      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
+static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac)
+{
+   int k;
+   if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG");
+
+   if (j->succ_high == 0) {
+      int shift = j->succ_low;
+
+      if (j->eob_run) {
+         --j->eob_run;
+         return 1;
+      }
+
+      k = j->spec_start;
+      do {
+         unsigned int zig;
+         int c,r,s;
+         if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
+         c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1);
+         r = fac[c];
+         if (r) { // fast-AC path
+            k += (r >> 4) & 15; // run
+            s = r & 15; // combined length
+            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 static stbi_uc stbi__clamp(int x)
+{
+   // trick to use a single test to catch both cases
+   if ((unsigned int) x > 255) {
+      if (x < 0) return 0;
+      if (x > 255) return 255;
+   }
+   return (stbi_uc) x;
+}
+
+#define stbi__f2f(x)  ((int) (((x) * 4096 + 0.5)))
+#define stbi__fsh(x)  ((x) * 4096)
+
+// derived from jidctint -- DCT_ISLOW
+#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \
+   int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \
+   p2 = s2;                                    \
+   p3 = s6;                                    \
+   p1 = (p2+p3) * stbi__f2f(0.5411961f);       \
+   t2 = p1 + p3*stbi__f2f(-1.847759065f);      \
+   t3 = p1 + p2*stbi__f2f( 0.765366865f);      \
+   p2 = s0;                                    \
+   p3 = s4;                                    \
+   t0 = stbi__fsh(p2+p3);                      \
+   t1 = stbi__fsh(p2-p3);                      \
+   x0 = t0+t3;                                 \
+   x3 = t0-t3;                                 \
+   x1 = t1+t2;                                 \
+   x2 = t1-t2;                                 \
+   t0 = s7;                                    \
+   t1 = s5;                                    \
+   t2 = s3;                                    \
+   t3 = s1;                                    \
+   p3 = t0+t2;                                 \
+   p4 = t1+t3;                                 \
+   p1 = t0+t3;                                 \
+   p2 = t1+t2;                                 \
+   p5 = (p3+p4)*stbi__f2f( 1.175875602f);      \
+   t0 = t0*stbi__f2f( 0.298631336f);           \
+   t1 = t1*stbi__f2f( 2.053119869f);           \
+   t2 = t2*stbi__f2f( 3.072711026f);           \
+   t3 = t3*stbi__f2f( 1.501321110f);           \
+   p1 = p5 + p1*stbi__f2f(-0.899976223f);      \
+   p2 = p5 + p2*stbi__f2f(-2.562915447f);      \
+   p3 = p3*stbi__f2f(-1.961570560f);           \
+   p4 = p4*stbi__f2f(-0.390180644f);           \
+   t3 += p1+p4;                                \
+   t2 += p2+p3;                                \
+   t1 += p2+p4;                                \
+   t0 += p1+p3;
+
+static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64])
+{
+   int i,val[64],*v=val;
+   stbi_uc *o;
+   short *d = data;
+
+   // columns
+   for (i=0; i < 8; ++i,++d, ++v) {
+      // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing
+      if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0
+           && d[40]==0 && d[48]==0 && d[56]==0) {
+         //    no shortcut                 0     seconds
+         //    (1|2|3|4|5|6|7)==0          0     seconds
+         //    all separate               -0.047 seconds
+         //    1 && 2|3 && 4|5 && 6|7:    -0.047 seconds
+         int dcterm = d[0]*4;
+         v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm;
+      } else {
+         STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56])
+         // constants scaled things up by 1<<12; let's bring them back
+         // down, but keep 2 extra bits of precision
+         x0 += 512; x1 += 512; x2 += 512; x3 += 512;
+         v[ 0] = (x0+t3) >> 10;
+         v[56] = (x0-t3) >> 10;
+         v[ 8] = (x1+t2) >> 10;
+         v[48] = (x1-t2) >> 10;
+         v[16] = (x2+t1) >> 10;
+         v[40] = (x2-t1) >> 10;
+         v[24] = (x3+t0) >> 10;
+         v[32] = (x3-t0) >> 10;
+      }
+   }
+
+   for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) {
+      // no fast case since the first 1D IDCT spread components out
+      STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7])
+      // constants scaled things up by 1<<12, plus we had 1<<2 from first
+      // loop, plus horizontal and vertical each scale by sqrt(8) so together
+      // we've got an extra 1<<3, so 1<<17 total we need to remove.
+      // so we want to round that, which means adding 0.5 * 1<<17,
+      // aka 65536. Also, we'll end up with -128 to 127 that we want
+      // to encode as 0..255 by adding 128, so we'll add that before the shift
+      x0 += 65536 + (128<<17);
+      x1 += 65536 + (128<<17);
+      x2 += 65536 + (128<<17);
+      x3 += 65536 + (128<<17);
+      // tried computing the shifts into temps, or'ing the temps to see
+      // if any were out of range, but that was slower
+      o[0] = stbi__clamp((x0+t3) >> 17);
+      o[7] = stbi__clamp((x0-t3) >> 17);
+      o[1] = stbi__clamp((x1+t2) >> 17);
+      o[6] = stbi__clamp((x1-t2) >> 17);
+      o[2] = stbi__clamp((x2+t1) >> 17);
+      o[5] = stbi__clamp((x2-t1) >> 17);
+      o[3] = stbi__clamp((x3+t0) >> 17);
+      o[4] = stbi__clamp((x3-t0) >> 17);
+   }
+}
+
+#ifdef STBI_SSE2
+// sse2 integer IDCT. not the fastest possible implementation but it
+// produces bit-identical results to the generic C version so it's
+// fully "transparent".
+static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])
+{
+   // This is constructed to match our regular (generic) integer IDCT exactly.
+   __m128i row0, row1, row2, row3, row4, row5, row6, row7;
+   __m128i tmp;
+
+   // dot product constant: even elems=x, odd elems=y
+   #define dct_const(x,y)  _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y))
+
+   // out(0) = c0[even]*x + c0[odd]*y   (c0, x, y 16-bit, out 32-bit)
+   // out(1) = c1[even]*x + c1[odd]*y
+   #define dct_rot(out0,out1, x,y,c0,c1) \
+      __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \
+      __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \
+      __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \
+      __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \
+      __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \
+      __m128i out1##_h = _mm_madd_epi16(c0##hi, c1)
+
+   // out = in << 12  (in 16-bit, out 32-bit)
+   #define dct_widen(out, in) \
+      __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \
+      __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4)
+
+   // wide add
+   #define dct_wadd(out, a, b) \
+      __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \
+      __m128i out##_h = _mm_add_epi32(a##_h, b##_h)
+
+   // wide sub
+   #define dct_wsub(out, a, b) \
+      __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \
+      __m128i out##_h = _mm_sub_epi32(a##_h, b##_h)
+
+   // butterfly a/b, add bias, then shift by "s" and pack
+   #define dct_bfly32o(out0, out1, a,b,bias,s) \
+      { \
+         __m128i abiased_l = _mm_add_epi32(a##_l, bias); \
+         __m128i abiased_h = _mm_add_epi32(a##_h, bias); \
+         dct_wadd(sum, abiased, b); \
+         dct_wsub(dif, abiased, b); \
+         out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \
+         out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \
+      }
+
+   // 8-bit interleave step (for transposes)
+   #define dct_interleave8(a, b) \
+      tmp = a; \
+      a = _mm_unpacklo_epi8(a, b); \
+      b = _mm_unpackhi_epi8(tmp, b)
+
+   // 16-bit interleave step (for transposes)
+   #define dct_interleave16(a, b) \
+      tmp = a; \
+      a = _mm_unpacklo_epi16(a, b); \
+      b = _mm_unpackhi_epi16(tmp, b)
+
+   #define dct_pass(bias,shift) \
+      { \
+         /* even part */ \
+         dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \
+         __m128i sum04 = _mm_add_epi16(row0, row4); \
+         __m128i dif04 = _mm_sub_epi16(row0, row4); \
+         dct_widen(t0e, sum04); \
+         dct_widen(t1e, dif04); \
+         dct_wadd(x0, t0e, t3e); \
+         dct_wsub(x3, t0e, t3e); \
+         dct_wadd(x1, t1e, t2e); \
+         dct_wsub(x2, t1e, t2e); \
+         /* odd part */ \
+         dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \
+         dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \
+         __m128i sum17 = _mm_add_epi16(row1, row7); \
+         __m128i sum35 = _mm_add_epi16(row3, row5); \
+         dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \
+         dct_wadd(x4, y0o, y4o); \
+         dct_wadd(x5, y1o, y5o); \
+         dct_wadd(x6, y2o, y5o); \
+         dct_wadd(x7, y3o, y4o); \
+         dct_bfly32o(row0,row7, x0,x7,bias,shift); \
+         dct_bfly32o(row1,row6, x1,x6,bias,shift); \
+         dct_bfly32o(row2,row5, x2,x5,bias,shift); \
+         dct_bfly32o(row3,row4, x3,x4,bias,shift); \
+      }
+
+   __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f));
+   __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f));
+   __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f));
+   __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f));
+   __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f));
+   __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f));
+   __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f));
+   __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f));
+
+   // rounding biases in column/row passes, see stbi__idct_block for explanation.
+   __m128i bias_0 = _mm_set1_epi32(512);
+   __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17));
+
+   // load
+   row0 = _mm_load_si128((const __m128i *) (data + 0*8));
+   row1 = _mm_load_si128((const __m128i *) (data + 1*8));
+   row2 = _mm_load_si128((const __m128i *) (data + 2*8));
+   row3 = _mm_load_si128((const __m128i *) (data + 3*8));
+   row4 = _mm_load_si128((const __m128i *) (data + 4*8));
+   row5 = _mm_load_si128((const __m128i *) (data + 5*8));
+   row6 = _mm_load_si128((const __m128i *) (data + 6*8));
+   row7 = _mm_load_si128((const __m128i *) (data + 7*8));
+
+   // column pass
+   dct_pass(bias_0, 10);
+
+   {
+      // 16bit 8x8 transpose pass 1
+      dct_interleave16(row0, row4);
+      dct_interleave16(row1, row5);
+      dct_interleave16(row2, row6);
+      dct_interleave16(row3, row7);
+
+      // transpose pass 2
+      dct_interleave16(row0, row2);
+      dct_interleave16(row1, row3);
+      dct_interleave16(row4, row6);
+      dct_interleave16(row5, row7);
+
+      // transpose pass 3
+      dct_interleave16(row0, row1);
+      dct_interleave16(row2, row3);
+      dct_interleave16(row4, row5);
+      dct_interleave16(row6, row7);
+   }
+
+   // row pass
+   dct_pass(bias_1, 17);
+
+   {
+      // pack
+      __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7
+      __m128i p1 = _mm_packus_epi16(row2, row3);
+      __m128i p2 = _mm_packus_epi16(row4, row5);
+      __m128i p3 = _mm_packus_epi16(row6, row7);
+
+      // 8bit 8x8 transpose pass 1
+      dct_interleave8(p0, p2); // a0e0a1e1...
+      dct_interleave8(p1, p3); // c0g0c1g1...
+
+      // transpose pass 2
+      dct_interleave8(p0, p1); // a0c0e0g0...
+      dct_interleave8(p2, p3); // b0d0f0h0...
+
+      // transpose pass 3
+      dct_interleave8(p0, p2); // a0b0c0d0...
+      dct_interleave8(p1, p3); // a4b4c4d4...
+
+      // store
+      _mm_storel_epi64((__m128i *) out, p0); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, p2); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, p1); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, p3); out += out_stride;
+      _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e));
+   }
+
+#undef dct_const
+#undef dct_rot
+#undef dct_widen
+#undef dct_wadd
+#undef dct_wsub
+#undef dct_bfly32o
+#undef dct_interleave8
+#undef dct_interleave16
+#undef dct_pass
+}
+
+#endif // STBI_SSE2
+
+#ifdef STBI_NEON
+
+// NEON integer IDCT. should produce bit-identical
+// results to the generic C version.
+static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64])
+{
+   int16x8_t row0, row1, row2, row3, row4, row5, row6, row7;
+
+   int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f));
+   int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f));
+   int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f));
+   int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f));
+   int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f));
+   int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f));
+   int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f));
+   int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f));
+   int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f));
+   int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f));
+   int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f));
+   int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f));
+
+#define dct_long_mul(out, inq, coeff) \
+   int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \
+   int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff)
+
+#define dct_long_mac(out, acc, inq, coeff) \
+   int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \
+   int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff)
+
+#define dct_widen(out, inq) \
+   int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \
+   int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12)
+
+// wide add
+#define dct_wadd(out, a, b) \
+   int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \
+   int32x4_t out##_h = vaddq_s32(a##_h, b##_h)
+
+// wide sub
+#define dct_wsub(out, a, b) \
+   int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \
+   int32x4_t out##_h = vsubq_s32(a##_h, b##_h)
+
+// butterfly a/b, then shift using "shiftop" by "s" and pack
+#define dct_bfly32o(out0,out1, a,b,shiftop,s) \
+   { \
+      dct_wadd(sum, a, b); \
+      dct_wsub(dif, a, b); \
+      out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \
+      out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \
+   }
+
+#define dct_pass(shiftop, shift) \
+   { \
+      /* even part */ \
+      int16x8_t sum26 = vaddq_s16(row2, row6); \
+      dct_long_mul(p1e, sum26, rot0_0); \
+      dct_long_mac(t2e, p1e, row6, rot0_1); \
+      dct_long_mac(t3e, p1e, row2, rot0_2); \
+      int16x8_t sum04 = vaddq_s16(row0, row4); \
+      int16x8_t dif04 = vsubq_s16(row0, row4); \
+      dct_widen(t0e, sum04); \
+      dct_widen(t1e, dif04); \
+      dct_wadd(x0, t0e, t3e); \
+      dct_wsub(x3, t0e, t3e); \
+      dct_wadd(x1, t1e, t2e); \
+      dct_wsub(x2, t1e, t2e); \
+      /* odd part */ \
+      int16x8_t sum15 = vaddq_s16(row1, row5); \
+      int16x8_t sum17 = vaddq_s16(row1, row7); \
+      int16x8_t sum35 = vaddq_s16(row3, row5); \
+      int16x8_t sum37 = vaddq_s16(row3, row7); \
+      int16x8_t sumodd = vaddq_s16(sum17, sum35); \
+      dct_long_mul(p5o, sumodd, rot1_0); \
+      dct_long_mac(p1o, p5o, sum17, rot1_1); \
+      dct_long_mac(p2o, p5o, sum35, rot1_2); \
+      dct_long_mul(p3o, sum37, rot2_0); \
+      dct_long_mul(p4o, sum15, rot2_1); \
+      dct_wadd(sump13o, p1o, p3o); \
+      dct_wadd(sump24o, p2o, p4o); \
+      dct_wadd(sump23o, p2o, p3o); \
+      dct_wadd(sump14o, p1o, p4o); \
+      dct_long_mac(x4, sump13o, row7, rot3_0); \
+      dct_long_mac(x5, sump24o, row5, rot3_1); \
+      dct_long_mac(x6, sump23o, row3, rot3_2); \
+      dct_long_mac(x7, sump14o, row1, rot3_3); \
+      dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \
+      dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \
+      dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \
+      dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \
+   }
+
+   // load
+   row0 = vld1q_s16(data + 0*8);
+   row1 = vld1q_s16(data + 1*8);
+   row2 = vld1q_s16(data + 2*8);
+   row3 = vld1q_s16(data + 3*8);
+   row4 = vld1q_s16(data + 4*8);
+   row5 = vld1q_s16(data + 5*8);
+   row6 = vld1q_s16(data + 6*8);
+   row7 = vld1q_s16(data + 7*8);
+
+   // add DC bias
+   row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0));
+
+   // column pass
+   dct_pass(vrshrn_n_s32, 10);
+
+   // 16bit 8x8 transpose
+   {
+// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively.
+// whether compilers actually get this is another story, sadly.
+#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; }
+#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); }
+#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); }
+
+      // pass 1
+      dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6
+      dct_trn16(row2, row3);
+      dct_trn16(row4, row5);
+      dct_trn16(row6, row7);
+
+      // pass 2
+      dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4
+      dct_trn32(row1, row3);
+      dct_trn32(row4, row6);
+      dct_trn32(row5, row7);
+
+      // pass 3
+      dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0
+      dct_trn64(row1, row5);
+      dct_trn64(row2, row6);
+      dct_trn64(row3, row7);
+
+#undef dct_trn16
+#undef dct_trn32
+#undef dct_trn64
+   }
+
+   // row pass
+   // vrshrn_n_s32 only supports shifts up to 16, we need
+   // 17. so do a non-rounding shift of 16 first then follow
+   // up with a rounding shift by 1.
+   dct_pass(vshrn_n_s32, 16);
+
+   {
+      // pack and round
+      uint8x8_t p0 = vqrshrun_n_s16(row0, 1);
+      uint8x8_t p1 = vqrshrun_n_s16(row1, 1);
+      uint8x8_t p2 = vqrshrun_n_s16(row2, 1);
+      uint8x8_t p3 = vqrshrun_n_s16(row3, 1);
+      uint8x8_t p4 = vqrshrun_n_s16(row4, 1);
+      uint8x8_t p5 = vqrshrun_n_s16(row5, 1);
+      uint8x8_t p6 = vqrshrun_n_s16(row6, 1);
+      uint8x8_t p7 = vqrshrun_n_s16(row7, 1);
+
+      // again, these can translate into one instruction, but often don't.
+#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; }
+#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); }
+#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); }
+
+      // sadly can't use interleaved stores here since we only write
+      // 8 bytes to each scan line!
+
+      // 8x8 8-bit transpose pass 1
+      dct_trn8_8(p0, p1);
+      dct_trn8_8(p2, p3);
+      dct_trn8_8(p4, p5);
+      dct_trn8_8(p6, p7);
+
+      // pass 2
+      dct_trn8_16(p0, p2);
+      dct_trn8_16(p1, p3);
+      dct_trn8_16(p4, p6);
+      dct_trn8_16(p5, p7);
+
+      // pass 3
+      dct_trn8_32(p0, p4);
+      dct_trn8_32(p1, p5);
+      dct_trn8_32(p2, p6);
+      dct_trn8_32(p3, p7);
+
+      // store
+      vst1_u8(out, p0); out += out_stride;
+      vst1_u8(out, p1); out += out_stride;
+      vst1_u8(out, p2); out += out_stride;
+      vst1_u8(out, p3); out += out_stride;
+      vst1_u8(out, p4); out += out_stride;
+      vst1_u8(out, p5); out += out_stride;
+      vst1_u8(out, p6); out += out_stride;
+      vst1_u8(out, p7);
+
+#undef dct_trn8_8
+#undef dct_trn8_16
+#undef dct_trn8_32
+   }
+
+#undef dct_long_mul
+#undef dct_long_mac
+#undef dct_widen
+#undef dct_wadd
+#undef dct_wsub
+#undef dct_bfly32o
+#undef dct_pass
+}
+
+#endif // STBI_NEON
+
+#define STBI__MARKER_none  0xff
+// if there's a pending marker from the entropy stream, return that
+// otherwise, fetch from the stream and get a marker. if there's no
+// marker, return 0xff, which is never a valid marker value
+static stbi_uc stbi__get_marker(stbi__jpeg *j)
+{
+   stbi_uc x;
+   if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; }
+   x = stbi__get8(j->s);
+   if (x != 0xff) return STBI__MARKER_none;
+   while (x == 0xff)
+      x = stbi__get8(j->s); // consume repeated 0xff fill bytes
+   return x;
+}
+
+// in each scan, we'll have scan_n components, and the order
+// of the components is specified by order[]
+#define STBI__RESTART(x)     ((x) >= 0xd0 && (x) <= 0xd7)
+
+// after a restart interval, stbi__jpeg_reset the entropy decoder and
+// the dc prediction
+static void stbi__jpeg_reset(stbi__jpeg *j)
+{
+   j->code_bits = 0;
+   j->code_buffer = 0;
+   j->nomore = 0;
+   j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0;
+   j->marker = STBI__MARKER_none;
+   j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff;
+   j->eob_run = 0;
+   // no more than 1<<31 MCUs if no restart_interal? that's plenty safe,
+   // since we don't even allow 1<<30 pixels
+}
+
+static int stbi__parse_entropy_coded_data(stbi__jpeg *z)
+{
+   stbi__jpeg_reset(z);
+   if (!z->progressive) {
+      if (z->scan_n == 1) {
+         int i,j;
+         STBI_SIMD_ALIGN(short, data[64]);
+         int n = z->order[0];
+         // non-interleaved data, we just need to process one block at a time,
+         // in trivial scanline order
+         // number of blocks to do just depends on how many actual "pixels" this
+         // component has, independent of interleaved MCU blocking and such
+         int w = (z->img_comp[n].x+7) >> 3;
+         int h = (z->img_comp[n].y+7) >> 3;
+         for (j=0; j < h; ++j) {
+            for (i=0; i < w; ++i) {
+               int ha = z->img_comp[n].ha;
+               if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;
+               z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);
+               // every data block is an MCU, so countdown the restart interval
+               if (--z->todo <= 0) {
+                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+                  // if it's NOT a restart, then just bail, so we get corrupt data
+                  // rather than no data
+                  if (!STBI__RESTART(z->marker)) return 1;
+                  stbi__jpeg_reset(z);
+               }
+            }
+         }
+         return 1;
+      } else { // interleaved
+         int i,j,k,x,y;
+         STBI_SIMD_ALIGN(short, data[64]);
+         for (j=0; j < z->img_mcu_y; ++j) {
+            for (i=0; i < z->img_mcu_x; ++i) {
+               // scan an interleaved mcu... process scan_n components in order
+               for (k=0; k < z->scan_n; ++k) {
+                  int n = z->order[k];
+                  // scan out an mcu's worth of this component; that's just determined
+                  // by the basic H and V specified for the component
+                  for (y=0; y < z->img_comp[n].v; ++y) {
+                     for (x=0; x < z->img_comp[n].h; ++x) {
+                        int x2 = (i*z->img_comp[n].h + x)*8;
+                        int y2 = (j*z->img_comp[n].v + y)*8;
+                        int ha = z->img_comp[n].ha;
+                        if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0;
+                        z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data);
+                     }
+                  }
+               }
+               // after all interleaved components, that's an interleaved MCU,
+               // so now count down the restart interval
+               if (--z->todo <= 0) {
+                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+                  if (!STBI__RESTART(z->marker)) return 1;
+                  stbi__jpeg_reset(z);
+               }
+            }
+         }
+         return 1;
+      }
+   } else {
+      if (z->scan_n == 1) {
+         int i,j;
+         int n = z->order[0];
+         // non-interleaved data, we just need to process one block at a time,
+         // in trivial scanline order
+         // number of blocks to do just depends on how many actual "pixels" this
+         // component has, independent of interleaved MCU blocking and such
+         int w = (z->img_comp[n].x+7) >> 3;
+         int h = (z->img_comp[n].y+7) >> 3;
+         for (j=0; j < h; ++j) {
+            for (i=0; i < w; ++i) {
+               short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
+               if (z->spec_start == 0) {
+                  if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))
+                     return 0;
+               } else {
+                  int ha = z->img_comp[n].ha;
+                  if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha]))
+                     return 0;
+               }
+               // every data block is an MCU, so countdown the restart interval
+               if (--z->todo <= 0) {
+                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+                  if (!STBI__RESTART(z->marker)) return 1;
+                  stbi__jpeg_reset(z);
+               }
+            }
+         }
+         return 1;
+      } else { // interleaved
+         int i,j,k,x,y;
+         for (j=0; j < z->img_mcu_y; ++j) {
+            for (i=0; i < z->img_mcu_x; ++i) {
+               // scan an interleaved mcu... process scan_n components in order
+               for (k=0; k < z->scan_n; ++k) {
+                  int n = z->order[k];
+                  // scan out an mcu's worth of this component; that's just determined
+                  // by the basic H and V specified for the component
+                  for (y=0; y < z->img_comp[n].v; ++y) {
+                     for (x=0; x < z->img_comp[n].h; ++x) {
+                        int x2 = (i*z->img_comp[n].h + x);
+                        int y2 = (j*z->img_comp[n].v + y);
+                        short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w);
+                        if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n))
+                           return 0;
+                     }
+                  }
+               }
+               // after all interleaved components, that's an interleaved MCU,
+               // so now count down the restart interval
+               if (--z->todo <= 0) {
+                  if (z->code_bits < 24) stbi__grow_buffer_unsafe(z);
+                  if (!STBI__RESTART(z->marker)) return 1;
+                  stbi__jpeg_reset(z);
+               }
+            }
+         }
+         return 1;
+      }
+   }
+}
+
+static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant)
+{
+   int i;
+   for (i=0; i < 64; ++i)
+      data[i] *= dequant[i];
+}
+
+static void stbi__jpeg_finish(stbi__jpeg *z)
+{
+   if (z->progressive) {
+      // dequantize and idct the data
+      int i,j,n;
+      for (n=0; n < z->s->img_n; ++n) {
+         int w = (z->img_comp[n].x+7) >> 3;
+         int h = (z->img_comp[n].y+7) >> 3;
+         for (j=0; j < h; ++j) {
+            for (i=0; i < w; ++i) {
+               short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w);
+               stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]);
+               z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data);
+            }
+         }
+      }
+   }
+}
+
+static int stbi__process_marker(stbi__jpeg *z, int m)
+{
+   int L;
+   switch (m) {
+      case STBI__MARKER_none: // no marker found
+         return stbi__err("expected marker","Corrupt JPEG");
+
+      case 0xDD: // DRI - specify restart interval
+         if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG");
+         z->restart_interval = stbi__get16be(z->s);
+         return 1;
+
+      case 0xDB: // DQT - define quantization table
+         L = stbi__get16be(z->s)-2;
+         while (L > 0) {
+            int q = stbi__get8(z->s);
+            int p = q >> 4, sixteen = (p != 0);
+            int t = q & 15,i;
+            if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG");
+            if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG");
+
+            for (i=0; i < 64; ++i)
+               z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s));
+            L -= (sixteen ? 129 : 65);
+         }
+         return L==0;
+
+      case 0xC4: // DHT - define huffman table
+         L = stbi__get16be(z->s)-2;
+         while (L > 0) {
+            stbi_uc *v;
+            int sizes[16],i,n=0;
+            int q = stbi__get8(z->s);
+            int tc = q >> 4;
+            int th = q & 15;
+            if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG");
+            for (i=0; i < 16; ++i) {
+               sizes[i] = stbi__get8(z->s);
+               n += sizes[i];
+            }
+            L -= 17;
+            if (tc == 0) {
+               if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0;
+               v = z->huff_dc[th].values;
+            } else {
+               if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0;
+               v = z->huff_ac[th].values;
+            }
+            for (i=0; i < n; ++i)
+               v[i] = stbi__get8(z->s);
+            if (tc != 0)
+               stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th);
+            L -= n;
+         }
+         return L==0;
+   }
+
+   // check for comment block or APP blocks
+   if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) {
+      L = stbi__get16be(z->s);
+      if (L < 2) {
+         if (m == 0xFE)
+            return stbi__err("bad COM len","Corrupt JPEG");
+         else
+            return stbi__err("bad APP len","Corrupt JPEG");
+      }
+      L -= 2;
+
+      if (m == 0xE0 && L >= 5) { // JFIF APP0 segment
+         static const unsigned char tag[5] = {'J','F','I','F','\0'};
+         int ok = 1;
+         int i;
+         for (i=0; i < 5; ++i)
+            if (stbi__get8(z->s) != tag[i])
+               ok = 0;
+         L -= 5;
+         if (ok)
+            z->jfif = 1;
+      } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment
+         static const unsigned char tag[6] = {'A','d','o','b','e','\0'};
+         int ok = 1;
+         int i;
+         for (i=0; i < 6; ++i)
+            if (stbi__get8(z->s) != tag[i])
+               ok = 0;
+         L -= 6;
+         if (ok) {
+            stbi__get8(z->s); // version
+            stbi__get16be(z->s); // flags0
+            stbi__get16be(z->s); // flags1
+            z->app14_color_transform = stbi__get8(z->s); // color transform
+            L -= 6;
+         }
+      }
+
+      stbi__skip(z->s, L);
+      return 1;
+   }
+
+   return stbi__err("unknown marker","Corrupt JPEG");
+}
+
+// after we see SOS
+static int stbi__process_scan_header(stbi__jpeg *z)
+{
+   int i;
+   int Ls = stbi__get16be(z->s);
+   z->scan_n = stbi__get8(z->s);
+   if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG");
+   if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG");
+   for (i=0; i < z->scan_n; ++i) {
+      int id = stbi__get8(z->s), which;
+      int q = stbi__get8(z->s);
+      for (which = 0; which < z->s->img_n; ++which)
+         if (z->img_comp[which].id == id)
+            break;
+      if (which == z->s->img_n) return 0; // no match
+      z->img_comp[which].hd = q >> 4;   if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG");
+      z->img_comp[which].ha = q & 15;   if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG");
+      z->order[i] = which;
+   }
+
+   {
+      int aa;
+      z->spec_start = stbi__get8(z->s);
+      z->spec_end   = stbi__get8(z->s); // should be 63, but might be 0
+      aa = stbi__get8(z->s);
+      z->succ_high = (aa >> 4);
+      z->succ_low  = (aa & 15);
+      if (z->progressive) {
+         if (z->spec_start > 63 || z->spec_end > 63  || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13)
+            return stbi__err("bad SOS", "Corrupt JPEG");
+      } else {
+         if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG");
+         if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG");
+         z->spec_end = 63;
+      }
+   }
+
+   return 1;
+}
+
+static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why)
+{
+   int i;
+   for (i=0; i < ncomp; ++i) {
+      if (z->img_comp[i].raw_data) {
+         STBI_FREE(z->img_comp[i].raw_data);
+         z->img_comp[i].raw_data = NULL;
+         z->img_comp[i].data = NULL;
+      }
+      if (z->img_comp[i].raw_coeff) {
+         STBI_FREE(z->img_comp[i].raw_coeff);
+         z->img_comp[i].raw_coeff = 0;
+         z->img_comp[i].coeff = 0;
+      }
+      if (z->img_comp[i].linebuf) {
+         STBI_FREE(z->img_comp[i].linebuf);
+         z->img_comp[i].linebuf = NULL;
+      }
+   }
+   return why;
+}
+
+static int stbi__process_frame_header(stbi__jpeg *z, int scan)
+{
+   stbi__context *s = z->s;
+   int Lf,p,i,q, h_max=1,v_max=1,c;
+   Lf = stbi__get16be(s);         if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG
+   p  = stbi__get8(s);            if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline
+   s->img_y = stbi__get16be(s);   if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG
+   s->img_x = stbi__get16be(s);   if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires
+   c = stbi__get8(s);
+   if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG");
+   s->img_n = c;
+   for (i=0; i < c; ++i) {
+      z->img_comp[i].data = NULL;
+      z->img_comp[i].linebuf = NULL;
+   }
+
+   if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG");
+
+   z->rgb = 0;
+   for (i=0; i < s->img_n; ++i) {
+      static const unsigned char rgb[3] = { 'R', 'G', 'B' };
+      z->img_comp[i].id = stbi__get8(s);
+      if (s->img_n == 3 && z->img_comp[i].id == rgb[i])
+         ++z->rgb;
+      q = stbi__get8(s);
+      z->img_comp[i].h = (q >> 4);  if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG");
+      z->img_comp[i].v = q & 15;    if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG");
+      z->img_comp[i].tq = stbi__get8(s);  if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG");
+   }
+
+   if (scan != STBI__SCAN_load) return 1;
+
+   if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode");
+
+   for (i=0; i < s->img_n; ++i) {
+      if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h;
+      if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v;
+   }
+
+   // compute interleaved mcu info
+   z->img_h_max = h_max;
+   z->img_v_max = v_max;
+   z->img_mcu_w = h_max * 8;
+   z->img_mcu_h = v_max * 8;
+   // these sizes can't be more than 17 bits
+   z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w;
+   z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h;
+
+   for (i=0; i < s->img_n; ++i) {
+      // number of effective pixels (e.g. for non-interleaved MCU)
+      z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max;
+      z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max;
+      // to simplify generation, we'll allocate enough memory to decode
+      // the bogus oversized data from using interleaved MCUs and their
+      // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't
+      // discard the extra data until colorspace conversion
+      //
+      // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier)
+      // so these muls can't overflow with 32-bit ints (which we require)
+      z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8;
+      z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8;
+      z->img_comp[i].coeff = 0;
+      z->img_comp[i].raw_coeff = 0;
+      z->img_comp[i].linebuf = NULL;
+      z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15);
+      if (z->img_comp[i].raw_data == NULL)
+         return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory"));
+      // align blocks for idct using mmx/sse
+      z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15);
+      if (z->progressive) {
+         // w2, h2 are multiples of 8 (see above)
+         z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8;
+         z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8;
+         z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15);
+         if (z->img_comp[i].raw_coeff == NULL)
+            return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory"));
+         z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15);
+      }
+   }
+
+   return 1;
+}
+
+// use comparisons since in some cases we handle more than one case (e.g. SOF)
+#define stbi__DNL(x)         ((x) == 0xdc)
+#define stbi__SOI(x)         ((x) == 0xd8)
+#define stbi__EOI(x)         ((x) == 0xd9)
+#define stbi__SOF(x)         ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2)
+#define stbi__SOS(x)         ((x) == 0xda)
+
+#define stbi__SOF_progressive(x)   ((x) == 0xc2)
+
+static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan)
+{
+   int m;
+   z->jfif = 0;
+   z->app14_color_transform = -1; // valid values are 0,1,2
+   z->marker = STBI__MARKER_none; // initialize cached marker to empty
+   m = stbi__get_marker(z);
+   if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG");
+   if (scan == STBI__SCAN_type) return 1;
+   m = stbi__get_marker(z);
+   while (!stbi__SOF(m)) {
+      if (!stbi__process_marker(z,m)) return 0;
+      m = stbi__get_marker(z);
+      while (m == STBI__MARKER_none) {
+         // some files have extra padding after their blocks, so ok, we'll scan
+         if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG");
+         m = stbi__get_marker(z);
+      }
+   }
+   z->progressive = stbi__SOF_progressive(m);
+   if (!stbi__process_frame_header(z, scan)) return 0;
+   return 1;
+}
+
+// decode image to YCbCr format
+static int stbi__decode_jpeg_image(stbi__jpeg *j)
+{
+   int m;
+   for (m = 0; m < 4; m++) {
+      j->img_comp[m].raw_data = NULL;
+      j->img_comp[m].raw_coeff = NULL;
+   }
+   j->restart_interval = 0;
+   if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0;
+   m = stbi__get_marker(j);
+   while (!stbi__EOI(m)) {
+      if (stbi__SOS(m)) {
+         if (!stbi__process_scan_header(j)) return 0;
+         if (!stbi__parse_entropy_coded_data(j)) return 0;
+         if (j->marker == STBI__MARKER_none ) {
+            // 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;
+               }
+            }
+            // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0
+         }
+      } else if (stbi__DNL(m)) {
+         int Ld = stbi__get16be(j->s);
+         stbi__uint32 NL = stbi__get16be(j->s);
+         if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG");
+         if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG");
+      } else {
+         if (!stbi__process_marker(j, m)) return 0;
+      }
+      m = stbi__get_marker(j);
+   }
+   if (j->progressive)
+      stbi__jpeg_finish(j);
+   return 1;
+}
+
+// static jfif-centered resampling (across block boundaries)
+
+typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1,
+                                    int w, int hs);
+
+#define stbi__div4(x) ((stbi_uc) ((x) >> 2))
+
+static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+   STBI_NOTUSED(out);
+   STBI_NOTUSED(in_far);
+   STBI_NOTUSED(w);
+   STBI_NOTUSED(hs);
+   return in_near;
+}
+
+static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+   // need to generate two samples vertically for every one in input
+   int i;
+   STBI_NOTUSED(hs);
+   for (i=0; i < w; ++i)
+      out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2);
+   return out;
+}
+
+static stbi_uc*  stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+   // need to generate two samples horizontally for every one in input
+   int i;
+   stbi_uc *input = in_near;
+
+   if (w == 1) {
+      // if only one sample, can't do any interpolation
+      out[0] = out[1] = input[0];
+      return out;
+   }
+
+   out[0] = input[0];
+   out[1] = stbi__div4(input[0]*3 + input[1] + 2);
+   for (i=1; i < w-1; ++i) {
+      int n = 3*input[i]+2;
+      out[i*2+0] = stbi__div4(n+input[i-1]);
+      out[i*2+1] = stbi__div4(n+input[i+1]);
+   }
+   out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2);
+   out[i*2+1] = input[w-1];
+
+   STBI_NOTUSED(in_far);
+   STBI_NOTUSED(hs);
+
+   return out;
+}
+
+#define stbi__div16(x) ((stbi_uc) ((x) >> 4))
+
+static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+   // need to generate 2x2 samples for every one in input
+   int i,t0,t1;
+   if (w == 1) {
+      out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);
+      return out;
+   }
+
+   t1 = 3*in_near[0] + in_far[0];
+   out[0] = stbi__div4(t1+2);
+   for (i=1; i < w; ++i) {
+      t0 = t1;
+      t1 = 3*in_near[i]+in_far[i];
+      out[i*2-1] = stbi__div16(3*t0 + t1 + 8);
+      out[i*2  ] = stbi__div16(3*t1 + t0 + 8);
+   }
+   out[w*2-1] = stbi__div4(t1+2);
+
+   STBI_NOTUSED(hs);
+
+   return out;
+}
+
+#if defined(STBI_SSE2) || defined(STBI_NEON)
+static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+   // need to generate 2x2 samples for every one in input
+   int i=0,t0,t1;
+
+   if (w == 1) {
+      out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2);
+      return out;
+   }
+
+   t1 = 3*in_near[0] + in_far[0];
+   // process groups of 8 pixels for as long as we can.
+   // note we can't handle the last pixel in a row in this loop
+   // because we need to handle the filter boundary conditions.
+   for (; i < ((w-1) & ~7); i += 8) {
+#if defined(STBI_SSE2)
+      // load and perform the vertical filtering pass
+      // this uses 3*x + y = 4*x + (y - x)
+      __m128i zero  = _mm_setzero_si128();
+      __m128i farb  = _mm_loadl_epi64((__m128i *) (in_far + i));
+      __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i));
+      __m128i farw  = _mm_unpacklo_epi8(farb, zero);
+      __m128i nearw = _mm_unpacklo_epi8(nearb, zero);
+      __m128i diff  = _mm_sub_epi16(farw, nearw);
+      __m128i nears = _mm_slli_epi16(nearw, 2);
+      __m128i curr  = _mm_add_epi16(nears, diff); // current row
+
+      // horizontal filter works the same based on shifted vers of current
+      // row. "prev" is current row shifted right by 1 pixel; we need to
+      // insert the previous pixel value (from t1).
+      // "next" is current row shifted left by 1 pixel, with first pixel
+      // of next block of 8 pixels added in.
+      __m128i prv0 = _mm_slli_si128(curr, 2);
+      __m128i nxt0 = _mm_srli_si128(curr, 2);
+      __m128i prev = _mm_insert_epi16(prv0, t1, 0);
+      __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7);
+
+      // horizontal filter, polyphase implementation since it's convenient:
+      // even pixels = 3*cur + prev = cur*4 + (prev - cur)
+      // odd  pixels = 3*cur + next = cur*4 + (next - cur)
+      // note the shared term.
+      __m128i bias  = _mm_set1_epi16(8);
+      __m128i curs = _mm_slli_epi16(curr, 2);
+      __m128i prvd = _mm_sub_epi16(prev, curr);
+      __m128i nxtd = _mm_sub_epi16(next, curr);
+      __m128i curb = _mm_add_epi16(curs, bias);
+      __m128i even = _mm_add_epi16(prvd, curb);
+      __m128i odd  = _mm_add_epi16(nxtd, curb);
+
+      // interleave even and odd pixels, then undo scaling.
+      __m128i int0 = _mm_unpacklo_epi16(even, odd);
+      __m128i int1 = _mm_unpackhi_epi16(even, odd);
+      __m128i de0  = _mm_srli_epi16(int0, 4);
+      __m128i de1  = _mm_srli_epi16(int1, 4);
+
+      // pack and write output
+      __m128i outv = _mm_packus_epi16(de0, de1);
+      _mm_storeu_si128((__m128i *) (out + i*2), outv);
+#elif defined(STBI_NEON)
+      // load and perform the vertical filtering pass
+      // this uses 3*x + y = 4*x + (y - x)
+      uint8x8_t farb  = vld1_u8(in_far + i);
+      uint8x8_t nearb = vld1_u8(in_near + i);
+      int16x8_t diff  = vreinterpretq_s16_u16(vsubl_u8(farb, nearb));
+      int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2));
+      int16x8_t curr  = vaddq_s16(nears, diff); // current row
+
+      // horizontal filter works the same based on shifted vers of current
+      // row. "prev" is current row shifted right by 1 pixel; we need to
+      // insert the previous pixel value (from t1).
+      // "next" is current row shifted left by 1 pixel, with first pixel
+      // of next block of 8 pixels added in.
+      int16x8_t prv0 = vextq_s16(curr, curr, 7);
+      int16x8_t nxt0 = vextq_s16(curr, curr, 1);
+      int16x8_t prev = vsetq_lane_s16(t1, prv0, 0);
+      int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7);
+
+      // horizontal filter, polyphase implementation since it's convenient:
+      // even pixels = 3*cur + prev = cur*4 + (prev - cur)
+      // odd  pixels = 3*cur + next = cur*4 + (next - cur)
+      // note the shared term.
+      int16x8_t curs = vshlq_n_s16(curr, 2);
+      int16x8_t prvd = vsubq_s16(prev, curr);
+      int16x8_t nxtd = vsubq_s16(next, curr);
+      int16x8_t even = vaddq_s16(curs, prvd);
+      int16x8_t odd  = vaddq_s16(curs, nxtd);
+
+      // undo scaling and round, then store with even/odd phases interleaved
+      uint8x8x2_t o;
+      o.val[0] = vqrshrun_n_s16(even, 4);
+      o.val[1] = vqrshrun_n_s16(odd,  4);
+      vst2_u8(out + i*2, o);
+#endif
+
+      // "previous" value for next iter
+      t1 = 3*in_near[i+7] + in_far[i+7];
+   }
+
+   t0 = t1;
+   t1 = 3*in_near[i] + in_far[i];
+   out[i*2] = stbi__div16(3*t1 + t0 + 8);
+
+   for (++i; i < w; ++i) {
+      t0 = t1;
+      t1 = 3*in_near[i]+in_far[i];
+      out[i*2-1] = stbi__div16(3*t0 + t1 + 8);
+      out[i*2  ] = stbi__div16(3*t1 + t0 + 8);
+   }
+   out[w*2-1] = stbi__div4(t1+2);
+
+   STBI_NOTUSED(hs);
+
+   return out;
+}
+#endif
+
+static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs)
+{
+   // resample with nearest-neighbor
+   int i,j;
+   STBI_NOTUSED(in_far);
+   for (i=0; i < w; ++i)
+      for (j=0; j < hs; ++j)
+         out[i*hs+j] = in_near[i];
+   return out;
+}
+
+// this is a reduced-precision calculation of YCbCr-to-RGB introduced
+// to make sure the code produces the same results in both SIMD and scalar
+#define stbi__float2fixed(x)  (((int) ((x) * 4096.0f + 0.5f)) << 8)
+static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step)
+{
+   int i;
+   for (i=0; i < count; ++i) {
+      int y_fixed = (y[i] << 20) + (1<<19); // rounding
+      int r,g,b;
+      int cr = pcr[i] - 128;
+      int cb = pcb[i] - 128;
+      r = y_fixed +  cr* stbi__float2fixed(1.40200f);
+      g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);
+      b = y_fixed                                     +   cb* stbi__float2fixed(1.77200f);
+      r >>= 20;
+      g >>= 20;
+      b >>= 20;
+      if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
+      if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
+      if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
+      out[0] = (stbi_uc)r;
+      out[1] = (stbi_uc)g;
+      out[2] = (stbi_uc)b;
+      out[3] = 255;
+      out += step;
+   }
+}
+
+#if defined(STBI_SSE2) || defined(STBI_NEON)
+static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step)
+{
+   int i = 0;
+
+#ifdef STBI_SSE2
+   // step == 3 is pretty ugly on the final interleave, and i'm not convinced
+   // it's useful in practice (you wouldn't use it for textures, for example).
+   // so just accelerate step == 4 case.
+   if (step == 4) {
+      // this is a fairly straightforward implementation and not super-optimized.
+      __m128i signflip  = _mm_set1_epi8(-0x80);
+      __m128i cr_const0 = _mm_set1_epi16(   (short) ( 1.40200f*4096.0f+0.5f));
+      __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f));
+      __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f));
+      __m128i cb_const1 = _mm_set1_epi16(   (short) ( 1.77200f*4096.0f+0.5f));
+      __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128);
+      __m128i xw = _mm_set1_epi16(255); // alpha channel
+
+      for (; i+7 < count; i += 8) {
+         // load
+         __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i));
+         __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i));
+         __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i));
+         __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128
+         __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128
+
+         // unpack to short (and left-shift cr, cb by 8)
+         __m128i yw  = _mm_unpacklo_epi8(y_bias, y_bytes);
+         __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased);
+         __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased);
+
+         // color transform
+         __m128i yws = _mm_srli_epi16(yw, 4);
+         __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw);
+         __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw);
+         __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1);
+         __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1);
+         __m128i rws = _mm_add_epi16(cr0, yws);
+         __m128i gwt = _mm_add_epi16(cb0, yws);
+         __m128i bws = _mm_add_epi16(yws, cb1);
+         __m128i gws = _mm_add_epi16(gwt, cr1);
+
+         // descale
+         __m128i rw = _mm_srai_epi16(rws, 4);
+         __m128i bw = _mm_srai_epi16(bws, 4);
+         __m128i gw = _mm_srai_epi16(gws, 4);
+
+         // back to byte, set up for transpose
+         __m128i brb = _mm_packus_epi16(rw, bw);
+         __m128i gxb = _mm_packus_epi16(gw, xw);
+
+         // transpose to interleave channels
+         __m128i t0 = _mm_unpacklo_epi8(brb, gxb);
+         __m128i t1 = _mm_unpackhi_epi8(brb, gxb);
+         __m128i o0 = _mm_unpacklo_epi16(t0, t1);
+         __m128i o1 = _mm_unpackhi_epi16(t0, t1);
+
+         // store
+         _mm_storeu_si128((__m128i *) (out + 0), o0);
+         _mm_storeu_si128((__m128i *) (out + 16), o1);
+         out += 32;
+      }
+   }
+#endif
+
+#ifdef STBI_NEON
+   // in this version, step=3 support would be easy to add. but is there demand?
+   if (step == 4) {
+      // this is a fairly straightforward implementation and not super-optimized.
+      uint8x8_t signflip = vdup_n_u8(0x80);
+      int16x8_t cr_const0 = vdupq_n_s16(   (short) ( 1.40200f*4096.0f+0.5f));
+      int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f));
+      int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f));
+      int16x8_t cb_const1 = vdupq_n_s16(   (short) ( 1.77200f*4096.0f+0.5f));
+
+      for (; i+7 < count; i += 8) {
+         // load
+         uint8x8_t y_bytes  = vld1_u8(y + i);
+         uint8x8_t cr_bytes = vld1_u8(pcr + i);
+         uint8x8_t cb_bytes = vld1_u8(pcb + i);
+         int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip));
+         int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip));
+
+         // expand to s16
+         int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4));
+         int16x8_t crw = vshll_n_s8(cr_biased, 7);
+         int16x8_t cbw = vshll_n_s8(cb_biased, 7);
+
+         // color transform
+         int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0);
+         int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0);
+         int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1);
+         int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1);
+         int16x8_t rws = vaddq_s16(yws, cr0);
+         int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1);
+         int16x8_t bws = vaddq_s16(yws, cb1);
+
+         // undo scaling, round, convert to byte
+         uint8x8x4_t o;
+         o.val[0] = vqrshrun_n_s16(rws, 4);
+         o.val[1] = vqrshrun_n_s16(gws, 4);
+         o.val[2] = vqrshrun_n_s16(bws, 4);
+         o.val[3] = vdup_n_u8(255);
+
+         // store, interleaving r/g/b/a
+         vst4_u8(out, o);
+         out += 8*4;
+      }
+   }
+#endif
+
+   for (; i < count; ++i) {
+      int y_fixed = (y[i] << 20) + (1<<19); // rounding
+      int r,g,b;
+      int cr = pcr[i] - 128;
+      int cb = pcb[i] - 128;
+      r = y_fixed + cr* stbi__float2fixed(1.40200f);
+      g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000);
+      b = y_fixed                                   +   cb* stbi__float2fixed(1.77200f);
+      r >>= 20;
+      g >>= 20;
+      b >>= 20;
+      if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; }
+      if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; }
+      if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; }
+      out[0] = (stbi_uc)r;
+      out[1] = (stbi_uc)g;
+      out[2] = (stbi_uc)b;
+      out[3] = 255;
+      out += step;
+   }
+}
+#endif
+
+// set up the kernels
+static void stbi__setup_jpeg(stbi__jpeg *j)
+{
+   j->idct_block_kernel = stbi__idct_block;
+   j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row;
+   j->resample_row_hv_2_kernel = stbi__resample_row_hv_2;
+
+#ifdef STBI_SSE2
+   if (stbi__sse2_available()) {
+      j->idct_block_kernel = stbi__idct_simd;
+      j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
+      j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
+   }
+#endif
+
+#ifdef STBI_NEON
+   j->idct_block_kernel = stbi__idct_simd;
+   j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd;
+   j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd;
+#endif
+}
+
+// clean up the temporary component buffers
+static void stbi__cleanup_jpeg(stbi__jpeg *j)
+{
+   stbi__free_jpeg_components(j, j->s->img_n, 0);
+}
+
+typedef struct
+{
+   resample_row_func resample;
+   stbi_uc *line0,*line1;
+   int hs,vs;   // expansion factor in each axis
+   int w_lores; // horizontal pixels pre-expansion
+   int ystep;   // how far through vertical expansion we are
+   int ypos;    // which pre-expansion row we're on
+} stbi__resample;
+
+// fast 0..255 * 0..255 => 0..255 rounded multiplication
+static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y)
+{
+   unsigned int t = x*y + 128;
+   return (stbi_uc) ((t + (t >>8)) >> 8);
+}
+
+static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp)
+{
+   int n, decode_n, is_rgb;
+   z->s->img_n = 0; // make stbi__cleanup_jpeg safe
+
+   // validate req_comp
+   if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
+
+   // load a jpeg image from whichever source, but leave in YCbCr format
+   if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; }
+
+   // determine actual number of components to generate
+   n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1;
+
+   is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif));
+
+   if (z->s->img_n == 3 && n < 3 && !is_rgb)
+      decode_n = 1;
+   else
+      decode_n = z->s->img_n;
+
+   // resample and color-convert
+   {
+      int k;
+      unsigned int i,j;
+      stbi_uc *output;
+      stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL };
+
+      stbi__resample res_comp[4];
+
+      for (k=0; k < decode_n; ++k) {
+         stbi__resample *r = &res_comp[k];
+
+         // allocate line buffer big enough for upsampling off the edges
+         // with upsample factor of 4
+         z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3);
+         if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); }
+
+         r->hs      = z->img_h_max / z->img_comp[k].h;
+         r->vs      = z->img_v_max / z->img_comp[k].v;
+         r->ystep   = r->vs >> 1;
+         r->w_lores = (z->s->img_x + r->hs-1) / r->hs;
+         r->ypos    = 0;
+         r->line0   = r->line1 = z->img_comp[k].data;
+
+         if      (r->hs == 1 && r->vs == 1) r->resample = resample_row_1;
+         else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2;
+         else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2;
+         else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel;
+         else                               r->resample = stbi__resample_row_generic;
+      }
+
+      // can't error after this so, this is safe
+      output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1);
+      if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); }
+
+      // now go ahead and resample
+      for (j=0; j < z->s->img_y; ++j) {
+         stbi_uc *out = output + n * z->s->img_x * j;
+         for (k=0; k < decode_n; ++k) {
+            stbi__resample *r = &res_comp[k];
+            int y_bot = r->ystep >= (r->vs >> 1);
+            coutput[k] = r->resample(z->img_comp[k].linebuf,
+                                     y_bot ? r->line1 : r->line0,
+                                     y_bot ? r->line0 : r->line1,
+                                     r->w_lores, r->hs);
+            if (++r->ystep >= r->vs) {
+               r->ystep = 0;
+               r->line0 = r->line1;
+               if (++r->ypos < z->img_comp[k].y)
+                  r->line1 += z->img_comp[k].w2;
+            }
+         }
+         if (n >= 3) {
+            stbi_uc *y = coutput[0];
+            if (z->s->img_n == 3) {
+               if (is_rgb) {
+                  for (i=0; i < z->s->img_x; ++i) {
+                     out[0] = y[i];
+                     out[1] = coutput[1][i];
+                     out[2] = coutput[2][i];
+                     out[3] = 255;
+                     out += n;
+                  }
+               } else {
+                  z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
+               }
+            } else if (z->s->img_n == 4) {
+               if (z->app14_color_transform == 0) { // CMYK
+                  for (i=0; i < z->s->img_x; ++i) {
+                     stbi_uc m = coutput[3][i];
+                     out[0] = stbi__blinn_8x8(coutput[0][i], m);
+                     out[1] = stbi__blinn_8x8(coutput[1][i], m);
+                     out[2] = stbi__blinn_8x8(coutput[2][i], m);
+                     out[3] = 255;
+                     out += n;
+                  }
+               } else if (z->app14_color_transform == 2) { // YCCK
+                  z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
+                  for (i=0; i < z->s->img_x; ++i) {
+                     stbi_uc m = coutput[3][i];
+                     out[0] = stbi__blinn_8x8(255 - out[0], m);
+                     out[1] = stbi__blinn_8x8(255 - out[1], m);
+                     out[2] = stbi__blinn_8x8(255 - out[2], m);
+                     out += n;
+                  }
+               } else { // YCbCr + alpha?  Ignore the fourth channel for now
+                  z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n);
+               }
+            } else
+               for (i=0; i < z->s->img_x; ++i) {
+                  out[0] = out[1] = out[2] = y[i];
+                  out[3] = 255; // not used if n==3
+                  out += n;
+               }
+         } else {
+            if (is_rgb) {
+               if (n == 1)
+                  for (i=0; i < z->s->img_x; ++i)
+                     *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);
+               else {
+                  for (i=0; i < z->s->img_x; ++i, out += 2) {
+                     out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]);
+                     out[1] = 255;
+                  }
+               }
+            } else if (z->s->img_n == 4 && z->app14_color_transform == 0) {
+               for (i=0; i < z->s->img_x; ++i) {
+                  stbi_uc m = coutput[3][i];
+                  stbi_uc r = stbi__blinn_8x8(coutput[0][i], m);
+                  stbi_uc g = stbi__blinn_8x8(coutput[1][i], m);
+                  stbi_uc b = stbi__blinn_8x8(coutput[2][i], m);
+                  out[0] = stbi__compute_y(r, g, b);
+                  out[1] = 255;
+                  out += n;
+               }
+            } else if (z->s->img_n == 4 && z->app14_color_transform == 2) {
+               for (i=0; i < z->s->img_x; ++i) {
+                  out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]);
+                  out[1] = 255;
+                  out += n;
+               }
+            } else {
+               stbi_uc *y = coutput[0];
+               if (n == 1)
+                  for (i=0; i < z->s->img_x; ++i) out[i] = y[i];
+               else
+                  for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; }
+            }
+         }
+      }
+      stbi__cleanup_jpeg(z);
+      *out_x = z->s->img_x;
+      *out_y = z->s->img_y;
+      if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output
+      return output;
+   }
+}
+
+static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   unsigned char* result;
+   stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg));
+   STBI_NOTUSED(ri);
+   j->s = s;
+   stbi__setup_jpeg(j);
+   result = load_jpeg_image(j, x,y,comp,req_comp);
+   STBI_FREE(j);
+   return result;
+}
+
+static int stbi__jpeg_test(stbi__context *s)
+{
+   int r;
+   stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg));
+   j->s = s;
+   stbi__setup_jpeg(j);
+   r = stbi__decode_jpeg_header(j, STBI__SCAN_type);
+   stbi__rewind(s);
+   STBI_FREE(j);
+   return r;
+}
+
+static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp)
+{
+   if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) {
+      stbi__rewind( j->s );
+      return 0;
+   }
+   if (x) *x = j->s->img_x;
+   if (y) *y = j->s->img_y;
+   if (comp) *comp = j->s->img_n >= 3 ? 3 : 1;
+   return 1;
+}
+
+static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   int result;
+   stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg)));
+   j->s = s;
+   result = stbi__jpeg_info_raw(j, x, y, comp);
+   STBI_FREE(j);
+   return result;
+}
+#endif
+
+// public domain zlib decode    v0.2  Sean Barrett 2006-11-18
+//    simple implementation
+//      - all input must be provided in an upfront buffer
+//      - all output is written to a single output buffer (can malloc/realloc)
+//    performance
+//      - fast huffman
+
+#ifndef STBI_NO_ZLIB
+
+// fast-way is faster to check than jpeg huffman, but slow way is slower
+#define STBI__ZFAST_BITS  9 // accelerate all cases in default tables
+#define STBI__ZFAST_MASK  ((1 << STBI__ZFAST_BITS) - 1)
+
+// 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 static int stbi__bitreverse16(int n)
+{
+  n = ((n & 0xAAAA) >>  1) | ((n & 0x5555) << 1);
+  n = ((n & 0xCCCC) >>  2) | ((n & 0x3333) << 2);
+  n = ((n & 0xF0F0) >>  4) | ((n & 0x0F0F) << 4);
+  n = ((n & 0xFF00) >>  8) | ((n & 0x00FF) << 8);
+  return n;
+}
+
+stbi_inline static int stbi__bit_reverse(int v, int bits)
+{
+   STBI_ASSERT(bits <= 16);
+   // to bit reverse n bits, reverse 16 and shift
+   // e.g. 11 bits, bit reverse and shift away 5
+   return stbi__bitreverse16(v) >> (16-bits);
+}
+
+static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num)
+{
+   int i,k=0;
+   int code, next_code[16], sizes[17];
+
+   // DEFLATE spec for generating codes
+   memset(sizes, 0, sizeof(sizes));
+   memset(z->fast, 0, sizeof(z->fast));
+   for (i=0; i < num; ++i)
+      ++sizes[sizelist[i]];
+   sizes[0] = 0;
+   for (i=1; i < 16; ++i)
+      if (sizes[i] > (1 << i))
+         return stbi__err("bad sizes", "Corrupt PNG");
+   code = 0;
+   for (i=1; i < 16; ++i) {
+      next_code[i] = code;
+      z->firstcode[i] = (stbi__uint16) code;
+      z->firstsymbol[i] = (stbi__uint16) k;
+      code = (code + sizes[i]);
+      if (sizes[i])
+         if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG");
+      z->maxcode[i] = code << (16-i); // preshift for inner loop
+      code <<= 1;
+      k += sizes[i];
+   }
+   z->maxcode[16] = 0x10000; // sentinel
+   for (i=0; i < num; ++i) {
+      int s = sizelist[i];
+      if (s) {
+         int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s];
+         stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i);
+         z->size [c] = (stbi_uc     ) s;
+         z->value[c] = (stbi__uint16) i;
+         if (s <= STBI__ZFAST_BITS) {
+            int j = stbi__bit_reverse(next_code[s],s);
+            while (j < (1 << STBI__ZFAST_BITS)) {
+               z->fast[j] = fastv;
+               j += (1 << s);
+            }
+         }
+         ++next_code[s];
+      }
+   }
+   return 1;
+}
+
+// zlib-from-memory implementation for PNG reading
+//    because PNG allows splitting the zlib stream arbitrarily,
+//    and it's annoying structurally to have PNG call ZLIB call PNG,
+//    we require PNG read all the IDATs and combine them into a single
+//    memory buffer
+
+typedef struct
+{
+   stbi_uc *zbuffer, *zbuffer_end;
+   int num_bits;
+   stbi__uint32 code_buffer;
+
+   char *zout;
+   char *zout_start;
+   char *zout_end;
+   int   z_expandable;
+
+   stbi__zhuffman z_length, z_distance;
+} stbi__zbuf;
+
+stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z)
+{
+   if (z->zbuffer >= z->zbuffer_end) return 0;
+   return *z->zbuffer++;
+}
+
+static 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 static unsigned int stbi__zreceive(stbi__zbuf *z, int n)
+{
+   unsigned int k;
+   if (z->num_bits < n) stbi__fill_bits(z);
+   k = z->code_buffer & ((1 << n) - 1);
+   z->code_buffer >>= n;
+   z->num_bits -= n;
+   return k;
+}
+
+static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z)
+{
+   int b,s,k;
+   // not resolved by fast table, so compute it the slow way
+   // use jpeg approach, which requires MSbits at top
+   k = stbi__bit_reverse(a->code_buffer, 16);
+   for (s=STBI__ZFAST_BITS+1; ; ++s)
+      if (k < z->maxcode[s])
+         break;
+   if (s == 16) return -1; // invalid code!
+   // code size is s, so:
+   b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s];
+   STBI_ASSERT(z->size[b] == s);
+   a->code_buffer >>= s;
+   a->num_bits -= s;
+   return z->value[b];
+}
+
+stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
+{
+   int b,s;
+   if (a->num_bits < 16) stbi__fill_bits(a);
+   b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
+   if (b) {
+      s = b >> 9;
+      a->code_buffer >>= s;
+      a->num_bits -= s;
+      return b & 511;
+   }
+   return stbi__zhuffman_decode_slowpath(a, z);
+}
+
+static int stbi__zexpand(stbi__zbuf *z, char *zout, int n)  // need to make room for n bytes
+{
+   char *q;
+   int cur, limit, old_limit;
+   z->zout = zout;
+   if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG");
+   cur   = (int) (z->zout     - z->zout_start);
+   limit = old_limit = (int) (z->zout_end - z->zout_start);
+   while (cur + n > limit)
+      limit *= 2;
+   q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit);
+   STBI_NOTUSED(old_limit);
+   if (q == NULL) return stbi__err("outofmem", "Out of memory");
+   z->zout_start = q;
+   z->zout       = q + cur;
+   z->zout_end   = q + limit;
+   return 1;
+}
+
+static const int stbi__zlength_base[31] = {
+   3,4,5,6,7,8,9,10,11,13,
+   15,17,19,23,27,31,35,43,51,59,
+   67,83,99,115,131,163,195,227,258,0,0 };
+
+static const int stbi__zlength_extra[31]=
+{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 };
+
+static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,
+257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0};
+
+static const int stbi__zdist_extra[32] =
+{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13};
+
+static int stbi__parse_huffman_block(stbi__zbuf *a)
+{
+   char *zout = a->zout;
+   for(;;) {
+      int z = stbi__zhuffman_decode(a, &a->z_length);
+      if (z < 256) {
+         if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes
+         if (zout >= a->zout_end) {
+            if (!stbi__zexpand(a, zout, 1)) return 0;
+            zout = a->zout;
+         }
+         *zout++ = (char) z;
+      } else {
+         stbi_uc *p;
+         int len,dist;
+         if (z == 256) {
+            a->zout = zout;
+            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); }
+         }
+      }
+   }
+}
+
+static int stbi__compute_huffman_codes(stbi__zbuf *a)
+{
+   static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 };
+   stbi__zhuffman z_codelength;
+   stbi_uc lencodes[286+32+137];//padding for maximum single op
+   stbi_uc codelength_sizes[19];
+   int i,n;
+
+   int hlit  = stbi__zreceive(a,5) + 257;
+   int hdist = stbi__zreceive(a,5) + 1;
+   int hclen = stbi__zreceive(a,4) + 4;
+   int ntot  = hlit + hdist;
+
+   memset(codelength_sizes, 0, sizeof(codelength_sizes));
+   for (i=0; i < hclen; ++i) {
+      int s = stbi__zreceive(a,3);
+      codelength_sizes[length_dezigzag[i]] = (stbi_uc) s;
+   }
+   if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0;
+
+   n = 0;
+   while (n < ntot) {
+      int c = stbi__zhuffman_decode(a, &z_codelength);
+      if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG");
+      if (c < 16)
+         lencodes[n++] = (stbi_uc) c;
+      else {
+         stbi_uc fill = 0;
+         if (c == 16) {
+            c = stbi__zreceive(a,2)+3;
+            if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG");
+            fill = lencodes[n-1];
+         } else if (c == 17)
+            c = stbi__zreceive(a,3)+3;
+         else {
+            STBI_ASSERT(c == 18);
+            c = stbi__zreceive(a,7)+11;
+         }
+         if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG");
+         memset(lencodes+n, fill, c);
+         n += c;
+      }
+   }
+   if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG");
+   if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0;
+   if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0;
+   return 1;
+}
+
+static int stbi__parse_uncompressed_block(stbi__zbuf *a)
+{
+   stbi_uc header[4];
+   int len,nlen,k;
+   if (a->num_bits & 7)
+      stbi__zreceive(a, a->num_bits & 7); // discard
+   // drain the bit-packed data into header
+   k = 0;
+   while (a->num_bits > 0) {
+      header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check
+      a->code_buffer >>= 8;
+      a->num_bits -= 8;
+   }
+   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;
+}
+
+static int stbi__parse_zlib_header(stbi__zbuf *a)
+{
+   int cmf   = stbi__zget8(a);
+   int cm    = cmf & 15;
+   /* int cinfo = cmf >> 4; */
+   int flg   = stbi__zget8(a);
+   if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec
+   if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png
+   if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png
+   // window = 1 << (8 + cinfo)... but who cares, we fully buffer output
+   return 1;
+}
+
+static const stbi_uc stbi__zdefault_length[288] =
+{
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
+   8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+   9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+   9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+   9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+   7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8
+};
+static const stbi_uc stbi__zdefault_distance[32] =
+{
+   5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5
+};
+/*
+Init algorithm:
+{
+   int i;   // use <= to match clearly with spec
+   for (i=0; i <= 143; ++i)     stbi__zdefault_length[i]   = 8;
+   for (   ; i <= 255; ++i)     stbi__zdefault_length[i]   = 9;
+   for (   ; i <= 279; ++i)     stbi__zdefault_length[i]   = 7;
+   for (   ; i <= 287; ++i)     stbi__zdefault_length[i]   = 8;
+
+   for (i=0; i <=  31; ++i)     stbi__zdefault_distance[i] = 5;
+}
+*/
+
+static int stbi__parse_zlib(stbi__zbuf *a, int parse_header)
+{
+   int final, type;
+   if (parse_header)
+      if (!stbi__parse_zlib_header(a)) return 0;
+   a->num_bits = 0;
+   a->code_buffer = 0;
+   do {
+      final = stbi__zreceive(a,1);
+      type = stbi__zreceive(a,2);
+      if (type == 0) {
+         if (!stbi__parse_uncompressed_block(a)) return 0;
+      } else if (type == 3) {
+         return 0;
+      } else {
+         if (type == 1) {
+            // use fixed code lengths
+            if (!stbi__zbuild_huffman(&a->z_length  , stbi__zdefault_length  , 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;
+}
+
+static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header)
+{
+   a->zout_start = obuf;
+   a->zout       = obuf;
+   a->zout_end   = obuf + olen;
+   a->z_expandable = exp;
+
+   return stbi__parse_zlib(a, parse_header);
+}
+
+STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen)
+{
+   stbi__zbuf a;
+   char *p = (char *) stbi__malloc(initial_size);
+   if (p == NULL) return NULL;
+   a.zbuffer = (stbi_uc *) buffer;
+   a.zbuffer_end = (stbi_uc *) buffer + len;
+   if (stbi__do_zlib(&a, p, initial_size, 1, 1)) {
+      if (outlen) *outlen = (int) (a.zout - a.zout_start);
+      return a.zout_start;
+   } else {
+      STBI_FREE(a.zout_start);
+      return NULL;
+   }
+}
+
+STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen)
+{
+   return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen);
+}
+
+STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header)
+{
+   stbi__zbuf a;
+   char *p = (char *) stbi__malloc(initial_size);
+   if (p == NULL) return NULL;
+   a.zbuffer = (stbi_uc *) buffer;
+   a.zbuffer_end = (stbi_uc *) buffer + len;
+   if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) {
+      if (outlen) *outlen = (int) (a.zout - a.zout_start);
+      return a.zout_start;
+   } else {
+      STBI_FREE(a.zout_start);
+      return NULL;
+   }
+}
+
+STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen)
+{
+   stbi__zbuf a;
+   a.zbuffer = (stbi_uc *) ibuffer;
+   a.zbuffer_end = (stbi_uc *) ibuffer + ilen;
+   if (stbi__do_zlib(&a, obuffer, olen, 0, 1))
+      return (int) (a.zout - a.zout_start);
+   else
+      return -1;
+}
+
+STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen)
+{
+   stbi__zbuf a;
+   char *p = (char *) stbi__malloc(16384);
+   if (p == NULL) return NULL;
+   a.zbuffer = (stbi_uc *) buffer;
+   a.zbuffer_end = (stbi_uc *) buffer+len;
+   if (stbi__do_zlib(&a, p, 16384, 1, 0)) {
+      if (outlen) *outlen = (int) (a.zout - a.zout_start);
+      return a.zout_start;
+   } else {
+      STBI_FREE(a.zout_start);
+      return NULL;
+   }
+}
+
+STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen)
+{
+   stbi__zbuf a;
+   a.zbuffer = (stbi_uc *) ibuffer;
+   a.zbuffer_end = (stbi_uc *) ibuffer + ilen;
+   if (stbi__do_zlib(&a, obuffer, olen, 0, 0))
+      return (int) (a.zout - a.zout_start);
+   else
+      return -1;
+}
+#endif
+
+// public domain "baseline" PNG decoder   v0.10  Sean Barrett 2006-11-18
+//    simple implementation
+//      - only 8-bit samples
+//      - no CRC checking
+//      - allocates lots of intermediate memory
+//        - avoids problem of streaming data between subsystems
+//        - avoids explicit window management
+//    performance
+//      - uses stb_zlib, a PD zlib implementation with fast huffman decoding
+
+#ifndef STBI_NO_PNG
+typedef struct
+{
+   stbi__uint32 length;
+   stbi__uint32 type;
+} stbi__pngchunk;
+
+static stbi__pngchunk stbi__get_chunk_header(stbi__context *s)
+{
+   stbi__pngchunk c;
+   c.length = stbi__get32be(s);
+   c.type   = stbi__get32be(s);
+   return c;
+}
+
+static int stbi__check_png_header(stbi__context *s)
+{
+   static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 };
+   int i;
+   for (i=0; i < 8; ++i)
+      if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG");
+   return 1;
+}
+
+typedef struct
+{
+   stbi__context *s;
+   stbi_uc *idata, *expanded, *out;
+   int depth;
+} stbi__png;
+
+
+enum {
+   STBI__F_none=0,
+   STBI__F_sub=1,
+   STBI__F_up=2,
+   STBI__F_avg=3,
+   STBI__F_paeth=4,
+   // synthetic filters used for first scanline to avoid needing a dummy row of 0s
+   STBI__F_avg_first,
+   STBI__F_paeth_first
+};
+
+static stbi_uc first_row_filter[5] =
+{
+   STBI__F_none,
+   STBI__F_sub,
+   STBI__F_none,
+   STBI__F_avg_first,
+   STBI__F_paeth_first
+};
+
+static 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;
+}
+
+static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 };
+
+// create the png data from post-deflated data
+static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color)
+{
+   int bytes = (depth == 16? 2 : 1);
+   stbi__context *s = a->s;
+   stbi__uint32 i,j,stride = x*out_n*bytes;
+   stbi__uint32 img_len, img_width_bytes;
+   int k;
+   int img_n = s->img_n; // copy it into a local for later
+
+   int output_bytes = out_n*bytes;
+   int filter_bytes = img_n*bytes;
+   int width = x;
+
+   STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1);
+   a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into
+   if (!a->out) return stbi__err("outofmem", "Out of memory");
+
+   if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG");
+   img_width_bytes = (((img_n * x * depth) + 7) >> 3);
+   img_len = (img_width_bytes + 1) * y;
+
+   // we used to check for exact match between raw_len and img_len on non-interlaced PNGs,
+   // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros),
+   // so just check for raw_len < img_len always.
+   if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG");
+
+   for (j=0; j < y; ++j) {
+      stbi_uc *cur = a->out + stride*j;
+      stbi_uc *prior;
+      int filter = *raw++;
+
+      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;
+      }
+      prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above
+
+      // 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 if (depth == 16) {
+         if (img_n != out_n) {
+            cur[filter_bytes]   = 255; // first pixel top byte
+            cur[filter_bytes+1] = 255; // first pixel bottom byte
+         }
+         raw += filter_bytes;
+         cur += output_bytes;
+         prior += output_bytes;
+      } 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)*filter_bytes;
+         #define STBI__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;
+            STBI__CASE(STBI__F_sub)          { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break;
+            STBI__CASE(STBI__F_up)           { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
+            STBI__CASE(STBI__F_avg)          { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break;
+            STBI__CASE(STBI__F_paeth)        { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break;
+            STBI__CASE(STBI__F_avg_first)    { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break;
+            STBI__CASE(STBI__F_paeth_first)  { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break;
+         }
+         #undef STBI__CASE
+         raw += nk;
+      } else {
+         STBI_ASSERT(img_n+1 == out_n);
+         #define STBI__CASE(f) \
+             case f:     \
+                for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \
+                   for (k=0; k < filter_bytes; ++k)
+         switch (filter) {
+            STBI__CASE(STBI__F_none)         { cur[k] = raw[k]; } break;
+            STBI__CASE(STBI__F_sub)          { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break;
+            STBI__CASE(STBI__F_up)           { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break;
+            STBI__CASE(STBI__F_avg)          { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break;
+            STBI__CASE(STBI__F_paeth)        { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break;
+            STBI__CASE(STBI__F_avg_first)    { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break;
+            STBI__CASE(STBI__F_paeth_first)  { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break;
+         }
+         #undef STBI__CASE
+
+         // the loop above sets the high byte of the pixels' alpha, but for
+         // 16 bit png files we also need the low byte set. we'll do that here.
+         if (depth == 16) {
+            cur = a->out + stride*j; // start at the beginning of the row again
+            for (i=0; i < x; ++i,cur+=output_bytes) {
+               cur[filter_bytes+1] = 255;
+            }
+         }
+      }
+   }
+
+   // 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];
+               }
+            }
+         }
+      }
+   } else if (depth == 16) {
+      // force the image data from big-endian to platform-native.
+      // this is done in a separate pass due to the decoding relying
+      // on the data being untouched, but could probably be done
+      // per-line during decode if care is taken.
+      stbi_uc *cur = a->out;
+      stbi__uint16 *cur16 = (stbi__uint16*)cur;
+
+      for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) {
+         *cur16 = (cur[0] << 8) | cur[1];
+      }
+   }
+
+   return 1;
+}
+
+static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced)
+{
+   int bytes = (depth == 16 ? 2 : 1);
+   int out_bytes = out_n * bytes;
+   stbi_uc *final;
+   int p;
+   if (!interlaced)
+      return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color);
+
+   // de-interlacing
+   final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0);
+   for (p=0; p < 7; ++p) {
+      int xorig[] = { 0,4,0,2,0,1,0 };
+      int yorig[] = { 0,0,4,0,2,0,1 };
+      int xspc[]  = { 8,8,4,4,2,2,1 };
+      int yspc[]  = { 8,8,8,4,4,2,2 };
+      int i,j,x,y;
+      // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1
+      x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p];
+      y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p];
+      if (x && y) {
+         stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y;
+         if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) {
+            STBI_FREE(final);
+            return 0;
+         }
+         for (j=0; j < y; ++j) {
+            for (i=0; i < x; ++i) {
+               int out_y = j*yspc[p]+yorig[p];
+               int out_x = i*xspc[p]+xorig[p];
+               memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes,
+                      a->out + (j*x+i)*out_bytes, out_bytes);
+            }
+         }
+         STBI_FREE(a->out);
+         image_data += img_len;
+         image_data_len -= img_len;
+      }
+   }
+   a->out = final;
+
+   return 1;
+}
+
+static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n)
+{
+   stbi__context *s = z->s;
+   stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+   stbi_uc *p = z->out;
+
+   // compute color-based transparency, assuming we've
+   // already got 255 as the alpha value in the output
+   STBI_ASSERT(out_n == 2 || out_n == 4);
+
+   if (out_n == 2) {
+      for (i=0; i < pixel_count; ++i) {
+         p[1] = (p[0] == tc[0] ? 0 : 255);
+         p += 2;
+      }
+   } else {
+      for (i=0; i < pixel_count; ++i) {
+         if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
+            p[3] = 0;
+         p += 4;
+      }
+   }
+   return 1;
+}
+
+static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n)
+{
+   stbi__context *s = z->s;
+   stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+   stbi__uint16 *p = (stbi__uint16*) z->out;
+
+   // compute color-based transparency, assuming we've
+   // already got 65535 as the alpha value in the output
+   STBI_ASSERT(out_n == 2 || out_n == 4);
+
+   if (out_n == 2) {
+      for (i = 0; i < pixel_count; ++i) {
+         p[1] = (p[0] == tc[0] ? 0 : 65535);
+         p += 2;
+      }
+   } else {
+      for (i = 0; i < pixel_count; ++i) {
+         if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2])
+            p[3] = 0;
+         p += 4;
+      }
+   }
+   return 1;
+}
+
+static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n)
+{
+   stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y;
+   stbi_uc *p, *temp_out, *orig = a->out;
+
+   p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0);
+   if (p == NULL) return stbi__err("outofmem", "Out of memory");
+
+   // between here and free(out) below, exitting would leak
+   temp_out = p;
+
+   if (pal_img_n == 3) {
+      for (i=0; i < pixel_count; ++i) {
+         int n = orig[i]*4;
+         p[0] = palette[n  ];
+         p[1] = palette[n+1];
+         p[2] = palette[n+2];
+         p += 3;
+      }
+   } else {
+      for (i=0; i < pixel_count; ++i) {
+         int n = orig[i]*4;
+         p[0] = palette[n  ];
+         p[1] = palette[n+1];
+         p[2] = palette[n+2];
+         p[3] = palette[n+3];
+         p += 4;
+      }
+   }
+   STBI_FREE(a->out);
+   a->out = temp_out;
+
+   STBI_NOTUSED(len);
+
+   return 1;
+}
+
+static int stbi__unpremultiply_on_load = 0;
+static int stbi__de_iphone_flag = 0;
+
+STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply)
+{
+   stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply;
+}
+
+STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert)
+{
+   stbi__de_iphone_flag = flag_true_if_should_convert;
+}
+
+static void stbi__de_iphone(stbi__png *z)
+{
+   stbi__context *s = z->s;
+   stbi__uint32 i, pixel_count = s->img_x * s->img_y;
+   stbi_uc *p = z->out;
+
+   if (s->img_out_n == 3) {  // convert bgr to rgb
+      for (i=0; i < pixel_count; ++i) {
+         stbi_uc t = p[0];
+         p[0] = p[2];
+         p[2] = t;
+         p += 3;
+      }
+   } else {
+      STBI_ASSERT(s->img_out_n == 4);
+      if (stbi__unpremultiply_on_load) {
+         // convert bgr to rgb and unpremultiply
+         for (i=0; i < pixel_count; ++i) {
+            stbi_uc a = p[3];
+            stbi_uc t = p[0];
+            if (a) {
+               stbi_uc half = a / 2;
+               p[0] = (p[2] * 255 + half) / a;
+               p[1] = (p[1] * 255 + half) / a;
+               p[2] = ( t   * 255 + half) / a;
+            } else {
+               p[0] = p[2];
+               p[2] = t;
+            }
+            p += 4;
+         }
+      } else {
+         // convert bgr to rgb
+         for (i=0; i < pixel_count; ++i) {
+            stbi_uc t = p[0];
+            p[0] = p[2];
+            p[2] = t;
+            p += 4;
+         }
+      }
+   }
+}
+
+#define STBI__PNG_TYPE(a,b,c,d)  (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d))
+
+static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
+{
+   stbi_uc palette[1024], pal_img_n=0;
+   stbi_uc has_trans=0, tc[3]={0};
+   stbi__uint16 tc16[3];
+   stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0;
+   int first=1,k,interlace=0, color=0, is_iphone=0;
+   stbi__context *s = z->s;
+
+   z->expanded = NULL;
+   z->idata = NULL;
+   z->out = NULL;
+
+   if (!stbi__check_png_header(s)) return 0;
+
+   if (scan == STBI__SCAN_type) return 1;
+
+   for (;;) {
+      stbi__pngchunk c = stbi__get_chunk_header(s);
+      switch (c.type) {
+         case STBI__PNG_TYPE('C','g','B','I'):
+            is_iphone = 1;
+            stbi__skip(s, c.length);
+            break;
+         case STBI__PNG_TYPE('I','H','D','R'): {
+            int comp,filter;
+            if (!first) return stbi__err("multiple IHDR","Corrupt PNG");
+            first = 0;
+            if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG");
+            s->img_x = stbi__get32be(s); 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?)");
+            z->depth = stbi__get8(s);  if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16)  return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only");
+            color = stbi__get8(s);  if (color > 6)         return stbi__err("bad ctype","Corrupt PNG");
+            if (color == 3 && z->depth == 16)                  return stbi__err("bad ctype","Corrupt PNG");
+            if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG");
+            comp  = stbi__get8(s);  if (comp) return stbi__err("bad comp method","Corrupt PNG");
+            filter= stbi__get8(s);  if (filter) return stbi__err("bad filter method","Corrupt PNG");
+            interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG");
+            if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG");
+            if (!pal_img_n) {
+               s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0);
+               if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode");
+               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;
+               if (z->depth == 16) {
+                  for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is
+               } else {
+                  for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger
+               }
+            }
+            break;
+         }
+
+         case STBI__PNG_TYPE('I','D','A','T'): {
+            if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+            if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG");
+            if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; }
+            if ((int)(ioff + c.length) < (int)ioff) return 0;
+            if (ioff + c.length > idata_limit) {
+               stbi__uint32 idata_limit_old = idata_limit;
+               stbi_uc *p;
+               if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096;
+               while (ioff + c.length > idata_limit)
+                  idata_limit *= 2;
+               STBI_NOTUSED(idata_limit_old);
+               p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory");
+               z->idata = p;
+            }
+            if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG");
+            ioff += c.length;
+            break;
+         }
+
+         case STBI__PNG_TYPE('I','E','N','D'): {
+            stbi__uint32 raw_len, bpl;
+            if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+            if (scan != STBI__SCAN_load) return 1;
+            if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG");
+            // initial guess for decoded data size to avoid unnecessary reallocs
+            bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component
+            raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */;
+            z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone);
+            if (z->expanded == NULL) return 0; // zlib should set error
+            STBI_FREE(z->idata); z->idata = NULL;
+            if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans)
+               s->img_out_n = s->img_n+1;
+            else
+               s->img_out_n = s->img_n;
+            if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0;
+            if (has_trans) {
+               if (z->depth == 16) {
+                  if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0;
+               } else {
+                  if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0;
+               }
+            }
+            if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2)
+               stbi__de_iphone(z);
+            if (pal_img_n) {
+               // pal_img_n == 3 or 4
+               s->img_n = pal_img_n; // record the actual colors we had
+               s->img_out_n = pal_img_n;
+               if (req_comp >= 3) s->img_out_n = req_comp;
+               if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n))
+                  return 0;
+            } else if (has_trans) {
+               // non-paletted image with tRNS -> source image has (constant) alpha
+               ++s->img_n;
+            }
+            STBI_FREE(z->expanded); z->expanded = NULL;
+            // end of PNG chunk, read and skip CRC
+            stbi__get32be(s);
+            return 1;
+         }
+
+         default:
+            // if critical, fail
+            if (first) return stbi__err("first not IHDR", "Corrupt PNG");
+            if ((c.type & (1 << 29)) == 0) {
+               #ifndef STBI_NO_FAILURE_STRINGS
+               // not threadsafe
+               static char invalid_chunk[] = "XXXX PNG chunk not known";
+               invalid_chunk[0] = STBI__BYTECAST(c.type >> 24);
+               invalid_chunk[1] = STBI__BYTECAST(c.type >> 16);
+               invalid_chunk[2] = STBI__BYTECAST(c.type >>  8);
+               invalid_chunk[3] = STBI__BYTECAST(c.type >>  0);
+               #endif
+               return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type");
+            }
+            stbi__skip(s, c.length);
+            break;
+      }
+      // end of PNG chunk, read and skip CRC
+      stbi__get32be(s);
+   }
+}
+
+static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri)
+{
+   void *result=NULL;
+   if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error");
+   if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) {
+      if (p->depth < 8)
+         ri->bits_per_channel = 8;
+      else
+         ri->bits_per_channel = p->depth;
+      result = p->out;
+      p->out = NULL;
+      if (req_comp && req_comp != p->s->img_out_n) {
+         if (ri->bits_per_channel == 8)
+            result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
+         else
+            result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y);
+         p->s->img_out_n = req_comp;
+         if (result == NULL) return result;
+      }
+      *x = p->s->img_x;
+      *y = p->s->img_y;
+      if (n) *n = p->s->img_n;
+   }
+   STBI_FREE(p->out);      p->out      = NULL;
+   STBI_FREE(p->expanded); p->expanded = NULL;
+   STBI_FREE(p->idata);    p->idata    = NULL;
+
+   return result;
+}
+
+static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   stbi__png p;
+   p.s = s;
+   return stbi__do_png(&p, x,y,comp,req_comp, ri);
+}
+
+static int stbi__png_test(stbi__context *s)
+{
+   int r;
+   r = stbi__check_png_header(s);
+   stbi__rewind(s);
+   return r;
+}
+
+static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp)
+{
+   if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) {
+      stbi__rewind( p->s );
+      return 0;
+   }
+   if (x) *x = p->s->img_x;
+   if (y) *y = p->s->img_y;
+   if (comp) *comp = p->s->img_n;
+   return 1;
+}
+
+static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   stbi__png p;
+   p.s = s;
+   return stbi__png_info_raw(&p, x, y, comp);
+}
+
+static int stbi__png_is16(stbi__context *s)
+{
+   stbi__png p;
+   p.s = s;
+   if (!stbi__png_info_raw(&p, NULL, NULL, NULL))
+	   return 0;
+   if (p.depth != 16) {
+      stbi__rewind(p.s);
+      return 0;
+   }
+   return 1;
+}
+#endif
+
+// Microsoft/Windows BMP image
+
+#ifndef STBI_NO_BMP
+static int stbi__bmp_test_raw(stbi__context *s)
+{
+   int r;
+   int sz;
+   if (stbi__get8(s) != 'B') return 0;
+   if (stbi__get8(s) != 'M') return 0;
+   stbi__get32le(s); // discard filesize
+   stbi__get16le(s); // discard reserved
+   stbi__get16le(s); // discard reserved
+   stbi__get32le(s); // discard data offset
+   sz = stbi__get32le(s);
+   r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124);
+   return r;
+}
+
+static int stbi__bmp_test(stbi__context *s)
+{
+   int r = stbi__bmp_test_raw(s);
+   stbi__rewind(s);
+   return r;
+}
+
+
+// returns 0..31 for the highest set bit
+static int stbi__high_bit(unsigned int z)
+{
+   int n=0;
+   if (z == 0) return -1;
+   if (z >= 0x10000) { n += 16; z >>= 16; }
+   if (z >= 0x00100) { n +=  8; z >>=  8; }
+   if (z >= 0x00010) { n +=  4; z >>=  4; }
+   if (z >= 0x00004) { n +=  2; z >>=  2; }
+   if (z >= 0x00002) { n +=  1;/* >>=  1;*/ }
+   return n;
+}
+
+static int stbi__bitcount(unsigned int a)
+{
+   a = (a & 0x55555555) + ((a >>  1) & 0x55555555); // max 2
+   a = (a & 0x33333333) + ((a >>  2) & 0x33333333); // max 4
+   a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits
+   a = (a + (a >> 8)); // max 16 per 8 bits
+   a = (a + (a >> 16)); // max 32 per 8 bits
+   return a & 0xff;
+}
+
+// extract an arbitrarily-aligned N-bit value (N=bits)
+// from v, and then make it 8-bits long and fractionally
+// extend it to full full range.
+static int stbi__shiftsigned(unsigned int v, int shift, int bits)
+{
+   static unsigned int mul_table[9] = {
+      0,
+      0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/,
+      0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/,
+   };
+   static unsigned int shift_table[9] = {
+      0, 0,0,1,0,2,4,6,0,
+   };
+   if (shift < 0)
+      v <<= -shift;
+   else
+      v >>= shift;
+   STBI_ASSERT(v < 256);
+   v >>= (8-bits);
+   STBI_ASSERT(bits >= 0 && bits <= 8);
+   return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits];
+}
+
+typedef struct
+{
+   int bpp, offset, hsz;
+   unsigned int mr,mg,mb,ma, all_a;
+   int extra_read;
+} stbi__bmp_data;
+
+static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
+{
+   int hsz;
+   if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP");
+   stbi__get32le(s); // discard filesize
+   stbi__get16le(s); // discard reserved
+   stbi__get16le(s); // discard reserved
+   info->offset = stbi__get32le(s);
+   info->hsz = hsz = stbi__get32le(s);
+   info->mr = info->mg = info->mb = info->ma = 0;
+   info->extra_read = 14;
+
+   if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown");
+   if (hsz == 12) {
+      s->img_x = stbi__get16le(s);
+      s->img_y = stbi__get16le(s);
+   } else {
+      s->img_x = stbi__get32le(s);
+      s->img_y = stbi__get32le(s);
+   }
+   if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP");
+   info->bpp = stbi__get16le(s);
+   if (hsz != 12) {
+      int compress = stbi__get32le(s);
+      if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE");
+      stbi__get32le(s); // discard sizeof
+      stbi__get32le(s); // discard hres
+      stbi__get32le(s); // discard vres
+      stbi__get32le(s); // discard colorsused
+      stbi__get32le(s); // discard max important
+      if (hsz == 40 || hsz == 56) {
+         if (hsz == 56) {
+            stbi__get32le(s);
+            stbi__get32le(s);
+            stbi__get32le(s);
+            stbi__get32le(s);
+         }
+         if (info->bpp == 16 || info->bpp == 32) {
+            if (compress == 0) {
+               if (info->bpp == 32) {
+                  info->mr = 0xffu << 16;
+                  info->mg = 0xffu <<  8;
+                  info->mb = 0xffu <<  0;
+                  info->ma = 0xffu << 24;
+                  info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0
+               } else {
+                  info->mr = 31u << 10;
+                  info->mg = 31u <<  5;
+                  info->mb = 31u <<  0;
+               }
+            } else if (compress == 3) {
+               info->mr = stbi__get32le(s);
+               info->mg = stbi__get32le(s);
+               info->mb = stbi__get32le(s);
+               info->extra_read += 12;
+               // not documented, but generated by photoshop and handled by mspaint
+               if (info->mr == info->mg && info->mg == info->mb) {
+                  // ?!?!?
+                  return stbi__errpuc("bad BMP", "bad BMP");
+               }
+            } else
+               return stbi__errpuc("bad BMP", "bad BMP");
+         }
+      } else {
+         int i;
+         if (hsz != 108 && hsz != 124)
+            return stbi__errpuc("bad BMP", "bad BMP");
+         info->mr = stbi__get32le(s);
+         info->mg = stbi__get32le(s);
+         info->mb = stbi__get32le(s);
+         info->ma = stbi__get32le(s);
+         stbi__get32le(s); // discard color space
+         for (i=0; i < 12; ++i)
+            stbi__get32le(s); // discard color space parameters
+         if (hsz == 124) {
+            stbi__get32le(s); // discard rendering intent
+            stbi__get32le(s); // discard offset of profile data
+            stbi__get32le(s); // discard size of profile data
+            stbi__get32le(s); // discard reserved
+         }
+      }
+   }
+   return (void *) 1;
+}
+
+
+static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   stbi_uc *out;
+   unsigned int mr=0,mg=0,mb=0,ma=0, all_a;
+   stbi_uc pal[256][4];
+   int psize=0,i,j,width;
+   int flip_vertically, pad, target;
+   stbi__bmp_data info;
+   STBI_NOTUSED(ri);
+
+   info.all_a = 255;
+   if (stbi__bmp_parse_header(s, &info) == NULL)
+      return NULL; // error code already set
+
+   flip_vertically = ((int) s->img_y) > 0;
+   s->img_y = abs((int) s->img_y);
+
+   mr = info.mr;
+   mg = info.mg;
+   mb = info.mb;
+   ma = info.ma;
+   all_a = info.all_a;
+
+   if (info.hsz == 12) {
+      if (info.bpp < 24)
+         psize = (info.offset - info.extra_read - 24) / 3;
+   } else {
+      if (info.bpp < 16)
+         psize = (info.offset - info.extra_read - info.hsz) >> 2;
+   }
+   if (psize == 0) {
+      STBI_ASSERT(info.offset == (s->img_buffer - s->buffer_start));
+   }
+
+   if (info.bpp == 24 && ma == 0xff000000)
+      s->img_n = 3;
+   else
+      s->img_n = ma ? 4 : 3;
+   if (req_comp && req_comp >= 3) // we can directly decode 3 or 4
+      target = req_comp;
+   else
+      target = s->img_n; // if they want monochrome, we'll post-convert
+
+   // sanity-check size
+   if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0))
+      return stbi__errpuc("too large", "Corrupt BMP");
+
+   out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0);
+   if (!out) return stbi__errpuc("outofmem", "Out of memory");
+   if (info.bpp < 16) {
+      int z=0;
+      if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); }
+      for (i=0; i < psize; ++i) {
+         pal[i][2] = stbi__get8(s);
+         pal[i][1] = stbi__get8(s);
+         pal[i][0] = stbi__get8(s);
+         if (info.hsz != 12) stbi__get8(s);
+         pal[i][3] = 255;
+      }
+      stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4));
+      if (info.bpp == 1) width = (s->img_x + 7) >> 3;
+      else if (info.bpp == 4) width = (s->img_x + 1) >> 1;
+      else if (info.bpp == 8) width = s->img_x;
+      else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); }
+      pad = (-width)&3;
+      if (info.bpp == 1) {
+         for (j=0; j < (int) s->img_y; ++j) {
+            int bit_offset = 7, v = stbi__get8(s);
+            for (i=0; i < (int) s->img_x; ++i) {
+               int color = (v>>bit_offset)&0x1;
+               out[z++] = pal[color][0];
+               out[z++] = pal[color][1];
+               out[z++] = pal[color][2];
+               if (target == 4) out[z++] = 255;
+               if (i+1 == (int) s->img_x) break;
+               if((--bit_offset) < 0) {
+                  bit_offset = 7;
+                  v = stbi__get8(s);
+               }
+            }
+            stbi__skip(s, pad);
+         }
+      } else {
+         for (j=0; j < (int) s->img_y; ++j) {
+            for (i=0; i < (int) s->img_x; i += 2) {
+               int v=stbi__get8(s),v2=0;
+               if (info.bpp == 4) {
+                  v2 = v & 15;
+                  v >>= 4;
+               }
+               out[z++] = pal[v][0];
+               out[z++] = pal[v][1];
+               out[z++] = pal[v][2];
+               if (target == 4) out[z++] = 255;
+               if (i+1 == (int) s->img_x) break;
+               v = (info.bpp == 8) ? stbi__get8(s) : v2;
+               out[z++] = pal[v][0];
+               out[z++] = pal[v][1];
+               out[z++] = pal[v][2];
+               if (target == 4) out[z++] = 255;
+            }
+            stbi__skip(s, pad);
+         }
+      }
+   } else {
+      int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0;
+      int z = 0;
+      int easy=0;
+      stbi__skip(s, info.offset - info.extra_read - info.hsz);
+      if (info.bpp == 24) width = 3 * s->img_x;
+      else if (info.bpp == 16) width = 2*s->img_x;
+      else /* bpp = 32 and pad = 0 */ width=0;
+      pad = (-width) & 3;
+      if (info.bpp == 24) {
+         easy = 1;
+      } else if (info.bpp == 32) {
+         if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000)
+            easy = 2;
+      }
+      if (!easy) {
+         if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); }
+         // right shift amt to put high bit in position #7
+         rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr);
+         gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg);
+         bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb);
+         ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma);
+      }
+      for (j=0; j < (int) s->img_y; ++j) {
+         if (easy) {
+            for (i=0; i < (int) s->img_x; ++i) {
+               unsigned char a;
+               out[z+2] = stbi__get8(s);
+               out[z+1] = stbi__get8(s);
+               out[z+0] = stbi__get8(s);
+               z += 3;
+               a = (easy == 2 ? stbi__get8(s) : 255);
+               all_a |= a;
+               if (target == 4) out[z++] = a;
+            }
+         } else {
+            int bpp = info.bpp;
+            for (i=0; i < (int) s->img_x; ++i) {
+               stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s));
+               unsigned int a;
+               out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount));
+               out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount));
+               out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount));
+               a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255);
+               all_a |= a;
+               if (target == 4) out[z++] = STBI__BYTECAST(a);
+            }
+         }
+         stbi__skip(s, pad);
+      }
+   }
+
+   // if alpha channel is all 0s, replace with all 255s
+   if (target == 4 && all_a == 0)
+      for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4)
+         out[i] = 255;
+
+   if (flip_vertically) {
+      stbi_uc t;
+      for (j=0; j < (int) s->img_y>>1; ++j) {
+         stbi_uc *p1 = out +      j     *s->img_x*target;
+         stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target;
+         for (i=0; i < (int) s->img_x*target; ++i) {
+            t = p1[i]; p1[i] = p2[i]; p2[i] = t;
+         }
+      }
+   }
+
+   if (req_comp && req_comp != target) {
+      out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y);
+      if (out == NULL) return out; // stbi__convert_format frees input on failure
+   }
+
+   *x = s->img_x;
+   *y = s->img_y;
+   if (comp) *comp = s->img_n;
+   return out;
+}
+#endif
+
+// Targa Truevision - TGA
+// by Jonathan Dummer
+#ifndef STBI_NO_TGA
+// returns STBI_rgb or whatever, 0 on error
+static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16)
+{
+   // only RGB or RGBA (incl. 16bit) or grey allowed
+   if (is_rgb16) *is_rgb16 = 0;
+   switch(bits_per_pixel) {
+      case 8:  return STBI_grey;
+      case 16: if(is_grey) return STBI_grey_alpha;
+               // fallthrough
+      case 15: if(is_rgb16) *is_rgb16 = 1;
+               return STBI_rgb;
+      case 24: // fallthrough
+      case 32: return bits_per_pixel/8;
+      default: return 0;
+   }
+}
+
+static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp)
+{
+    int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp;
+    int sz, tga_colormap_type;
+    stbi__get8(s);                   // discard Offset
+    tga_colormap_type = stbi__get8(s); // colormap type
+    if( tga_colormap_type > 1 ) {
+        stbi__rewind(s);
+        return 0;      // only RGB or indexed allowed
+    }
+    tga_image_type = stbi__get8(s); // image type
+    if ( tga_colormap_type == 1 ) { // colormapped (paletted) image
+        if (tga_image_type != 1 && tga_image_type != 9) {
+            stbi__rewind(s);
+            return 0;
+        }
+        stbi__skip(s,4);       // skip index of first colormap entry and number of entries
+        sz = stbi__get8(s);    //   check bits per palette color entry
+        if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) {
+            stbi__rewind(s);
+            return 0;
+        }
+        stbi__skip(s,4);       // skip image x and y origin
+        tga_colormap_bpp = sz;
+    } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE
+        if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) {
+            stbi__rewind(s);
+            return 0; // only RGB or grey allowed, +/- RLE
+        }
+        stbi__skip(s,9); // skip colormap specification and image x/y origin
+        tga_colormap_bpp = 0;
+    }
+    tga_w = stbi__get16le(s);
+    if( tga_w < 1 ) {
+        stbi__rewind(s);
+        return 0;   // test width
+    }
+    tga_h = stbi__get16le(s);
+    if( tga_h < 1 ) {
+        stbi__rewind(s);
+        return 0;   // test height
+    }
+    tga_bits_per_pixel = stbi__get8(s); // bits per pixel
+    stbi__get8(s); // ignore alpha bits
+    if (tga_colormap_bpp != 0) {
+        if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) {
+            // when using a colormap, tga_bits_per_pixel is the size of the indexes
+            // I don't think anything but 8 or 16bit indexes makes sense
+            stbi__rewind(s);
+            return 0;
+        }
+        tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL);
+    } else {
+        tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL);
+    }
+    if(!tga_comp) {
+      stbi__rewind(s);
+      return 0;
+    }
+    if (x) *x = tga_w;
+    if (y) *y = tga_h;
+    if (comp) *comp = tga_comp;
+    return 1;                   // seems to have passed everything
+}
+
+static int stbi__tga_test(stbi__context *s)
+{
+   int res = 0;
+   int sz, tga_color_type;
+   stbi__get8(s);      //   discard Offset
+   tga_color_type = stbi__get8(s);   //   color type
+   if ( tga_color_type > 1 ) goto errorEnd;   //   only RGB or indexed allowed
+   sz = stbi__get8(s);   //   image type
+   if ( tga_color_type == 1 ) { // colormapped (paletted) image
+      if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9
+      stbi__skip(s,4);       // skip index of first colormap entry and number of entries
+      sz = stbi__get8(s);    //   check bits per palette color entry
+      if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;
+      stbi__skip(s,4);       // skip image x and y origin
+   } else { // "normal" image w/o colormap
+      if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE
+      stbi__skip(s,9); // skip colormap specification and image x/y origin
+   }
+   if ( stbi__get16le(s) < 1 ) goto errorEnd;      //   test width
+   if ( stbi__get16le(s) < 1 ) goto errorEnd;      //   test height
+   sz = stbi__get8(s);   //   bits per pixel
+   if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index
+   if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd;
+
+   res = 1; // if we got this far, everything's good and we can return 1 instead of 0
+
+errorEnd:
+   stbi__rewind(s);
+   return res;
+}
+
+// read 16bit value and convert to 24bit RGB
+static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out)
+{
+   stbi__uint16 px = (stbi__uint16)stbi__get16le(s);
+   stbi__uint16 fiveBitMask = 31;
+   // we have 3 channels with 5bits each
+   int r = (px >> 10) & fiveBitMask;
+   int g = (px >> 5) & fiveBitMask;
+   int b = px & fiveBitMask;
+   // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later
+   out[0] = (stbi_uc)((r * 255)/31);
+   out[1] = (stbi_uc)((g * 255)/31);
+   out[2] = (stbi_uc)((b * 255)/31);
+
+   // some people claim that the most significant bit might be used for alpha
+   // (possibly if an alpha-bit is set in the "image descriptor byte")
+   // but that only made 16bit test images completely translucent..
+   // so let's treat all 15 and 16bit TGAs as RGB with no alpha.
+}
+
+static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   //   read in the TGA header stuff
+   int tga_offset = stbi__get8(s);
+   int tga_indexed = stbi__get8(s);
+   int tga_image_type = stbi__get8(s);
+   int tga_is_RLE = 0;
+   int tga_palette_start = stbi__get16le(s);
+   int tga_palette_len = stbi__get16le(s);
+   int tga_palette_bits = stbi__get8(s);
+   int tga_x_origin = stbi__get16le(s);
+   int tga_y_origin = stbi__get16le(s);
+   int tga_width = stbi__get16le(s);
+   int tga_height = stbi__get16le(s);
+   int tga_bits_per_pixel = stbi__get8(s);
+   int tga_comp, tga_rgb16=0;
+   int tga_inverted = stbi__get8(s);
+   // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?)
+   //   image data
+   unsigned char *tga_data;
+   unsigned char *tga_palette = NULL;
+   int i, j;
+   unsigned char raw_data[4] = {0};
+   int RLE_count = 0;
+   int RLE_repeating = 0;
+   int read_next_pixel = 1;
+   STBI_NOTUSED(ri);
+   STBI_NOTUSED(tga_x_origin); // @TODO
+   STBI_NOTUSED(tga_y_origin); // @TODO
+
+   //   do a tiny bit of precessing
+   if ( tga_image_type >= 8 )
+   {
+      tga_image_type -= 8;
+      tga_is_RLE = 1;
+   }
+   tga_inverted = 1 - ((tga_inverted >> 5) & 1);
+
+   //   If I'm paletted, then I'll use the number of bits from the palette
+   if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16);
+   else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16);
+
+   if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency
+      return stbi__errpuc("bad format", "Can't find out TGA pixelformat");
+
+   //   tga info
+   *x = tga_width;
+   *y = tga_height;
+   if (comp) *comp = tga_comp;
+
+   if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0))
+      return stbi__errpuc("too large", "Corrupt TGA");
+
+   tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0);
+   if (!tga_data) return stbi__errpuc("outofmem", "Out of memory");
+
+   // skip to the data's starting position (offset usually = 0)
+   stbi__skip(s, tga_offset );
+
+   if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) {
+      for (i=0; i < tga_height; ++i) {
+         int row = tga_inverted ? tga_height -i - 1 : i;
+         stbi_uc *tga_row = tga_data + row*tga_width*tga_comp;
+         stbi__getn(s, tga_row, tga_width * tga_comp);
+      }
+   } else  {
+      //   do I need to load a palette?
+      if ( tga_indexed)
+      {
+         //   any data to skip? (offset usually = 0)
+         stbi__skip(s, tga_palette_start );
+         //   load the palette
+         tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0);
+         if (!tga_palette) {
+            STBI_FREE(tga_data);
+            return stbi__errpuc("outofmem", "Out of memory");
+         }
+         if (tga_rgb16) {
+            stbi_uc *pal_entry = tga_palette;
+            STBI_ASSERT(tga_comp == STBI_rgb);
+            for (i=0; i < tga_palette_len; ++i) {
+               stbi__tga_read_rgb16(s, pal_entry);
+               pal_entry += tga_comp;
+            }
+         } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) {
+               STBI_FREE(tga_data);
+               STBI_FREE(tga_palette);
+               return stbi__errpuc("bad palette", "Corrupt TGA");
+         }
+      }
+      //   load the data
+      for (i=0; i < tga_width * tga_height; ++i)
+      {
+         //   if I'm in RLE mode, do I need to get a RLE stbi__pngchunk?
+         if ( tga_is_RLE )
+         {
+            if ( RLE_count == 0 )
+            {
+               //   yep, get the next byte as a RLE command
+               int RLE_cmd = stbi__get8(s);
+               RLE_count = 1 + (RLE_cmd & 127);
+               RLE_repeating = RLE_cmd >> 7;
+               read_next_pixel = 1;
+            } else if ( !RLE_repeating )
+            {
+               read_next_pixel = 1;
+            }
+         } else
+         {
+            read_next_pixel = 1;
+         }
+         //   OK, if I need to read a pixel, do it now
+         if ( read_next_pixel )
+         {
+            //   load however much data we did have
+            if ( tga_indexed )
+            {
+               // read in index, then perform the lookup
+               int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s);
+               if ( pal_idx >= tga_palette_len ) {
+                  // invalid index
+                  pal_idx = 0;
+               }
+               pal_idx *= tga_comp;
+               for (j = 0; j < tga_comp; ++j) {
+                  raw_data[j] = tga_palette[pal_idx+j];
+               }
+            } else if(tga_rgb16) {
+               STBI_ASSERT(tga_comp == STBI_rgb);
+               stbi__tga_read_rgb16(s, raw_data);
+            } else {
+               //   read in the data raw
+               for (j = 0; j < tga_comp; ++j) {
+                  raw_data[j] = stbi__get8(s);
+               }
+            }
+            //   clear the reading flag for the next pixel
+            read_next_pixel = 0;
+         } // end of reading a pixel
+
+         // copy data
+         for (j = 0; j < tga_comp; ++j)
+           tga_data[i*tga_comp+j] = raw_data[j];
+
+         //   in case we're in RLE mode, keep counting down
+         --RLE_count;
+      }
+      //   do I need to invert the image?
+      if ( tga_inverted )
+      {
+         for (j = 0; j*2 < tga_height; ++j)
+         {
+            int index1 = j * tga_width * tga_comp;
+            int index2 = (tga_height - 1 - j) * tga_width * tga_comp;
+            for (i = tga_width * tga_comp; i > 0; --i)
+            {
+               unsigned char temp = tga_data[index1];
+               tga_data[index1] = tga_data[index2];
+               tga_data[index2] = temp;
+               ++index1;
+               ++index2;
+            }
+         }
+      }
+      //   clear my palette, if I had one
+      if ( tga_palette != NULL )
+      {
+         STBI_FREE( tga_palette );
+      }
+   }
+
+   // swap RGB - if the source data was RGB16, it already is in the right order
+   if (tga_comp >= 3 && !tga_rgb16)
+   {
+      unsigned char* tga_pixel = tga_data;
+      for (i=0; i < tga_width * tga_height; ++i)
+      {
+         unsigned char temp = tga_pixel[0];
+         tga_pixel[0] = tga_pixel[2];
+         tga_pixel[2] = temp;
+         tga_pixel += tga_comp;
+      }
+   }
+
+   // convert to target component count
+   if (req_comp && req_comp != tga_comp)
+      tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height);
+
+   //   the things I do to get rid of an error message, and yet keep
+   //   Microsoft's C compilers happy... [8^(
+   tga_palette_start = tga_palette_len = tga_palette_bits =
+         tga_x_origin = tga_y_origin = 0;
+   STBI_NOTUSED(tga_palette_start);
+   //   OK, done
+   return tga_data;
+}
+#endif
+
+// *************************************************************************************************
+// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB
+
+#ifndef STBI_NO_PSD
+static int stbi__psd_test(stbi__context *s)
+{
+   int r = (stbi__get32be(s) == 0x38425053);
+   stbi__rewind(s);
+   return r;
+}
+
+static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount)
+{
+   int count, nleft, len;
+
+   count = 0;
+   while ((nleft = pixelCount - count) > 0) {
+      len = stbi__get8(s);
+      if (len == 128) {
+         // No-op.
+      } else if (len < 128) {
+         // Copy next len+1 bytes literally.
+         len++;
+         if (len > nleft) return 0; // corrupt data
+         count += len;
+         while (len) {
+            *p = stbi__get8(s);
+            p += 4;
+            len--;
+         }
+      } else if (len > 128) {
+         stbi_uc   val;
+         // Next -len+1 bytes in the dest are replicated from next source byte.
+         // (Interpret len as a negative 8-bit int.)
+         len = 257 - len;
+         if (len > nleft) return 0; // corrupt data
+         val = stbi__get8(s);
+         count += len;
+         while (len) {
+            *p = val;
+            p += 4;
+            len--;
+         }
+      }
+   }
+
+   return 1;
+}
+
+static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc)
+{
+   int pixelCount;
+   int channelCount, compression;
+   int channel, i;
+   int bitdepth;
+   int w,h;
+   stbi_uc *out;
+   STBI_NOTUSED(ri);
+
+   // Check identifier
+   if (stbi__get32be(s) != 0x38425053)   // "8BPS"
+      return stbi__errpuc("not PSD", "Corrupt PSD image");
+
+   // Check file type version.
+   if (stbi__get16be(s) != 1)
+      return stbi__errpuc("wrong version", "Unsupported version of PSD image");
+
+   // Skip 6 reserved bytes.
+   stbi__skip(s, 6 );
+
+   // Read the number of channels (R, G, B, A, etc).
+   channelCount = stbi__get16be(s);
+   if (channelCount < 0 || channelCount > 16)
+      return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image");
+
+   // Read the rows and columns of the image.
+   h = stbi__get32be(s);
+   w = stbi__get32be(s);
+
+   // Make sure the depth is 8 bits.
+   bitdepth = stbi__get16be(s);
+   if (bitdepth != 8 && bitdepth != 16)
+      return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit");
+
+   // Make sure the color mode is RGB.
+   // Valid options are:
+   //   0: Bitmap
+   //   1: Grayscale
+   //   2: Indexed color
+   //   3: RGB color
+   //   4: CMYK color
+   //   7: Multichannel
+   //   8: Duotone
+   //   9: Lab color
+   if (stbi__get16be(s) != 3)
+      return stbi__errpuc("wrong color format", "PSD is not in RGB color format");
+
+   // Skip the Mode Data.  (It's the palette for indexed color; other info for other modes.)
+   stbi__skip(s,stbi__get32be(s) );
+
+   // Skip the image resources.  (resolution, pen tool paths, etc)
+   stbi__skip(s, stbi__get32be(s) );
+
+   // Skip the reserved data.
+   stbi__skip(s, stbi__get32be(s) );
+
+   // Find out if the data is compressed.
+   // Known values:
+   //   0: no compression
+   //   1: RLE compressed
+   compression = stbi__get16be(s);
+   if (compression > 1)
+      return stbi__errpuc("bad compression", "PSD has an unknown compression format");
+
+   // Check size
+   if (!stbi__mad3sizes_valid(4, w, h, 0))
+      return stbi__errpuc("too large", "Corrupt PSD");
+
+   // Create the destination image.
+
+   if (!compression && bitdepth == 16 && bpc == 16) {
+      out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0);
+      ri->bits_per_channel = 16;
+   } else
+      out = (stbi_uc *) stbi__malloc(4 * w*h);
+
+   if (!out) return stbi__errpuc("outofmem", "Out of memory");
+   pixelCount = w*h;
+
+   // Initialize the data to zero.
+   //memset( out, 0, pixelCount * 4 );
+
+   // Finally, the image data.
+   if (compression) {
+      // RLE as used by .PSD and .TIFF
+      // Loop until you get the number of unpacked bytes you are expecting:
+      //     Read the next source byte into n.
+      //     If n is between 0 and 127 inclusive, copy the next n+1 bytes literally.
+      //     Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times.
+      //     Else if n is 128, noop.
+      // Endloop
+
+      // The RLE-compressed data is preceded by a 2-byte data count for each row in the data,
+      // which we're going to just skip.
+      stbi__skip(s, h * channelCount * 2 );
+
+      // Read the RLE data by channel.
+      for (channel = 0; channel < 4; channel++) {
+         stbi_uc *p;
+
+         p = out+channel;
+         if (channel >= channelCount) {
+            // Fill this channel with default data.
+            for (i = 0; i < pixelCount; i++, p += 4)
+               *p = (channel == 3 ? 255 : 0);
+         } else {
+            // Read the RLE data.
+            if (!stbi__psd_decode_rle(s, p, pixelCount)) {
+               STBI_FREE(out);
+               return stbi__errpuc("corrupt", "bad RLE data");
+            }
+         }
+      }
+
+   } else {
+      // We're at the raw image data.  It's each channel in order (Red, Green, Blue, Alpha, ...)
+      // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image.
+
+      // Read the data by channel.
+      for (channel = 0; channel < 4; channel++) {
+         if (channel >= channelCount) {
+            // Fill this channel with default data.
+            if (bitdepth == 16 && bpc == 16) {
+               stbi__uint16 *q = ((stbi__uint16 *) out) + channel;
+               stbi__uint16 val = channel == 3 ? 65535 : 0;
+               for (i = 0; i < pixelCount; i++, q += 4)
+                  *q = val;
+            } else {
+               stbi_uc *p = out+channel;
+               stbi_uc val = channel == 3 ? 255 : 0;
+               for (i = 0; i < pixelCount; i++, p += 4)
+                  *p = val;
+            }
+         } else {
+            if (ri->bits_per_channel == 16) {    // output bpc
+               stbi__uint16 *q = ((stbi__uint16 *) out) + channel;
+               for (i = 0; i < pixelCount; i++, q += 4)
+                  *q = (stbi__uint16) stbi__get16be(s);
+            } else {
+               stbi_uc *p = out+channel;
+               if (bitdepth == 16) {  // input bpc
+                  for (i = 0; i < pixelCount; i++, p += 4)
+                     *p = (stbi_uc) (stbi__get16be(s) >> 8);
+               } else {
+                  for (i = 0; i < pixelCount; i++, p += 4)
+                     *p = stbi__get8(s);
+               }
+            }
+         }
+      }
+   }
+
+   // remove weird white matte from PSD
+   if (channelCount >= 4) {
+      if (ri->bits_per_channel == 16) {
+         for (i=0; i < w*h; ++i) {
+            stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i;
+            if (pixel[3] != 0 && pixel[3] != 65535) {
+               float a = pixel[3] / 65535.0f;
+               float ra = 1.0f / a;
+               float inv_a = 65535.0f * (1 - ra);
+               pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a);
+               pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a);
+               pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a);
+            }
+         }
+      } else {
+         for (i=0; i < w*h; ++i) {
+            unsigned char *pixel = out + 4*i;
+            if (pixel[3] != 0 && pixel[3] != 255) {
+               float a = pixel[3] / 255.0f;
+               float ra = 1.0f / a;
+               float inv_a = 255.0f * (1 - ra);
+               pixel[0] = (unsigned char) (pixel[0]*ra + inv_a);
+               pixel[1] = (unsigned char) (pixel[1]*ra + inv_a);
+               pixel[2] = (unsigned char) (pixel[2]*ra + inv_a);
+            }
+         }
+      }
+   }
+
+   // convert to desired output format
+   if (req_comp && req_comp != 4) {
+      if (ri->bits_per_channel == 16)
+         out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h);
+      else
+         out = stbi__convert_format(out, 4, req_comp, w, h);
+      if (out == NULL) return out; // stbi__convert_format frees input on failure
+   }
+
+   if (comp) *comp = 4;
+   *y = h;
+   *x = w;
+
+   return out;
+}
+#endif
+
+// *************************************************************************************************
+// Softimage PIC loader
+// by Tom Seddon
+//
+// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format
+// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/
+
+#ifndef STBI_NO_PIC
+static int stbi__pic_is4(stbi__context *s,const char *str)
+{
+   int i;
+   for (i=0; i<4; ++i)
+      if (stbi__get8(s) != (stbi_uc)str[i])
+         return 0;
+
+   return 1;
+}
+
+static int stbi__pic_test_core(stbi__context *s)
+{
+   int i;
+
+   if (!stbi__pic_is4(s,"\x53\x80\xF6\x34"))
+      return 0;
+
+   for(i=0;i<84;++i)
+      stbi__get8(s);
+
+   if (!stbi__pic_is4(s,"PICT"))
+      return 0;
+
+   return 1;
+}
+
+typedef struct
+{
+   stbi_uc size,type,channel;
+} stbi__pic_packet;
+
+static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest)
+{
+   int mask=0x80, i;
+
+   for (i=0; i<4; ++i, mask>>=1) {
+      if (channel & mask) {
+         if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short");
+         dest[i]=stbi__get8(s);
+      }
+   }
+
+   return dest;
+}
+
+static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src)
+{
+   int mask=0x80,i;
+
+   for (i=0;i<4; ++i, mask>>=1)
+      if (channel&mask)
+         dest[i]=src[i];
+}
+
+static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result)
+{
+   int act_comp=0,num_packets=0,y,chained;
+   stbi__pic_packet packets[10];
+
+   // this will (should...) cater for even some bizarre stuff like having data
+    // for the same channel in multiple packets.
+   do {
+      stbi__pic_packet *packet;
+
+      if (num_packets==sizeof(packets)/sizeof(packets[0]))
+         return stbi__errpuc("bad format","too many packets");
+
+      packet = &packets[num_packets++];
+
+      chained = stbi__get8(s);
+      packet->size    = stbi__get8(s);
+      packet->type    = stbi__get8(s);
+      packet->channel = stbi__get8(s);
+
+      act_comp |= packet->channel;
+
+      if (stbi__at_eof(s))          return stbi__errpuc("bad file","file too short (reading packets)");
+      if (packet->size != 8)  return stbi__errpuc("bad format","packet isn't 8bpp");
+   } while (chained);
+
+   *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel?
+
+   for(y=0; 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;
+}
+
+static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri)
+{
+   stbi_uc *result;
+   int i, x,y, internal_comp;
+   STBI_NOTUSED(ri);
+
+   if (!comp) comp = &internal_comp;
+
+   for (i=0; i<92; ++i)
+      stbi__get8(s);
+
+   x = stbi__get16be(s);
+   y = stbi__get16be(s);
+   if (stbi__at_eof(s))  return stbi__errpuc("bad file","file too short (pic header)");
+   if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode");
+
+   stbi__get32be(s); //skip `ratio'
+   stbi__get16be(s); //skip `fields'
+   stbi__get16be(s); //skip `pad'
+
+   // intermediate buffer is RGBA
+   result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0);
+   memset(result, 0xff, x*y*4);
+
+   if (!stbi__pic_load_core(s,x,y,comp, result)) {
+      STBI_FREE(result);
+      result=0;
+   }
+   *px = x;
+   *py = y;
+   if (req_comp == 0) req_comp = *comp;
+   result=stbi__convert_format(result,4,req_comp,x,y);
+
+   return result;
+}
+
+static int stbi__pic_test(stbi__context *s)
+{
+   int r = stbi__pic_test_core(s);
+   stbi__rewind(s);
+   return r;
+}
+#endif
+
+// *************************************************************************************************
+// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb
+
+#ifndef STBI_NO_GIF
+typedef struct
+{
+   stbi__int16 prefix;
+   stbi_uc first;
+   stbi_uc suffix;
+} stbi__gif_lzw;
+
+typedef struct
+{
+   int w,h;
+   stbi_uc *out;                 // output buffer (always 4 components)
+   stbi_uc *background;          // The current "background" as far as a gif is concerned
+   stbi_uc *history;
+   int flags, bgindex, ratio, transparent, eflags;
+   stbi_uc  pal[256][4];
+   stbi_uc lpal[256][4];
+   stbi__gif_lzw codes[8192];
+   stbi_uc *color_table;
+   int parse, step;
+   int lflags;
+   int start_x, start_y;
+   int max_x, max_y;
+   int cur_x, cur_y;
+   int line_size;
+   int delay;
+} stbi__gif;
+
+static int stbi__gif_test_raw(stbi__context *s)
+{
+   int sz;
+   if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0;
+   sz = stbi__get8(s);
+   if (sz != '9' && sz != '7') return 0;
+   if (stbi__get8(s) != 'a') return 0;
+   return 1;
+}
+
+static int stbi__gif_test(stbi__context *s)
+{
+   int r = stbi__gif_test_raw(s);
+   stbi__rewind(s);
+   return r;
+}
+
+static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp)
+{
+   int i;
+   for (i=0; i < num_entries; ++i) {
+      pal[i][2] = stbi__get8(s);
+      pal[i][1] = stbi__get8(s);
+      pal[i][0] = stbi__get8(s);
+      pal[i][3] = transp == i ? 0 : 255;
+   }
+}
+
+static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info)
+{
+   stbi_uc version;
+   if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8')
+      return stbi__err("not GIF", "Corrupt GIF");
+
+   version = stbi__get8(s);
+   if (version != '7' && version != '9')    return stbi__err("not GIF", "Corrupt GIF");
+   if (stbi__get8(s) != 'a')                return stbi__err("not GIF", "Corrupt GIF");
+
+   stbi__g_failure_reason = "";
+   g->w = stbi__get16le(s);
+   g->h = stbi__get16le(s);
+   g->flags = stbi__get8(s);
+   g->bgindex = stbi__get8(s);
+   g->ratio = stbi__get8(s);
+   g->transparent = -1;
+
+   if (comp != 0) *comp = 4;  // can't actually tell whether it's 3 or 4 until we parse the comments
+
+   if (is_info) return 1;
+
+   if (g->flags & 0x80)
+      stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1);
+
+   return 1;
+}
+
+static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp)
+{
+   stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif));
+   if (!stbi__gif_header(s, g, comp, 1)) {
+      STBI_FREE(g);
+      stbi__rewind( s );
+      return 0;
+   }
+   if (x) *x = g->w;
+   if (y) *y = g->h;
+   STBI_FREE(g);
+   return 1;
+}
+
+static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code)
+{
+   stbi_uc *p, *c;
+   int idx;
+
+   // recurse to decode the prefixes, since the linked-list is backwards,
+   // and working backwards through an interleaved image would be nasty
+   if (g->codes[code].prefix >= 0)
+      stbi__out_gif_code(g, g->codes[code].prefix);
+
+   if (g->cur_y >= g->max_y) return;
+
+   idx = g->cur_x + g->cur_y;
+   p = &g->out[idx];
+   g->history[idx / 4] = 1;
+
+   c = &g->color_table[g->codes[code].suffix * 4];
+   if (c[3] > 128) { // don't render transparent pixels;
+      p[0] = c[2];
+      p[1] = c[1];
+      p[2] = c[0];
+      p[3] = c[3];
+   }
+   g->cur_x += 4;
+
+   if (g->cur_x >= g->max_x) {
+      g->cur_x = g->start_x;
+      g->cur_y += g->step;
+
+      while (g->cur_y >= g->max_y && g->parse > 0) {
+         g->step = (1 << g->parse) * g->line_size;
+         g->cur_y = g->start_y + (g->step >> 1);
+         --g->parse;
+      }
+   }
+}
+
+static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g)
+{
+   stbi_uc lzw_cs;
+   stbi__int32 len, init_code;
+   stbi__uint32 first;
+   stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear;
+   stbi__gif_lzw *p;
+
+   lzw_cs = stbi__get8(s);
+   if (lzw_cs > 12) return NULL;
+   clear = 1 << lzw_cs;
+   first = 1;
+   codesize = lzw_cs + 1;
+   codemask = (1 << codesize) - 1;
+   bits = 0;
+   valid_bits = 0;
+   for (init_code = 0; init_code < clear; init_code++) {
+      g->codes[init_code].prefix = -1;
+      g->codes[init_code].first = (stbi_uc) init_code;
+      g->codes[init_code].suffix = (stbi_uc) init_code;
+   }
+
+   // support no starting clear code
+   avail = clear+2;
+   oldcode = -1;
+
+   len = 0;
+   for(;;) {
+      if (valid_bits < codesize) {
+         if (len == 0) {
+            len = stbi__get8(s); // start new block
+            if (len == 0)
+               return g->out;
+         }
+         --len;
+         bits |= (stbi__int32) stbi__get8(s) << valid_bits;
+         valid_bits += 8;
+      } else {
+         stbi__int32 code = bits & codemask;
+         bits >>= codesize;
+         valid_bits -= codesize;
+         // @OPTIMIZE: is there some way we can accelerate the non-clear path?
+         if (code == clear) {  // clear code
+            codesize = lzw_cs + 1;
+            codemask = (1 << codesize) - 1;
+            avail = clear + 2;
+            oldcode = -1;
+            first = 0;
+         } else if (code == clear + 1) { // end of stream code
+            stbi__skip(s, len);
+            while ((len = stbi__get8(s)) > 0)
+               stbi__skip(s,len);
+            return g->out;
+         } else if (code <= avail) {
+            if (first) {
+               return stbi__errpuc("no clear code", "Corrupt GIF");
+            }
+
+            if (oldcode >= 0) {
+               p = &g->codes[avail++];
+               if (avail > 8192) {
+                  return stbi__errpuc("too many codes", "Corrupt GIF");
+               }
+
+               p->prefix = (stbi__int16) oldcode;
+               p->first = g->codes[oldcode].first;
+               p->suffix = (code == avail) ? p->first : g->codes[code].first;
+            } else if (code == avail)
+               return stbi__errpuc("illegal code in raster", "Corrupt GIF");
+
+            stbi__out_gif_code(g, (stbi__uint16) code);
+
+            if ((avail & codemask) == 0 && avail <= 0x0FFF) {
+               codesize++;
+               codemask = (1 << codesize) - 1;
+            }
+
+            oldcode = code;
+         } else {
+            return stbi__errpuc("illegal code in raster", "Corrupt GIF");
+         }
+      }
+   }
+}
+
+// this function is designed to support animated gifs, although stb_image doesn't support it
+// two back is the image from two frames ago, used for a very specific disposal format
+static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back)
+{
+   int dispose;
+   int first_frame;
+   int pi;
+   int pcount;
+   STBI_NOTUSED(req_comp);
+
+   // on first frame, any non-written pixels get the background colour (non-transparent)
+   first_frame = 0;
+   if (g->out == 0) {
+      if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header
+      if (!stbi__mad3sizes_valid(4, g->w, g->h, 0))
+         return stbi__errpuc("too large", "GIF image is too large");
+      pcount = g->w * g->h;
+      g->out = (stbi_uc *) stbi__malloc(4 * pcount);
+      g->background = (stbi_uc *) stbi__malloc(4 * pcount);
+      g->history = (stbi_uc *) stbi__malloc(pcount);
+      if (!g->out || !g->background || !g->history)
+         return stbi__errpuc("outofmem", "Out of memory");
+
+      // image is treated as "transparent" at the start - ie, nothing overwrites the current background;
+      // background colour is only used for pixels that are not rendered first frame, after that "background"
+      // color refers to the color that was there the previous frame.
+      memset(g->out, 0x00, 4 * pcount);
+      memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent)
+      memset(g->history, 0x00, pcount);        // pixels that were affected previous frame
+      first_frame = 1;
+   } else {
+      // second frame - how do we dispoase of the previous one?
+      dispose = (g->eflags & 0x1C) >> 2;
+      pcount = g->w * g->h;
+
+      if ((dispose == 3) && (two_back == 0)) {
+         dispose = 2; // if I don't have an image to revert back to, default to the old background
+      }
+
+      if (dispose == 3) { // use previous graphic
+         for (pi = 0; pi < pcount; ++pi) {
+            if (g->history[pi]) {
+               memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 );
+            }
+         }
+      } else if (dispose == 2) {
+         // restore what was changed last frame to background before that frame;
+         for (pi = 0; pi < pcount; ++pi) {
+            if (g->history[pi]) {
+               memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 );
+            }
+         }
+      } else {
+         // This is a non-disposal case eithe way, so just
+         // leave the pixels as is, and they will become the new background
+         // 1: do not dispose
+         // 0:  not specified.
+      }
+
+      // background is what out is after the undoing of the previou frame;
+      memcpy( g->background, g->out, 4 * g->w * g->h );
+   }
+
+   // clear my history;
+   memset( g->history, 0x00, g->w * g->h );        // pixels that were affected previous frame
+
+   for (;;) {
+      int tag = stbi__get8(s);
+      switch (tag) {
+         case 0x2C: /* Image Descriptor */
+         {
+            stbi__int32 x, y, w, h;
+            stbi_uc *o;
+
+            x = stbi__get16le(s);
+            y = stbi__get16le(s);
+            w = stbi__get16le(s);
+            h = stbi__get16le(s);
+            if (((x + w) > (g->w)) || ((y + h) > (g->h)))
+               return stbi__errpuc("bad Image Descriptor", "Corrupt GIF");
+
+            g->line_size = g->w * 4;
+            g->start_x = x * 4;
+            g->start_y = y * g->line_size;
+            g->max_x   = g->start_x + w * 4;
+            g->max_y   = g->start_y + h * g->line_size;
+            g->cur_x   = g->start_x;
+            g->cur_y   = g->start_y;
+
+            // if the width of the specified rectangle is 0, that means
+            // we may not see *any* pixels or the image is malformed;
+            // to make sure this is caught, move the current y down to
+            // max_y (which is what out_gif_code checks).
+            if (w == 0)
+               g->cur_y = g->max_y;
+
+            g->lflags = stbi__get8(s);
+
+            if (g->lflags & 0x40) {
+               g->step = 8 * g->line_size; // first interlaced spacing
+               g->parse = 3;
+            } else {
+               g->step = g->line_size;
+               g->parse = 0;
+            }
+
+            if (g->lflags & 0x80) {
+               stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1);
+               g->color_table = (stbi_uc *) g->lpal;
+            } else if (g->flags & 0x80) {
+               g->color_table = (stbi_uc *) g->pal;
+            } else
+               return stbi__errpuc("missing color table", "Corrupt GIF");
+
+            o = stbi__process_gif_raster(s, g);
+            if (!o) return NULL;
+
+            // if this was the first frame,
+            pcount = g->w * g->h;
+            if (first_frame && (g->bgindex > 0)) {
+               // if first frame, any pixel not drawn to gets the background color
+               for (pi = 0; pi < pcount; ++pi) {
+                  if (g->history[pi] == 0) {
+                     g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be;
+                     memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 );
+                  }
+               }
+            }
+
+            return o;
+         }
+
+         case 0x21: // Comment Extension.
+         {
+            int len;
+            int ext = stbi__get8(s);
+            if (ext == 0xF9) { // Graphic Control Extension.
+               len = stbi__get8(s);
+               if (len == 4) {
+                  g->eflags = stbi__get8(s);
+                  g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths.
+
+                  // unset old transparent
+                  if (g->transparent >= 0) {
+                     g->pal[g->transparent][3] = 255;
+                  }
+                  if (g->eflags & 0x01) {
+                     g->transparent = stbi__get8(s);
+                     if (g->transparent >= 0) {
+                        g->pal[g->transparent][3] = 0;
+                     }
+                  } else {
+                     // don't need transparent
+                     stbi__skip(s, 1);
+                     g->transparent = -1;
+                  }
+               } else {
+                  stbi__skip(s, len);
+                  break;
+               }
+            }
+            while ((len = stbi__get8(s)) != 0) {
+               stbi__skip(s, len);
+            }
+            break;
+         }
+
+         case 0x3B: // gif stream termination code
+            return (stbi_uc *) s; // using '1' causes warning on some compilers
+
+         default:
+            return stbi__errpuc("unknown code", "Corrupt GIF");
+      }
+   }
+}
+
+static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
+{
+   if (stbi__gif_test(s)) {
+      int layers = 0;
+      stbi_uc *u = 0;
+      stbi_uc *out = 0;
+      stbi_uc *two_back = 0;
+      stbi__gif g;
+      int stride;
+      memset(&g, 0, sizeof(g));
+      if (delays) {
+         *delays = 0;
+      }
+
+      do {
+         u = stbi__gif_load_next(s, &g, comp, req_comp, two_back);
+         if (u == (stbi_uc *) s) u = 0;  // end of animated gif marker
+
+         if (u) {
+            *x = g.w;
+            *y = g.h;
+            ++layers;
+            stride = g.w * g.h * 4;
+
+            if (out) {
+               void *tmp = (stbi_uc*) STBI_REALLOC( out, layers * stride );
+               if (NULL == tmp) {
+                  STBI_FREE(g.out);
+                  STBI_FREE(g.history);
+                  STBI_FREE(g.background);
+                  return stbi__errpuc("outofmem", "Out of memory");
+               }
+               else
+                  out = (stbi_uc*) tmp;
+               if (delays) {
+                  *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers );
+               }
+            } else {
+               out = (stbi_uc*)stbi__malloc( layers * stride );
+               if (delays) {
+                  *delays = (int*) stbi__malloc( layers * sizeof(int) );
+               }
+            }
+            memcpy( out + ((layers - 1) * stride), u, stride );
+            if (layers >= 2) {
+               two_back = out - 2 * stride;
+            }
+
+            if (delays) {
+               (*delays)[layers - 1U] = g.delay;
+            }
+         }
+      } while (u != 0);
+
+      // free temp buffer;
+      STBI_FREE(g.out);
+      STBI_FREE(g.history);
+      STBI_FREE(g.background);
+
+      // do the final conversion after loading everything;
+      if (req_comp && req_comp != 4)
+         out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h);
+
+      *z = layers;
+      return out;
+   } else {
+      return stbi__errpuc("not GIF", "Image was not as a gif type.");
+   }
+}
+
+static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   stbi_uc *u = 0;
+   stbi__gif g;
+   memset(&g, 0, sizeof(g));
+   STBI_NOTUSED(ri);
+
+   u = stbi__gif_load_next(s, &g, comp, req_comp, 0);
+   if (u == (stbi_uc *) s) u = 0;  // end of animated gif marker
+   if (u) {
+      *x = g.w;
+      *y = g.h;
+
+      // moved conversion to after successful load so that the same
+      // can be done for multiple frames.
+      if (req_comp && req_comp != 4)
+         u = stbi__convert_format(u, 4, req_comp, g.w, g.h);
+   } else if (g.out) {
+      // if there was an error and we allocated an image buffer, free it!
+      STBI_FREE(g.out);
+   }
+
+   // free buffers needed for multiple frame loading;
+   STBI_FREE(g.history);
+   STBI_FREE(g.background);
+
+   return u;
+}
+
+static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   return stbi__gif_info_raw(s,x,y,comp);
+}
+#endif
+
+// *************************************************************************************************
+// Radiance RGBE HDR loader
+// originally by Nicolas Schulz
+#ifndef STBI_NO_HDR
+static int stbi__hdr_test_core(stbi__context *s, const char *signature)
+{
+   int i;
+   for (i=0; signature[i]; ++i)
+      if (stbi__get8(s) != signature[i])
+          return 0;
+   stbi__rewind(s);
+   return 1;
+}
+
+static int stbi__hdr_test(stbi__context* s)
+{
+   int r = stbi__hdr_test_core(s, "#?RADIANCE\n");
+   stbi__rewind(s);
+   if(!r) {
+       r = stbi__hdr_test_core(s, "#?RGBE\n");
+       stbi__rewind(s);
+   }
+   return r;
+}
+
+#define STBI__HDR_BUFLEN  1024
+static char *stbi__hdr_gettoken(stbi__context *z, char *buffer)
+{
+   int len=0;
+   char c = '\0';
+
+   c = (char) stbi__get8(z);
+
+   while (!stbi__at_eof(z) && c != '\n') {
+      buffer[len++] = c;
+      if (len == STBI__HDR_BUFLEN-1) {
+         // flush to end of line
+         while (!stbi__at_eof(z) && stbi__get8(z) != '\n')
+            ;
+         break;
+      }
+      c = (char) stbi__get8(z);
+   }
+
+   buffer[len] = 0;
+   return buffer;
+}
+
+static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp)
+{
+   if ( input[3] != 0 ) {
+      float f1;
+      // Exponent
+      f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8));
+      if (req_comp <= 2)
+         output[0] = (input[0] + input[1] + input[2]) * f1 / 3;
+      else {
+         output[0] = input[0] * f1;
+         output[1] = input[1] * f1;
+         output[2] = input[2] * f1;
+      }
+      if (req_comp == 2) output[1] = 1;
+      if (req_comp == 4) output[3] = 1;
+   } else {
+      switch (req_comp) {
+         case 4: output[3] = 1; /* fallthrough */
+         case 3: output[0] = output[1] = output[2] = 0;
+                 break;
+         case 2: output[1] = 1; /* fallthrough */
+         case 1: output[0] = 0;
+                 break;
+      }
+   }
+}
+
+static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   char buffer[STBI__HDR_BUFLEN];
+   char *token;
+   int valid = 0;
+   int width, height;
+   stbi_uc *scanline;
+   float *hdr_data;
+   int len;
+   unsigned char count, value;
+   int i, j, k, c1,c2, z;
+   const char *headerToken;
+   STBI_NOTUSED(ri);
+
+   // Check identifier
+   headerToken = stbi__hdr_gettoken(s,buffer);
+   if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0)
+      return stbi__errpf("not HDR", "Corrupt HDR image");
+
+   // Parse header
+   for(;;) {
+      token = stbi__hdr_gettoken(s,buffer);
+      if (token[0] == 0) break;
+      if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
+   }
+
+   if (!valid)    return stbi__errpf("unsupported format", "Unsupported HDR format");
+
+   // Parse width and height
+   // can't use sscanf() if we're not using stdio!
+   token = stbi__hdr_gettoken(s,buffer);
+   if (strncmp(token, "-Y ", 3))  return stbi__errpf("unsupported data layout", "Unsupported HDR format");
+   token += 3;
+   height = (int) strtol(token, &token, 10);
+   while (*token == ' ') ++token;
+   if (strncmp(token, "+X ", 3))  return stbi__errpf("unsupported data layout", "Unsupported HDR format");
+   token += 3;
+   width = (int) strtol(token, NULL, 10);
+
+   *x = width;
+   *y = height;
+
+   if (comp) *comp = 3;
+   if (req_comp == 0) req_comp = 3;
+
+   if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0))
+      return stbi__errpf("too large", "HDR image is too large");
+
+   // Read data
+   hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0);
+   if (!hdr_data)
+      return stbi__errpf("outofmem", "Out of memory");
+
+   // Load image data
+   // image data is stored as some number of sca
+   if ( width < 8 || width >= 32768) {
+      // Read flat data
+      for (j=0; j < height; ++j) {
+         for (i=0; i < width; ++i) {
+            stbi_uc rgbe[4];
+           main_decode_loop:
+            stbi__getn(s, rgbe, 4);
+            stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp);
+         }
+      }
+   } else {
+      // Read RLE-encoded data
+      scanline = NULL;
+
+      for (j = 0; j < height; ++j) {
+         c1 = stbi__get8(s);
+         c2 = stbi__get8(s);
+         len = stbi__get8(s);
+         if (c1 != 2 || c2 != 2 || (len & 0x80)) {
+            // not run-length encoded, so we have to actually use THIS data as a decoded
+            // pixel (note this can't be a valid pixel--one of RGB must be >= 128)
+            stbi_uc rgbe[4];
+            rgbe[0] = (stbi_uc) c1;
+            rgbe[1] = (stbi_uc) c2;
+            rgbe[2] = (stbi_uc) len;
+            rgbe[3] = (stbi_uc) stbi__get8(s);
+            stbi__hdr_convert(hdr_data, rgbe, req_comp);
+            i = 1;
+            j = 0;
+            STBI_FREE(scanline);
+            goto main_decode_loop; // yes, this makes no sense
+         }
+         len <<= 8;
+         len |= stbi__get8(s);
+         if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); }
+         if (scanline == NULL) {
+            scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0);
+            if (!scanline) {
+               STBI_FREE(hdr_data);
+               return stbi__errpf("outofmem", "Out of memory");
+            }
+         }
+
+         for (k = 0; k < 4; ++k) {
+            int nleft;
+            i = 0;
+            while ((nleft = width - i) > 0) {
+               count = stbi__get8(s);
+               if (count > 128) {
+                  // Run
+                  value = stbi__get8(s);
+                  count -= 128;
+                  if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
+                  for (z = 0; z < count; ++z)
+                     scanline[i++ * 4 + k] = value;
+               } else {
+                  // Dump
+                  if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); }
+                  for (z = 0; z < count; ++z)
+                     scanline[i++ * 4 + k] = stbi__get8(s);
+               }
+            }
+         }
+         for (i=0; i < width; ++i)
+            stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp);
+      }
+      if (scanline)
+         STBI_FREE(scanline);
+   }
+
+   return hdr_data;
+}
+
+static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   char buffer[STBI__HDR_BUFLEN];
+   char *token;
+   int valid = 0;
+   int dummy;
+
+   if (!x) x = &dummy;
+   if (!y) y = &dummy;
+   if (!comp) comp = &dummy;
+
+   if (stbi__hdr_test(s) == 0) {
+       stbi__rewind( s );
+       return 0;
+   }
+
+   for(;;) {
+      token = stbi__hdr_gettoken(s,buffer);
+      if (token[0] == 0) break;
+      if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1;
+   }
+
+   if (!valid) {
+       stbi__rewind( s );
+       return 0;
+   }
+   token = stbi__hdr_gettoken(s,buffer);
+   if (strncmp(token, "-Y ", 3)) {
+       stbi__rewind( s );
+       return 0;
+   }
+   token += 3;
+   *y = (int) strtol(token, &token, 10);
+   while (*token == ' ') ++token;
+   if (strncmp(token, "+X ", 3)) {
+       stbi__rewind( s );
+       return 0;
+   }
+   token += 3;
+   *x = (int) strtol(token, NULL, 10);
+   *comp = 3;
+   return 1;
+}
+#endif // STBI_NO_HDR
+
+#ifndef STBI_NO_BMP
+static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   void *p;
+   stbi__bmp_data info;
+
+   info.all_a = 255;
+   p = stbi__bmp_parse_header(s, &info);
+   stbi__rewind( s );
+   if (p == NULL)
+      return 0;
+   if (x) *x = s->img_x;
+   if (y) *y = s->img_y;
+   if (comp) {
+      if (info.bpp == 24 && info.ma == 0xff000000)
+         *comp = 3;
+      else
+         *comp = info.ma ? 4 : 3;
+   }
+   return 1;
+}
+#endif
+
+#ifndef STBI_NO_PSD
+static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   int channelCount, dummy, depth;
+   if (!x) x = &dummy;
+   if (!y) y = &dummy;
+   if (!comp) comp = &dummy;
+   if (stbi__get32be(s) != 0x38425053) {
+       stbi__rewind( s );
+       return 0;
+   }
+   if (stbi__get16be(s) != 1) {
+       stbi__rewind( s );
+       return 0;
+   }
+   stbi__skip(s, 6);
+   channelCount = stbi__get16be(s);
+   if (channelCount < 0 || channelCount > 16) {
+       stbi__rewind( s );
+       return 0;
+   }
+   *y = stbi__get32be(s);
+   *x = stbi__get32be(s);
+   depth = stbi__get16be(s);
+   if (depth != 8 && depth != 16) {
+       stbi__rewind( s );
+       return 0;
+   }
+   if (stbi__get16be(s) != 3) {
+       stbi__rewind( s );
+       return 0;
+   }
+   *comp = 4;
+   return 1;
+}
+
+static int stbi__psd_is16(stbi__context *s)
+{
+   int channelCount, depth;
+   if (stbi__get32be(s) != 0x38425053) {
+       stbi__rewind( s );
+       return 0;
+   }
+   if (stbi__get16be(s) != 1) {
+       stbi__rewind( s );
+       return 0;
+   }
+   stbi__skip(s, 6);
+   channelCount = stbi__get16be(s);
+   if (channelCount < 0 || channelCount > 16) {
+       stbi__rewind( s );
+       return 0;
+   }
+   (void) stbi__get32be(s);
+   (void) stbi__get32be(s);
+   depth = stbi__get16be(s);
+   if (depth != 16) {
+       stbi__rewind( s );
+       return 0;
+   }
+   return 1;
+}
+#endif
+
+#ifndef STBI_NO_PIC
+static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   int act_comp=0,num_packets=0,chained,dummy;
+   stbi__pic_packet packets[10];
+
+   if (!x) x = &dummy;
+   if (!y) y = &dummy;
+   if (!comp) comp = &dummy;
+
+   if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) {
+      stbi__rewind(s);
+      return 0;
+   }
+
+   stbi__skip(s, 88);
+
+   *x = stbi__get16be(s);
+   *y = stbi__get16be(s);
+   if (stbi__at_eof(s)) {
+      stbi__rewind( s);
+      return 0;
+   }
+   if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) {
+      stbi__rewind( s );
+      return 0;
+   }
+
+   stbi__skip(s, 8);
+
+   do {
+      stbi__pic_packet *packet;
+
+      if (num_packets==sizeof(packets)/sizeof(packets[0]))
+         return 0;
+
+      packet = &packets[num_packets++];
+      chained = stbi__get8(s);
+      packet->size    = stbi__get8(s);
+      packet->type    = stbi__get8(s);
+      packet->channel = stbi__get8(s);
+      act_comp |= packet->channel;
+
+      if (stbi__at_eof(s)) {
+          stbi__rewind( s );
+          return 0;
+      }
+      if (packet->size != 8) {
+          stbi__rewind( s );
+          return 0;
+      }
+   } while (chained);
+
+   *comp = (act_comp & 0x10 ? 4 : 3);
+
+   return 1;
+}
+#endif
+
+// *************************************************************************************************
+// Portable Gray Map and Portable Pixel Map loader
+// by Ken Miller
+//
+// PGM: http://netpbm.sourceforge.net/doc/pgm.html
+// PPM: http://netpbm.sourceforge.net/doc/ppm.html
+//
+// Known limitations:
+//    Does not support comments in the header section
+//    Does not support ASCII image data (formats P2 and P3)
+//    Does not support 16-bit-per-channel
+
+#ifndef STBI_NO_PNM
+
+static int      stbi__pnm_test(stbi__context *s)
+{
+   char p, t;
+   p = (char) stbi__get8(s);
+   t = (char) stbi__get8(s);
+   if (p != 'P' || (t != '5' && t != '6')) {
+       stbi__rewind( s );
+       return 0;
+   }
+   return 1;
+}
+
+static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri)
+{
+   stbi_uc *out;
+   STBI_NOTUSED(ri);
+
+   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;
+   if (comp) *comp = s->img_n;
+
+   if (!stbi__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0))
+      return stbi__errpuc("too large", "PNM too large");
+
+   out = (stbi_uc *) stbi__malloc_mad3(s->img_n, s->img_x, s->img_y, 0);
+   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;
+}
+
+static int      stbi__pnm_isspace(char c)
+{
+   return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r';
+}
+
+static void     stbi__pnm_skip_whitespace(stbi__context *s, char *c)
+{
+   for (;;) {
+      while (!stbi__at_eof(s) && stbi__pnm_isspace(*c))
+         *c = (char) stbi__get8(s);
+
+      if (stbi__at_eof(s) || *c != '#')
+         break;
+
+      while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' )
+         *c = (char) stbi__get8(s);
+   }
+}
+
+static int      stbi__pnm_isdigit(char c)
+{
+   return c >= '0' && c <= '9';
+}
+
+static int      stbi__pnm_getinteger(stbi__context *s, char *c)
+{
+   int value = 0;
+
+   while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) {
+      value = value*10 + (*c - '0');
+      *c = (char) stbi__get8(s);
+   }
+
+   return value;
+}
+
+static int      stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp)
+{
+   int maxv, dummy;
+   char c, p, t;
+
+   if (!x) x = &dummy;
+   if (!y) y = &dummy;
+   if (!comp) comp = &dummy;
+
+   stbi__rewind(s);
+
+   // Get identifier
+   p = (char) stbi__get8(s);
+   t = (char) stbi__get8(s);
+   if (p != 'P' || (t != '5' && t != '6')) {
+       stbi__rewind(s);
+       return 0;
+   }
+
+   *comp = (t == '6') ? 3 : 1;  // '5' is 1-component .pgm; '6' is 3-component .ppm
+
+   c = (char) stbi__get8(s);
+   stbi__pnm_skip_whitespace(s, &c);
+
+   *x = stbi__pnm_getinteger(s, &c); // read width
+   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
+
+static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp)
+{
+   #ifndef STBI_NO_JPEG
+   if (stbi__jpeg_info(s, x, y, comp)) return 1;
+   #endif
+
+   #ifndef STBI_NO_PNG
+   if (stbi__png_info(s, x, y, comp))  return 1;
+   #endif
+
+   #ifndef STBI_NO_GIF
+   if (stbi__gif_info(s, x, y, comp))  return 1;
+   #endif
+
+   #ifndef STBI_NO_BMP
+   if (stbi__bmp_info(s, x, y, comp))  return 1;
+   #endif
+
+   #ifndef STBI_NO_PSD
+   if (stbi__psd_info(s, x, y, comp))  return 1;
+   #endif
+
+   #ifndef STBI_NO_PIC
+   if (stbi__pic_info(s, x, y, comp))  return 1;
+   #endif
+
+   #ifndef STBI_NO_PNM
+   if (stbi__pnm_info(s, x, y, comp))  return 1;
+   #endif
+
+   #ifndef STBI_NO_HDR
+   if (stbi__hdr_info(s, x, y, comp))  return 1;
+   #endif
+
+   // test tga last because it's a crappy test!
+   #ifndef STBI_NO_TGA
+   if (stbi__tga_info(s, x, y, comp))
+       return 1;
+   #endif
+   return stbi__err("unknown image type", "Image not of any known type, or corrupt");
+}
+
+static int stbi__is_16_main(stbi__context *s)
+{
+   #ifndef STBI_NO_PNG
+   if (stbi__png_is16(s))  return 1;
+   #endif
+
+   #ifndef STBI_NO_PSD
+   if (stbi__psd_is16(s))  return 1;
+   #endif
+
+   return 0;
+}
+
+#ifndef STBI_NO_STDIO
+STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp)
+{
+    FILE *f = stbi__fopen(filename, "rb");
+    int result;
+    if (!f) return stbi__err("can't fopen", "Unable to open file");
+    result = stbi_info_from_file(f, x, y, comp);
+    fclose(f);
+    return result;
+}
+
+STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp)
+{
+   int r;
+   stbi__context s;
+   long pos = ftell(f);
+   stbi__start_file(&s, f);
+   r = stbi__info_main(&s,x,y,comp);
+   fseek(f,pos,SEEK_SET);
+   return r;
+}
+
+STBIDEF int stbi_is_16_bit(char const *filename)
+{
+    FILE *f = stbi__fopen(filename, "rb");
+    int result;
+    if (!f) return stbi__err("can't fopen", "Unable to open file");
+    result = stbi_is_16_bit_from_file(f);
+    fclose(f);
+    return result;
+}
+
+STBIDEF int stbi_is_16_bit_from_file(FILE *f)
+{
+   int r;
+   stbi__context s;
+   long pos = ftell(f);
+   stbi__start_file(&s, f);
+   r = stbi__is_16_main(&s);
+   fseek(f,pos,SEEK_SET);
+   return r;
+}
+#endif // !STBI_NO_STDIO
+
+STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp)
+{
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+   return stbi__info_main(&s,x,y,comp);
+}
+
+STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp)
+{
+   stbi__context s;
+   stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);
+   return stbi__info_main(&s,x,y,comp);
+}
+
+STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len)
+{
+   stbi__context s;
+   stbi__start_mem(&s,buffer,len);
+   return stbi__is_16_main(&s);
+}
+
+STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user)
+{
+   stbi__context s;
+   stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user);
+   return stbi__is_16_main(&s);
+}
+
+#endif // STB_IMAGE_IMPLEMENTATION
+
+/*
+   revision history:
+      2.20  (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs
+      2.19  (2018-02-11) fix warning
+      2.18  (2018-01-30) fix warnings
+      2.17  (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug
+                         1-bit BMP
+                         *_is_16_bit api
+                         avoid warnings
+      2.16  (2017-07-23) all functions have 16-bit variants;
+                         STBI_NO_STDIO works again;
+                         compilation fixes;
+                         fix rounding in unpremultiply;
+                         optimize vertical flip;
+                         disable raw_len validation;
+                         documentation fixes
+      2.15  (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode;
+                         warning fixes; disable run-time SSE detection on gcc;
+                         uniform handling of optional "return" values;
+                         thread-safe initialization of zlib tables
+      2.14  (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs
+      2.13  (2016-11-29) add 16-bit API, only supported for PNG right now
+      2.12  (2016-04-02) fix typo in 2.11 PSD fix that caused crashes
+      2.11  (2016-04-02) allocate large structures on the stack
+                         remove white matting for transparent PSD
+                         fix reported channel count for PNG & BMP
+                         re-enable SSE2 in non-gcc 64-bit
+                         support RGB-formatted JPEG
+                         read 16-bit PNGs (only as 8-bit)
+      2.10  (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED
+      2.09  (2016-01-16) allow comments in PNM files
+                         16-bit-per-pixel TGA (not bit-per-component)
+                         info() for TGA could break due to .hdr handling
+                         info() for BMP to shares code instead of sloppy parse
+                         can use STBI_REALLOC_SIZED if allocator doesn't support realloc
+                         code cleanup
+      2.08  (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA
+      2.07  (2015-09-13) fix compiler warnings
+                         partial animated GIF support
+                         limited 16-bpc PSD support
+                         #ifdef unused functions
+                         bug with < 92 byte PIC,PNM,HDR,TGA
+      2.06  (2015-04-19) fix bug where PSD returns wrong '*comp' value
+      2.05  (2015-04-19) fix bug in progressive JPEG handling, fix warning
+      2.04  (2015-04-15) try to re-enable SIMD on MinGW 64-bit
+      2.03  (2015-04-12) extra corruption checking (mmozeiko)
+                         stbi_set_flip_vertically_on_load (nguillemot)
+                         fix NEON support; fix mingw support
+      2.02  (2015-01-19) fix incorrect assert, fix warning
+      2.01  (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2
+      2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG
+      2.00  (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg)
+                         progressive JPEG (stb)
+                         PGM/PPM support (Ken Miller)
+                         STBI_MALLOC,STBI_REALLOC,STBI_FREE
+                         GIF bugfix -- seemingly never worked
+                         STBI_NO_*, STBI_ONLY_*
+      1.48  (2014-12-14) fix incorrectly-named assert()
+      1.47  (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb)
+                         optimize PNG (ryg)
+                         fix bug in interlaced PNG with user-specified channel count (stb)
+      1.46  (2014-08-26)
+              fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG
+      1.45  (2014-08-16)
+              fix MSVC-ARM internal compiler error by wrapping malloc
+      1.44  (2014-08-07)
+              various warning fixes from Ronny Chevalier
+      1.43  (2014-07-15)
+              fix MSVC-only compiler problem in code changed in 1.42
+      1.42  (2014-07-09)
+              don't define _CRT_SECURE_NO_WARNINGS (affects user code)
+              fixes to stbi__cleanup_jpeg path
+              added STBI_ASSERT to avoid requiring assert.h
+      1.41  (2014-06-25)
+              fix search&replace from 1.36 that messed up comments/error messages
+      1.40  (2014-06-22)
+              fix gcc struct-initialization warning
+      1.39  (2014-06-15)
+              fix to TGA optimization when req_comp != number of components in TGA;
+              fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite)
+              add support for BMP version 5 (more ignored fields)
+      1.38  (2014-06-06)
+              suppress MSVC warnings on integer casts truncating values
+              fix accidental rename of 'skip' field of I/O
+      1.37  (2014-06-04)
+              remove duplicate typedef
+      1.36  (2014-06-03)
+              convert to header file single-file library
+              if de-iphone isn't set, load iphone images color-swapped instead of returning NULL
+      1.35  (2014-05-27)
+              various warnings
+              fix broken STBI_SIMD path
+              fix bug where stbi_load_from_file no longer left file pointer in correct place
+              fix broken non-easy path for 32-bit BMP (possibly never used)
+              TGA optimization by Arseny Kapoulkine
+      1.34  (unknown)
+              use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case
+      1.33  (2011-07-14)
+              make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements
+      1.32  (2011-07-13)
+              support for "info" function for all supported filetypes (SpartanJ)
+      1.31  (2011-06-20)
+              a few more leak fixes, bug in PNG handling (SpartanJ)
+      1.30  (2011-06-11)
+              added ability to load files via callbacks to accomidate custom input streams (Ben Wenger)
+              removed deprecated format-specific test/load functions
+              removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway
+              error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha)
+              fix inefficiency in decoding 32-bit BMP (David Woo)
+      1.29  (2010-08-16)
+              various warning fixes from Aurelien Pocheville
+      1.28  (2010-08-01)
+              fix bug in GIF palette transparency (SpartanJ)
+      1.27  (2010-08-01)
+              cast-to-stbi_uc to fix warnings
+      1.26  (2010-07-24)
+              fix bug in file buffering for PNG reported by SpartanJ
+      1.25  (2010-07-17)
+              refix trans_data warning (Won Chun)
+      1.24  (2010-07-12)
+              perf improvements reading from files on platforms with lock-heavy fgetc()
+              minor perf improvements for jpeg
+              deprecated type-specific functions so we'll get feedback if they're needed
+              attempt to fix trans_data warning (Won Chun)
+      1.23    fixed bug in iPhone support
+      1.22  (2010-07-10)
+              removed image *writing* support
+              stbi_info support from Jetro Lauha
+              GIF support from Jean-Marc Lienher
+              iPhone PNG-extensions from James Brown
+              warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva)
+      1.21    fix use of 'stbi_uc' in header (reported by jon blow)
+      1.20    added support for Softimage PIC, by Tom Seddon
+      1.19    bug in interlaced PNG corruption check (found by ryg)
+      1.18  (2008-08-02)
+              fix a threading bug (local mutable static)
+      1.17    support interlaced PNG
+      1.16    major bugfix - stbi__convert_format converted one too many pixels
+      1.15    initialize some fields for thread safety
+      1.14    fix threadsafe conversion bug
+              header-file-only version (#define STBI_HEADER_FILE_ONLY before including)
+      1.13    threadsafe
+      1.12    const qualifiers in the API
+      1.11    Support installable IDCT, colorspace conversion routines
+      1.10    Fixes for 64-bit (don't use "unsigned long")
+              optimized upsampling by Fabian "ryg" Giesen
+      1.09    Fix format-conversion for PSD code (bad global variables!)
+      1.08    Thatcher Ulrich's PSD code integrated by Nicolas Schulz
+      1.07    attempt to fix C++ warning/errors again
+      1.06    attempt to fix C++ warning/errors again
+      1.05    fix TGA loading to return correct *comp and use good luminance calc
+      1.04    default float alpha is 1, not 255; use 'void *' for stbi_image_free
+      1.03    bugfixes to STBI_NO_STDIO, STBI_NO_HDR
+      1.02    support for (subset of) HDR files, float interface for preferred access to them
+      1.01    fix bug: possible bug in handling right-side up bmps... not sure
+              fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all
+      1.00    interface to zlib that skips zlib header
+      0.99    correct handling of alpha in palette
+      0.98    TGA loader by lonesock; dynamically add loaders (untested)
+      0.97    jpeg errors on too large a file; also catch another malloc failure
+      0.96    fix detection of invalid v value - particleman@mollyrocket forum
+      0.95    during header scan, seek to markers in case of padding
+      0.94    STBI_NO_STDIO to disable stdio usage; rename all #defines the same
+      0.93    handle jpegtran output; verbose errors
+      0.92    read 4,8,16,24,32-bit BMP files of several formats
+      0.91    output 24-bit Windows 3.0 BMP files
+      0.90    fix a few more warnings; bump version number to approach 1.0
+      0.61    bugfixes due to Marc LeBlanc, Christopher Lloyd
+      0.60    fix compiling as c++
+      0.59    fix warnings: merge Dave Moore's -Wall fixes
+      0.58    fix bug: zlib uncompressed mode len/nlen was wrong endian
+      0.57    fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available
+      0.56    fix bug: zlib uncompressed mode len vs. nlen
+      0.55    fix bug: restart_interval not initialized to 0
+      0.54    allow NULL for 'int *comp'
+      0.53    fix bug in png 3->4; speedup png decoding
+      0.52    png handles req_comp=3,4 directly; minor cleanup; jpeg comments
+      0.51    obey req_comp requests, 1-component jpegs return as 1-component,
+              on 'test' only check type, not whether we support this variant
+      0.50  (2006-11-19)
+              first released version
+*/
+
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+*/

+ 2598 - 0
code/lib/jomjol_image_proc/stb_image_resize.h

@@ -0,0 +1,2598 @@
+/* stb_image_resize - v0.96 - public domain image resizing
+   by Jorge L Rodriguez (@VinoBS) - 2014
+   http://github.com/nothings/stb
+   Written with emphasis on usability, portability, and efficiency. (No
+   SIMD or threads, so it be easily outperformed by libs that use those.)
+   Only scaling and translation is supported, no rotations or shears.
+   Easy API downsamples w/Mitchell filter, upsamples w/cubic interpolation.
+   COMPILING & LINKING
+      In one C/C++ file that #includes this file, do this:
+         #define STB_IMAGE_RESIZE_IMPLEMENTATION
+      before the #include. That will create the implementation in that file.
+   QUICKSTART
+      stbir_resize_uint8(      input_pixels , in_w , in_h , 0,
+                               output_pixels, out_w, out_h, 0, num_channels)
+      stbir_resize_float(...)
+      stbir_resize_uint8_srgb( input_pixels , in_w , in_h , 0,
+                               output_pixels, out_w, out_h, 0,
+                               num_channels , alpha_chan  , 0)
+      stbir_resize_uint8_srgb_edgemode(
+                               input_pixels , in_w , in_h , 0,
+                               output_pixels, out_w, out_h, 0,
+                               num_channels , alpha_chan  , 0, STBIR_EDGE_CLAMP)
+                                                            // WRAP/REFLECT/ZERO
+   FULL API
+      See the "header file" section of the source for API documentation.
+   ADDITIONAL DOCUMENTATION
+      SRGB & FLOATING POINT REPRESENTATION
+         The sRGB functions presume IEEE floating point. If you do not have
+         IEEE floating point, define STBIR_NON_IEEE_FLOAT. This will use
+         a slower implementation.
+      MEMORY ALLOCATION
+         The resize functions here perform a single memory allocation using
+         malloc. To control the memory allocation, before the #include that
+         triggers the implementation, do:
+            #define STBIR_MALLOC(size,context) ...
+            #define STBIR_FREE(ptr,context)   ...
+         Each resize function makes exactly one call to malloc/free, so to use
+         temp memory, store the temp memory in the context and return that.
+      ASSERT
+         Define STBIR_ASSERT(boolval) to override assert() and not use assert.h
+      OPTIMIZATION
+         Define STBIR_SATURATE_INT to compute clamp values in-range using
+         integer operations instead of float operations. This may be faster
+         on some platforms.
+      DEFAULT FILTERS
+         For functions which don't provide explicit control over what filters
+         to use, you can change the compile-time defaults with
+            #define STBIR_DEFAULT_FILTER_UPSAMPLE     STBIR_FILTER_something
+            #define STBIR_DEFAULT_FILTER_DOWNSAMPLE   STBIR_FILTER_something
+         See stbir_filter in the header-file section for the list of filters.
+      NEW FILTERS
+         A number of 1D filter kernels are used. For a list of
+         supported filters see the stbir_filter enum. To add a new filter,
+         write a filter function and add it to stbir__filter_info_table.
+      PROGRESS
+         For interactive use with slow resize operations, you can install
+         a progress-report callback:
+            #define STBIR_PROGRESS_REPORT(val)   some_func(val)
+         The parameter val is a float which goes from 0 to 1 as progress is made.
+         For example:
+            static void my_progress_report(float progress);
+            #define STBIR_PROGRESS_REPORT(val) my_progress_report(val)
+            #define STB_IMAGE_RESIZE_IMPLEMENTATION
+            #include "stb_image_resize.h"
+            static void my_progress_report(float progress)
+            {
+               printf("Progress: %f%%\n", progress*100);
+            }
+      MAX CHANNELS
+         If your image has more than 64 channels, define STBIR_MAX_CHANNELS
+         to the max you'll have.
+      ALPHA CHANNEL
+         Most of the resizing functions provide the ability to control how
+         the alpha channel of an image is processed. The important things
+         to know about this:
+         1. The best mathematically-behaved version of alpha to use is
+         called "premultiplied alpha", in which the other color channels
+         have had the alpha value multiplied in. If you use premultiplied
+         alpha, linear filtering (such as image resampling done by this
+         library, or performed in texture units on GPUs) does the "right
+         thing". While premultiplied alpha is standard in the movie CGI
+         industry, it is still uncommon in the videogame/real-time world.
+         If you linearly filter non-premultiplied alpha, strange effects
+         occur. (For example, the 50/50 average of 99% transparent bright green
+         and 1% transparent black produces 50% transparent dark green when
+         non-premultiplied, whereas premultiplied it produces 50%
+         transparent near-black. The former introduces green energy
+         that doesn't exist in the source image.)
+         2. Artists should not edit premultiplied-alpha images; artists
+         want non-premultiplied alpha images. Thus, art tools generally output
+         non-premultiplied alpha images.
+         3. You will get best results in most cases by converting images
+         to premultiplied alpha before processing them mathematically.
+         4. If you pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED, the
+         resizer does not do anything special for the alpha channel;
+         it is resampled identically to other channels. This produces
+         the correct results for premultiplied-alpha images, but produces
+         less-than-ideal results for non-premultiplied-alpha images.
+         5. If you do not pass the flag STBIR_FLAG_ALPHA_PREMULTIPLIED,
+         then the resizer weights the contribution of input pixels
+         based on their alpha values, or, equivalently, it multiplies
+         the alpha value into the color channels, resamples, then divides
+         by the resultant alpha value. Input pixels which have alpha=0 do
+         not contribute at all to output pixels unless _all_ of the input
+         pixels affecting that output pixel have alpha=0, in which case
+         the result for that pixel is the same as it would be without
+         STBIR_FLAG_ALPHA_PREMULTIPLIED. However, this is only true for
+         input images in integer formats. For input images in float format,
+         input pixels with alpha=0 have no effect, and output pixels
+         which have alpha=0 will be 0 in all channels. (For float images,
+         you can manually achieve the same result by adding a tiny epsilon
+         value to the alpha channel of every image, and then subtracting
+         or clamping it at the end.)
+         6. You can suppress the behavior described in #5 and make
+         all-0-alpha pixels have 0 in all channels by #defining
+         STBIR_NO_ALPHA_EPSILON.
+         7. You can separately control whether the alpha channel is
+         interpreted as linear or affected by the colorspace. By default
+         it is linear; you almost never want to apply the colorspace.
+         (For example, graphics hardware does not apply sRGB conversion
+         to the alpha channel.)
+   CONTRIBUTORS
+      Jorge L Rodriguez: Implementation
+      Sean Barrett: API design, optimizations
+      Aras Pranckevicius: bugfix
+      Nathan Reed: warning fixes
+   REVISIONS
+      0.97 (2020-02-02) fixed warning
+      0.96 (2019-03-04) fixed warnings
+      0.95 (2017-07-23) fixed warnings
+      0.94 (2017-03-18) fixed warnings
+      0.93 (2017-03-03) fixed bug with certain combinations of heights
+      0.92 (2017-01-02) fix integer overflow on large (>2GB) images
+      0.91 (2016-04-02) fix warnings; fix handling of subpixel regions
+      0.90 (2014-09-17) first released version
+   LICENSE
+     See end of file for license information.
+   TODO
+      Don't decode all of the image data when only processing a partial tile
+      Don't use full-width decode buffers when only processing a partial tile
+      When processing wide images, break processing into tiles so data fits in L1 cache
+      Installable filters?
+      Resize that respects alpha test coverage
+         (Reference code: FloatImage::alphaTestCoverage and FloatImage::scaleAlphaToCoverage:
+         https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvimage/FloatImage.cpp )
+*/
+
+
+
+
+#ifndef STBIR_INCLUDE_STB_IMAGE_RESIZE_H
+#define STBIR_INCLUDE_STB_IMAGE_RESIZE_H
+
+#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
+
+#ifndef STBIRDEF
+#ifdef STB_IMAGE_RESIZE_STATIC
+#define STBIRDEF static
+#else
+#ifdef __cplusplus
+#define STBIRDEF extern "C"
+#else
+#define STBIRDEF extern
+#endif
+#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.
+
+STBIRDEF 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);
+
+STBIRDEF 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)
+
+STBIRDEF 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.
+STBIRDEF 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
+
+STBIRDEF 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);
+
+STBIRDEF 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);
+
+STBIRDEF 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;
+
+STBIRDEF 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);
+
+STBIRDEF 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);
+
+STBIRDEF 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.
+
+//
+//
+////   end header file   /////////////////////////////////////////////////////
+#endif // STBIR_INCLUDE_STB_IMAGE_RESIZE_H
+
+
+
+
+
+#ifdef STB_IMAGE_RESIZE_IMPLEMENTATION
+
+#ifndef STBIR_ASSERT
+#include <assert.h>
+#define STBIR_ASSERT(x) assert(x)
+#endif
+
+// For memset
+#include <string.h>
+
+#include <math.h>
+
+#ifndef STBIR_MALLOC
+#include <stdlib.h>
+// use comma operator to evaluate c, to avoid "unused parameter" warnings
+#define STBIR_MALLOC(size,c) ((void)(c), malloc(size))
+#define STBIR_FREE(ptr,c)    ((void)(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
+static 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_num_entries;    // Total number of entries in the ring buffer.
+    int ring_buffer_first_scanline;
+    int ring_buffer_last_scanline;
+    int ring_buffer_begin_index;    // first_scanline is at this index in the ring buffer
+    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;
+
+
+static const float stbir__max_uint8_as_float = 255.0f;
+static const float stbir__max_uint16_as_float = 65535.0f;
+static const double stbir__max_uint32_as_float = 4294967295.0;
+
+
+static stbir__inline int stbir__min(int a, int b)
+{
+    return a < b ? a : b;
+}
+
+static stbir__inline float stbir__saturate(float x)
+{
+    if (x < 0)
+        return 0;
+
+    if (x > 1)
+        return 1;
+
+    return x;
+}
+
+#ifdef STBIR_SATURATE_INT
+static stbir__inline stbir_uint8 stbir__saturate8(int x)
+{
+    if ((unsigned int)x <= 255)
+        return x;
+
+    if (x < 0)
+        return 0;
+
+    return 255;
+}
+
+static stbir__inline stbir_uint16 stbir__saturate16(int x)
+{
+    if ((unsigned int)x <= 65535)
+        return x;
+
+    if (x < 0)
+        return 0;
+
+    return 65535;
+}
+#endif
+
+static 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
+};
+
+static 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);
+}
+
+static 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;
+
+static 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,
+};
+
+static stbir_uint8 stbir__linear_to_srgb_uchar(float in)
+{
+    static const stbir__FP32 almostone = { 0x3f7fffff }; // 1-eps
+    static 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
+static 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,
+};
+
+static 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
+
+static float stbir__filter_trapezoid(float x, float scale)
+{
+    float halfscale = scale / 2;
+    float t = 0.5f + halfscale;
+    STBIR_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;
+    }
+}
+
+static float stbir__support_trapezoid(float scale)
+{
+    STBIR_ASSERT(scale <= 1);
+    return 0.5f + scale / 2;
+}
+
+static 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;
+}
+
+static 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);
+}
+
+static 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);
+}
+
+static 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);
+}
+
+static float stbir__support_zero(float s)
+{
+    STBIR__UNUSED_PARAM(s);
+    return 0;
+}
+
+static float stbir__support_one(float s)
+{
+    STBIR__UNUSED_PARAM(s);
+    return 1;
+}
+
+static float stbir__support_two(float s)
+{
+    STBIR__UNUSED_PARAM(s);
+    return 2;
+}
+
+static 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 static int stbir__use_upsampling(float ratio)
+{
+    return ratio > 1;
+}
+
+stbir__inline static int stbir__use_width_upsampling(stbir__info* stbir_info)
+{
+    return stbir__use_upsampling(stbir_info->horizontal_scale);
+}
+
+stbir__inline static 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
+static 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.
+static int stbir__get_filter_pixel_margin(stbir_filter filter, float scale)
+{
+    return stbir__get_filter_pixel_width(filter, scale) / 2;
+}
+
+static 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);
+}
+
+static 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);
+}
+
+static int stbir__get_total_horizontal_coefficients(stbir__info* info)
+{
+    return info->horizontal_num_contributors
+        * stbir__get_coefficient_width(info->horizontal_filter, info->horizontal_scale);
+}
+
+static int stbir__get_total_vertical_coefficients(stbir__info* info)
+{
+    return info->vertical_num_contributors
+        * stbir__get_coefficient_width(info->vertical_filter, info->vertical_scale);
+}
+
+static 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.
+static 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];
+}
+
+static 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);
+        }
+        // NOTREACHED
+
+    default:
+        STBIR_ASSERT(!"Unimplemented edge type");
+        return 0;
+    }
+}
+
+stbir__inline static 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?
+static 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?
+static 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));
+}
+
+static void stbir__calculate_coefficients_upsample(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_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_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_ASSERT(stbir__filter_info_table[filter].kernel((float)(in_last_pixel + 1) + 0.5f - in_center_of_out, 1 / scale) == 0);
+
+    STBIR_ASSERT(total_filter > 0.9);
+    STBIR_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;
+    }
+}
+
+static void stbir__calculate_coefficients_downsample(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_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_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_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;
+    }
+}
+
+static void stbir__normalize_downsample_coefficients(stbir__contributors* contributors, float* coefficients, stbir_filter filter, float scale_ratio, 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_ASSERT(total > 0.9f);
+        STBIR_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.
+static void stbir__calculate_filters(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(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(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(contributors, coefficients, filter, scale_ratio, input_size, output_size);
+    }
+}
+
+static 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) ((int)(type) * (STBIR_MAX_COLORSPACES) + (int)(colorspace))
+
+static 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;
+    size_t 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;
+    size_t 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]) / stbir__max_uint8_as_float;
+        }
+        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]) / stbir__max_uint8_as_float;
+        }
+        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]) / stbir__max_uint16_as_float;
+        }
+        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]) / stbir__max_uint16_as_float);
+
+            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]) / stbir__max_uint16_as_float;
+        }
+        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]) / stbir__max_uint32_as_float);
+        }
+        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]) / stbir__max_uint32_as_float));
+
+            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]) / stbir__max_uint32_as_float);
+        }
+        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_ASSERT(!"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;
+        }
+    }
+}
+
+static float* stbir__get_ring_buffer_entry(float* ring_buffer, int index, int ring_buffer_length)
+{
+    return &ring_buffer[index * ring_buffer_length];
+}
+
+static float* stbir__add_empty_ring_buffer_entry(stbir__info* stbir_info, int n)
+{
+    int ring_buffer_index;
+    float* ring_buffer;
+
+    stbir_info->ring_buffer_last_scanline = n;
+
+    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)) % stbir_info->ring_buffer_num_entries;
+        STBIR_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);
+
+    return ring_buffer;
+}
+
+
+static void stbir__resample_horizontal_upsample(stbir__info* stbir_info, float* output_buffer)
+{
+    int x, k;
+    int output_w = stbir_info->output_w;
+    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_ASSERT(n1 >= n0);
+        STBIR_ASSERT(n0 >= -stbir_info->horizontal_filter_pixel_margin);
+        STBIR_ASSERT(n1 >= -stbir_info->horizontal_filter_pixel_margin);
+        STBIR_ASSERT(n0 < stbir_info->input_w + stbir_info->horizontal_filter_pixel_margin);
+        STBIR_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_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_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_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_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_ASSERT(coefficient != 0);
+                for (c = 0; c < channels; c++)
+                    output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
+            }
+            break;
+        }
+    }
+}
+
+static void stbir__resample_horizontal_downsample(stbir__info* stbir_info, float* output_buffer)
+{
+    int x, k;
+    int input_w = stbir_info->input_w;
+    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_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_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_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_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_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_ASSERT(coefficient != 0);
+                for (c = 0; c < channels; c++)
+                    output_buffer[out_pixel_index + c] += decode_buffer[in_pixel_index + c] * coefficient;
+            }
+        }
+        break;
+    }
+}
+
+static 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, stbir__add_empty_ring_buffer_entry(stbir_info, n));
+    else
+        stbir__resample_horizontal_downsample(stbir_info, 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.
+}
+
+static 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, stbir_info->horizontal_buffer);
+    else
+        stbir__resample_horizontal_downsample(stbir_info, 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.
+static float* stbir__get_ring_buffer_scanline(int get_scanline, float* ring_buffer, int begin_index, int first_scanline, int ring_buffer_num_entries, int ring_buffer_length)
+{
+    int ring_buffer_index = (begin_index + (get_scanline - first_scanline)) % ring_buffer_num_entries;
+    return stbir__get_ring_buffer_entry(ring_buffer, ring_buffer_index, ring_buffer_length);
+}
+
+
+static 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++] = (stbir_uint16)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) * stbir__max_uint8_as_float ))
+#define STBIR__ENCODE_LINEAR16(f)  stbir__saturate16(STBIR__ROUND_INT((f) * stbir__max_uint16_as_float))
+#else
+#define STBIR__ENCODE_LINEAR8(f)   (unsigned char ) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint8_as_float )
+#define STBIR__ENCODE_LINEAR16(f)  (unsigned short) STBIR__ROUND_INT(stbir__saturate(f) * stbir__max_uint16_as_float)
+#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])) * stbir__max_uint16_as_float);
+            }
+
+            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])) * stbir__max_uint32_as_float);
+            }
+        }
+        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]))) * stbir__max_uint32_as_float);
+            }
+
+            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])) * stbir__max_uint32_as_float);
+        }
+        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_ASSERT(!"Unknown type/colorspace/channels combination.");
+        break;
+    }
+}
+
+static void stbir__resample_vertical_upsample(stbir__info* stbir_info, int n)
+{
+    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 ring_buffer_entries = stbir_info->ring_buffer_num_entries;
+    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_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_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, ring_buffer_entries, 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, ring_buffer_entries, 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, ring_buffer_entries, 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, ring_buffer_entries, 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, ring_buffer_entries, 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);
+}
+
+static void stbir__resample_vertical_downsample(stbir__info* stbir_info, int n)
+{
+    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 ring_buffer_entries = stbir_info->ring_buffer_num_entries;
+    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_length = stbir_info->ring_buffer_length_bytes / sizeof(float);
+    int n0, n1;
+
+    n0 = vertical_contributors[contributor].n0;
+    n1 = vertical_contributors[contributor].n1;
+
+    STBIR_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, ring_buffer_entries, 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;
+        }
+    }
+}
+
+static 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_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_ASSERT(in_last_scanline - in_first_scanline + 1 <= stbir_info->ring_buffer_num_entries);
+
+        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->ring_buffer_num_entries;
+                }
+            }
+        }
+
+        // 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);
+
+        STBIR_PROGRESS_REPORT((float)y / stbir_info->output_h);
+    }
+}
+
+static 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->ring_buffer_num_entries;
+            }
+        }
+    }
+}
+
+static 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_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_ASSERT(out_last_scanline - out_first_scanline + 1 <= stbir_info->ring_buffer_num_entries);
+
+        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);
+    }
+
+    stbir__empty_ring_buffer(stbir_info, stbir_info->output_h);
+}
+
+static 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;
+}
+
+static 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->output_w / (s1 - s0);
+        info->vertical_shift = t0 * info->output_h / (t1 - t0);
+    }
+}
+
+static 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;
+}
+
+static 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);
+
+    // One extra entry because floating point precision problems sometimes cause an extra to be necessary.
+    info->ring_buffer_num_entries = filter_height + 1;
+
+    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 * info->ring_buffer_num_entries * 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;
+}
+
+static 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_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_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->horizontal_contributors, info->horizontal_coefficients, info->horizontal_filter, info->horizontal_scale, info->horizontal_shift, info->input_w, info->output_w);
+    stbir__calculate_filters(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_ASSERT(memcmp(overwrite_output_before_pre, &((unsigned char*)output_data)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
+    STBIR_ASSERT(memcmp(overwrite_output_after_pre, &((unsigned char*)output_data)[begin_forbidden], OVERWRITE_ARRAY_SIZE) == 0);
+    STBIR_ASSERT(memcmp(overwrite_tempmem_before_pre, &((unsigned char*)tempmem)[-OVERWRITE_ARRAY_SIZE], OVERWRITE_ARRAY_SIZE) == 0);
+    STBIR_ASSERT(memcmp(overwrite_tempmem_after_pre, &((unsigned char*)tempmem)[tempmem_size_in_bytes], OVERWRITE_ARRAY_SIZE) == 0);
+#endif
+
+    return 1;
+}
+
+
+static 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;
+}
+
+STBIRDEF 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);
+}
+
+STBIRDEF 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);
+}
+
+STBIRDEF 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);
+}
+
+STBIRDEF 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);
+}
+
+STBIRDEF 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);
+}
+
+STBIRDEF 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);
+}
+
+
+STBIRDEF 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);
+}
+
+
+STBIRDEF 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);
+}
+
+
+STBIRDEF 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);
+}
+
+STBIRDEF 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);
+}
+
+#endif // STB_IMAGE_RESIZE_IMPLEMENTATION
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+*/

+ 1671 - 0
code/lib/jomjol_image_proc/stb_image_write.h

@@ -0,0 +1,1671 @@
+/* stb_image_write - v1.14 - public domain - http://nothings.org/stb
+   writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015
+                                     no warranty implied; use at your own risk
+
+   Before #including,
+
+       #define STB_IMAGE_WRITE_IMPLEMENTATION
+
+   in the file that you want to have the implementation.
+
+   Will probably not work correctly with strict-aliasing optimizations.
+
+ABOUT:
+
+   This header file is a library for writing images to C stdio or a callback.
+
+   The PNG output is not optimal; it is 20-50% larger than the file
+   written by a decent optimizing implementation; though providing a custom
+   zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that.
+   This library is designed for source code compactness and simplicity,
+   not optimal image file size or run-time performance.
+
+BUILDING:
+
+   You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h.
+   You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace
+   malloc,realloc,free.
+   You can #define STBIW_MEMMOVE() to replace memmove()
+   You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function
+   for PNG compression (instead of the builtin one), it must have the following signature:
+   unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality);
+   The returned data will be freed with STBIW_FREE() (free() by default),
+   so it must be heap allocated with STBIW_MALLOC() (malloc() by default),
+
+UNICODE:
+
+   If compiling for Windows and you wish to use Unicode filenames, compile
+   with
+       #define STBIW_WINDOWS_UTF8
+   and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert
+   Windows wchar_t filenames to utf8.
+
+USAGE:
+
+   There are five functions, one for each image file format:
+
+     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_jpg(char const *filename, int w, int h, int comp, const void *data, int quality);
+     int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
+
+     void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically
+
+   There are also five equivalent functions that use an arbitrary write function. You are
+   expected to open/close your file-equivalent before and after calling these:
+
+     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);
+     int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality);
+
+   where the callback is:
+      void stbi_write_func(void *context, void *data, int size);
+
+   You can configure it with these global variables:
+      int stbi_write_tga_with_rle;             // defaults to true; set to 0 to disable RLE
+      int stbi_write_png_compression_level;    // defaults to 8; set to higher for more compression
+      int stbi_write_force_png_filter;         // defaults to -1; set to 0..5 to force a filter mode
+
+
+   You can define STBI_WRITE_NO_STDIO to disable the file variant of these
+   functions, so the library will not use stdio.h at all. However, this will
+   also disable HDR writing, because it requires stdio for formatted output.
+
+   Each function returns 0 on failure and non-0 on success.
+
+   The functions create an image file defined by the parameters. The image
+   is a rectangle of pixels stored from left-to-right, top-to-bottom.
+   Each pixel contains 'comp' channels of data stored interleaved with 8-bits
+   per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is
+   monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall.
+   The *data pointer points to the first byte of the top-left-most pixel.
+   For PNG, "stride_in_bytes" is the distance in bytes from the first byte of
+   a row of pixels to the first byte of the next row of pixels.
+
+   PNG creates output files with the same number of components as the input.
+   The BMP format expands Y to RGB in the file format and does not
+   output alpha.
+
+   PNG supports writing rectangles of data even when the bytes storing rows of
+   data are not consecutive in memory (e.g. sub-rectangles of a larger image),
+   by supplying the stride between the beginning of adjacent rows. The other
+   formats do not. (Thus you cannot write a native-format BMP through the BMP
+   writer, both because it is in BGR order and because it may have padding
+   at the end of the line.)
+
+   PNG allows you to set the deflate compression level by setting the global
+   variable 'stbi_write_png_compression_level' (it defaults to 8).
+
+   HDR expects linear float data. Since the format is always 32-bit rgb(e)
+   data, alpha (if provided) is discarded, and for monochrome data it is
+   replicated across all three channels.
+
+   TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed
+   data, set the global variable 'stbi_write_tga_with_rle' to 0.
+
+   JPEG does ignore alpha channels in input data; quality is between 1 and 100.
+   Higher quality looks better but results in a bigger image.
+   JPEG baseline (no JPEG progressive).
+
+CREDITS:
+
+
+   Sean Barrett           -    PNG/BMP/TGA
+   Baldur Karlsson        -    HDR
+   Jean-Sebastien Guay    -    TGA monochrome
+   Tim Kelsey             -    misc enhancements
+   Alan Hickman           -    TGA RLE
+   Emmanuel Julien        -    initial file IO callback implementation
+   Jon Olick              -    original jo_jpeg.cpp code
+   Daniel Gibson          -    integrate JPEG, allow external zlib
+   Aarni Koskela          -    allow choosing PNG filter
+
+   bugfixes:
+      github:Chribba
+      Guillaume Chereau
+      github:jry2
+      github:romigrou
+      Sergio Gonzalez
+      Jonas Karlsson
+      Filip Wasil
+      Thatcher Ulrich
+      github:poppolopoppo
+      Patrick Boettcher
+      github:xeekworx
+      Cap Petschulat
+      Simon Rodriguez
+      Ivan Tikhonov
+      github:ignotion
+      Adam Schackart
+
+LICENSE
+
+  See end of file for license information.
+
+*/
+
+
+#ifndef INCLUDE_STB_IMAGE_WRITE_H
+#define INCLUDE_STB_IMAGE_WRITE_H
+
+#include <stdlib.h>
+
+// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline'
+#ifndef STBIWDEF
+#ifdef STB_IMAGE_WRITE_STATIC
+#define STBIWDEF  static
+#else
+#ifdef __cplusplus
+#define STBIWDEF  extern "C"
+#else
+#define STBIWDEF  extern
+#endif
+#endif
+#endif
+
+#ifndef STB_IMAGE_WRITE_STATIC  // C++ forbids static forward declarations
+extern int stbi_write_tga_with_rle;
+extern int stbi_write_png_compression_level;
+extern int stbi_write_force_png_filter;
+#endif
+
+#ifndef STBI_WRITE_NO_STDIO
+STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void  *data, int stride_in_bytes);
+STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void  *data);
+STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void  *data);
+STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data);
+STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void  *data, int quality);
+
+#ifdef STBI_WINDOWS_UTF8
+STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input);
+#endif
+#endif
+
+typedef void stbi_write_func(void *context, void *data, int size);
+
+STBIWDEF 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);
+STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data);
+STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void  *data);
+STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data);
+STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void  *data, int quality);
+
+STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean);
+
+#endif//INCLUDE_STB_IMAGE_WRITE_H
+
+#ifdef STB_IMAGE_WRITE_IMPLEMENTATION
+
+#ifdef _WIN32
+   #ifndef _CRT_SECURE_NO_WARNINGS
+   #define _CRT_SECURE_NO_WARNINGS
+   #endif
+   #ifndef _CRT_NONSTDC_NO_DEPRECATE
+   #define _CRT_NONSTDC_NO_DEPRECATE
+   #endif
+#endif
+
+#ifndef STBI_WRITE_NO_STDIO
+#include <stdio.h>
+#endif // STBI_WRITE_NO_STDIO
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED))
+// ok
+#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED)
+// ok
+#else
+#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)."
+#endif
+
+#ifndef STBIW_MALLOC
+#define STBIW_MALLOC(sz)        malloc(sz)
+#define STBIW_REALLOC(p,newsz)  realloc(p,newsz)
+#define STBIW_FREE(p)           free(p)
+#endif
+
+#ifndef STBIW_REALLOC_SIZED
+#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz)
+#endif
+
+
+#ifndef STBIW_MEMMOVE
+#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz)
+#endif
+
+
+#ifndef STBIW_ASSERT
+#include <assert.h>
+#define STBIW_ASSERT(x) assert(x)
+#endif
+
+#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff)
+
+#ifdef STB_IMAGE_WRITE_STATIC
+static int stbi_write_png_compression_level = 8;
+static int stbi_write_tga_with_rle = 1;
+static int stbi_write_force_png_filter = -1;
+#else
+int stbi_write_png_compression_level = 8;
+int stbi_write_tga_with_rle = 1;
+int stbi_write_force_png_filter = -1;
+#endif
+
+static int stbi__flip_vertically_on_write = 0;
+
+STBIWDEF void stbi_flip_vertically_on_write(int flag)
+{
+   stbi__flip_vertically_on_write = flag;
+}
+
+typedef struct
+{
+   stbi_write_func *func;
+   void *context;
+} stbi__write_context;
+
+// initialize a callback-based context
+static 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
+
+static void stbi__stdio_write(void *context, void *data, int size)
+{
+   fwrite(data,1,size,(FILE*) context);
+}
+
+#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)
+#ifdef __cplusplus
+#define STBIW_EXTERN extern "C"
+#else
+#define STBIW_EXTERN extern
+#endif
+STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide);
+STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default);
+
+STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input)
+{
+	return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL);
+}
+#endif
+
+static FILE *stbiw__fopen(char const *filename, char const *mode)
+{
+   FILE *f;
+#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)
+   wchar_t wMode[64];
+   wchar_t wFilename[1024];
+	if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)))
+      return 0;
+
+	if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)))
+      return 0;
+
+#if _MSC_VER >= 1400
+	if (0 != _wfopen_s(&f, wFilename, wMode))
+		f = 0;
+#else
+   f = _wfopen(wFilename, wMode);
+#endif
+
+#elif defined(_MSC_VER) && _MSC_VER >= 1400
+   if (0 != fopen_s(&f, filename, mode))
+      f=0;
+#else
+   f = fopen(filename, mode);
+#endif
+   return f;
+}
+
+static int stbi__start_write_file(stbi__write_context *s, const char *filename)
+{
+   FILE *f = stbiw__fopen(filename, "wb");
+   stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f);
+   return f != NULL;
+}
+
+static void stbi__end_write_file(stbi__write_context *s)
+{
+//   fflush((FILE *)s->context);
+//   fsync((FILE *)s->context);
+   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];
+
+static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v)
+{
+   while (*fmt) {
+      switch (*fmt++) {
+         case ' ': break;
+         case '1': { unsigned char x = STBIW_UCHAR(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] = STBIW_UCHAR(x);
+                     b[1] = STBIW_UCHAR(x>>8);
+                     s->func(s->context,b,2);
+                     break; }
+         case '4': { stbiw_uint32 x = va_arg(v,int);
+                     unsigned char b[4];
+                     b[0]=STBIW_UCHAR(x);
+                     b[1]=STBIW_UCHAR(x>>8);
+                     b[2]=STBIW_UCHAR(x>>16);
+                     b[3]=STBIW_UCHAR(x>>24);
+                     s->func(s->context,b,4);
+                     break; }
+         default:
+            STBIW_ASSERT(0);
+            return;
+      }
+   }
+}
+
+static 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);
+}
+
+static void stbiw__putc(stbi__write_context *s, unsigned char c)
+{
+   s->func(s->context, &c, 1);
+}
+
+static 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);
+}
+
+static 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 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case
+      case 1:
+         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);
+}
+
+static 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 (stbi__flip_vertically_on_write)
+      vdir *= -1;
+
+   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);
+   }
+}
+
+static 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;
+   }
+}
+
+static 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
+}
+
+STBIWDEF 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
+STBIWDEF 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
+
+static 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;
+      int jend, jdir;
+
+      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);
+
+      if (stbi__flip_vertically_on_write) {
+         j = 0;
+         jend = y;
+         jdir = 1;
+      } else {
+         j = y-1;
+         jend = -1;
+         jdir = -1;
+      }
+      for (; j != jend; j += jdir) {
+         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 = STBIW_UCHAR(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 = STBIW_UCHAR(len - 129);
+               s->func(s->context, &header, 1);
+               stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin);
+            }
+         }
+      }
+   }
+   return 1;
+}
+
+STBIWDEF 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
+STBIWDEF 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
+
+#define stbiw__max(a, b)  ((a) > (b) ? (a) : (b))
+
+static 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-32f) {
+      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);
+   }
+}
+
+static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte)
+{
+   unsigned char lengthbyte = STBIW_UCHAR(length+128);
+   STBIW_ASSERT(length+128 <= 255);
+   s->func(s->context, &lengthbyte, 1);
+   s->func(s->context, &databyte, 1);
+}
+
+static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data)
+{
+   unsigned char lengthbyte = STBIW_UCHAR(length);
+   STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code
+   s->func(s->context, &lengthbyte, 1);
+   s->func(s->context, data, length);
+}
+
+static 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;
+               }
+            }
+         }
+      }
+   }
+}
+
+static 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);
+
+#ifdef __STDC_WANT_SECURE_LIB__
+      len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE=          1.0000000000000\n\n-Y %d +X %d\n", y, x);
+#else
+      len = sprintf(buffer, "EXPOSURE=          1.0000000000000\n\n-Y %d +X %d\n", y, x);
+#endif
+      s->func(s->context, buffer, len);
+
+      for(i=0; i < y; i++)
+         stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i));
+      STBIW_FREE(scratch);
+      return 1;
+   }
+}
+
+STBIWDEF 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);
+}
+
+#ifndef STBI_WRITE_NO_STDIO
+STBIWDEF 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
+//
+
+#ifndef STBIW_ZLIB_COMPRESS
+// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size()
+#define stbiw__sbraw(a) ((int *) (void *) (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)
+
+static void *stbiw__sbgrowf(void **arr, int increment, int itemsize)
+{
+   int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1;
+   void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 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;
+}
+
+static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount)
+{
+   while (*bitcount >= 8) {
+      stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer));
+      *bitbuffer >>= 8;
+      *bitcount -= 8;
+   }
+   return data;
+}
+
+static int stbiw__zlib_bitrev(int code, int codebits)
+{
+   int res=0;
+   while (codebits--) {
+      res = (res << 1) | (code & 1);
+      code >>= 1;
+   }
+   return res;
+}
+
+static 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;
+}
+
+static 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
+
+#endif // STBIW_ZLIB_COMPRESS
+
+STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality)
+{
+#ifdef STBIW_ZLIB_COMPRESS
+   // user provided a zlib compress implementation, use that
+   return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality);
+#else // use builtin
+   static 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 };
+   static 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 };
+   static 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 };
+   static 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 = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**));
+   if (hash_table == NULL)
+      return NULL;
+   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]);
+   STBIW_FREE(hash_table);
+
+   {
+      // compute adler32 on input
+      unsigned int 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, STBIW_UCHAR(s2 >> 8));
+      stbiw__sbpush(out, STBIW_UCHAR(s2));
+      stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8));
+      stbiw__sbpush(out, STBIW_UCHAR(s1));
+   }
+   *out_len = stbiw__sbn(out);
+   // make returned pointer freeable
+   STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len);
+   return (unsigned char *) stbiw__sbraw(out);
+#endif // STBIW_ZLIB_COMPRESS
+}
+
+static unsigned int stbiw__crc32(unsigned char *buffer, int len)
+{
+#ifdef STBIW_CRC32
+    return STBIW_CRC32(buffer, len);
+#else
+   static unsigned int crc_table[256] =
+   {
+      0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
+      0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
+      0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
+      0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
+      0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
+      0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
+      0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
+      0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
+      0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
+      0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
+      0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
+      0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
+      0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
+      0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
+      0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
+      0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
+      0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
+      0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
+      0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
+      0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
+      0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
+      0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
+      0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
+      0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
+      0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
+      0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
+      0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
+      0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
+      0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
+      0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
+      0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
+      0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
+   };
+
+   unsigned int crc = ~0u;
+   int i;
+   for (i=0; i < len; ++i)
+      crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)];
+   return ~crc;
+#endif
+}
+
+#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(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])
+
+static void stbiw__wpcrc(unsigned char **data, int len)
+{
+   unsigned int crc = stbiw__crc32(*data - len - 4, len+4);
+   stbiw__wp32(*data, crc);
+}
+
+static 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 STBIW_UCHAR(a);
+   if (pb <= pc) return STBIW_UCHAR(b);
+   return STBIW_UCHAR(c);
+}
+
+// @OPTIMIZE: provide an option that always forces left-predict or paeth predict
+static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer)
+{
+   static int mapping[] = { 0,1,2,3,4 };
+   static int firstmap[] = { 0,1,0,5,6 };
+   int *mymap = (y != 0) ? mapping : firstmap;
+   int i;
+   int type = mymap[filter_type];
+   unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y);
+   int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes;
+
+   if (type==0) {
+      memcpy(line_buffer, z, width*n);
+      return;
+   }
+
+   // first loop isn't optimized since it's just one pixel
+   for (i = 0; i < n; ++i) {
+      switch (type) {
+         case 1: line_buffer[i] = z[i]; break;
+         case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break;
+         case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break;
+         case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break;
+         case 5: line_buffer[i] = z[i]; break;
+         case 6: line_buffer[i] = z[i]; break;
+      }
+   }
+   switch (type) {
+      case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break;
+      case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break;
+      case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break;
+      case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break;
+      case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break;
+      case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break;
+   }
+}
+
+STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len)
+{
+   int force_filter = stbi_write_force_png_filter;
+   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 j,zlen;
+
+   if (stride_bytes == 0)
+      stride_bytes = x * n;
+
+   if (force_filter >= 5) {
+      force_filter = -1;
+   }
+
+   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 filter_type;
+      if (force_filter > -1) {
+         filter_type = force_filter;
+         stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer);
+      } else { // Estimate the best filter by running through all of them:
+         int best_filter = 0, best_filter_val = 0x7fffffff, est, i;
+         for (filter_type = 0; filter_type < 5; filter_type++) {
+            stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer);
+
+            // Estimate the entropy of the line using this filter; the less, the better.
+            est = 0;
+            for (i = 0; i < x*n; ++i) {
+               est += abs((signed char) line_buffer[i]);
+            }
+            if (est < best_filter_val) {
+               best_filter_val = est;
+               best_filter = filter_type;
+            }
+         }
+         if (filter_type != best_filter) {  // If the last iteration already got us the best filter, don't redo it
+            stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer);
+            filter_type = best_filter;
+         }
+      }
+      // when we get here, filter_type contains the filter type, and line_buffer contains the data
+      filt[j*(x*n+1)] = (unsigned char) filter_type;
+      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, stbi_write_png_compression_level);
+   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++ = STBIW_UCHAR(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
+STBIWDEF 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((const unsigned char *) data, stride_bytes, x, y, comp, &len);
+   if (png == NULL) return 0;
+
+   f = stbiw__fopen(filename, "wb");
+   if (!f) { STBIW_FREE(png); return 0; }
+   fwrite(png, 1, len, f);
+   fclose(f);
+   STBIW_FREE(png);
+   return 1;
+}
+#endif
+
+STBIWDEF 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((const unsigned char *) data, stride_bytes, x, y, comp, &len);
+   if (png == NULL) return 0;
+   func(context, png, len);
+   STBIW_FREE(png);
+   return 1;
+}
+
+
+/* ***************************************************************************
+ *
+ * JPEG writer
+ *
+ * This is based on Jon Olick's jo_jpeg.cpp:
+ * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html
+ */
+
+static const unsigned char stbiw__jpg_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 };
+
+static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) {
+   int bitBuf = *bitBufP, bitCnt = *bitCntP;
+   bitCnt += bs[1];
+   bitBuf |= bs[0] << (24 - bitCnt);
+   while(bitCnt >= 8) {
+      unsigned char c = (bitBuf >> 16) & 255;
+      stbiw__putc(s, c);
+      if(c == 255) {
+         stbiw__putc(s, 0);
+      }
+      bitBuf <<= 8;
+      bitCnt -= 8;
+   }
+   *bitBufP = bitBuf;
+   *bitCntP = bitCnt;
+}
+
+static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) {
+   float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p;
+   float z1, z2, z3, z4, z5, z11, z13;
+
+   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;
+
+   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.
+   z5 = (tmp10 - tmp12) * 0.382683433f; // c6
+   z2 = tmp10 * 0.541196100f + z5; // c2-c6
+   z4 = tmp12 * 1.306562965f + z5; // c2+c6
+   z3 = tmp11 * 0.707106781f; // c4
+
+   z11 = tmp7 + z3;      // phase 5
+   z13 = tmp7 - z3;
+
+   *d5p = z13 + z2;         // phase 6
+   *d3p = z13 - z2;
+   *d1p = z11 + z4;
+   *d7p = z11 - z4;
+
+   *d0p = d0;  *d2p = d2;  *d4p = d4;  *d6p = d6;
+}
+
+static void stbiw__jpg_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);
+}
+
+static int stbiw__jpg_processDU(stbi__write_context *s, int *bitBuf, int *bitCnt, float *CDU, int du_stride, 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] };
+   int dataOff, i, j, n, diff, end0pos, x, y;
+   int DU[64];
+
+   // DCT rows
+   for(dataOff=0, n=du_stride*8; dataOff<n; dataOff+=du_stride) {
+      stbiw__jpg_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(dataOff=0; dataOff<8; ++dataOff) {
+      stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff+du_stride], &CDU[dataOff+du_stride*2], &CDU[dataOff+du_stride*3], &CDU[dataOff+du_stride*4],
+                     &CDU[dataOff+du_stride*5], &CDU[dataOff+du_stride*6], &CDU[dataOff+du_stride*7]);
+   }
+   // Quantize/descale/zigzag the coefficients
+   for(y = 0, j=0; y < 8; ++y) {
+      for(x = 0; x < 8; ++x,++j) {
+         float v;
+         i = y*du_stride+x;
+         v = CDU[i]*fdtbl[j];
+         // DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f));
+         // ceilf() and floorf() are C99, not C89, but I /think/ they're not needed here anyway?
+         DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? v - 0.5f : v + 0.5f);
+      }
+   }
+
+   // Encode DC
+   diff = DU[0] - DC;
+   if (diff == 0) {
+      stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[0]);
+   } else {
+      unsigned short bits[2];
+      stbiw__jpg_calcBits(diff, bits);
+      stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[bits[1]]);
+      stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits);
+   }
+   // Encode ACs
+   end0pos = 63;
+   for(; (end0pos>0)&&(DU[end0pos]==0); --end0pos) {
+   }
+   // end0pos = first element in reverse order !=0
+   if(end0pos == 0) {
+      stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB);
+      return DU[0];
+   }
+   for(i = 1; i <= end0pos; ++i) {
+      int startpos = i;
+      int nrzeroes;
+      unsigned short bits[2];
+      for (; DU[i]==0 && i<=end0pos; ++i) {
+      }
+      nrzeroes = i-startpos;
+      if ( nrzeroes >= 16 ) {
+         int lng = nrzeroes>>4;
+         int nrmarker;
+         for (nrmarker=1; nrmarker <= lng; ++nrmarker)
+            stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes);
+         nrzeroes &= 15;
+      }
+      stbiw__jpg_calcBits(DU[i], bits);
+      stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]);
+      stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits);
+   }
+   if(end0pos != 63) {
+      stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB);
+   }
+   return DU[0];
+}
+
+static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) {
+   // Constants that don't pollute global namespace
+   static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0};
+   static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11};
+   static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d};
+   static 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
+   };
+   static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0};
+   static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11};
+   static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77};
+   static 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
+   static 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}};
+   static 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}};
+   static 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}
+   };
+   static 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}
+   };
+   static 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};
+   static 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};
+   static 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 };
+
+   int row, col, i, k, subsample;
+   float fdtbl_Y[64], fdtbl_UV[64];
+   unsigned char YTable[64], UVTable[64];
+
+   if(!data || !width || !height || comp > 4 || comp < 1) {
+      return 0;
+   }
+
+   quality = quality ? quality : 90;
+   subsample = quality <= 90 ? 1 : 0;
+   quality = quality < 1 ? 1 : quality > 100 ? 100 : quality;
+   quality = quality < 50 ? 5000 / quality : 200 - quality * 2;
+
+   for(i = 0; i < 64; ++i) {
+      int uvti, yti = (YQT[i]*quality+50)/100;
+      YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti);
+      uvti = (UVQT[i]*quality+50)/100;
+      UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti);
+   }
+
+   for(row = 0, k = 0; row < 8; ++row) {
+      for(col = 0; col < 8; ++col, ++k) {
+         fdtbl_Y[k]  = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]);
+         fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]);
+      }
+   }
+
+   // Write Headers
+   {
+      static 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 };
+      static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 };
+      const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width),
+                                      3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 };
+      s->func(s->context, (void*)head0, sizeof(head0));
+      s->func(s->context, (void*)YTable, sizeof(YTable));
+      stbiw__putc(s, 1);
+      s->func(s->context, UVTable, sizeof(UVTable));
+      s->func(s->context, (void*)head1, sizeof(head1));
+      s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1);
+      s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values));
+      stbiw__putc(s, 0x10); // HTYACinfo
+      s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1);
+      s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values));
+      stbiw__putc(s, 1); // HTUDCinfo
+      s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1);
+      s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values));
+      stbiw__putc(s, 0x11); // HTUACinfo
+      s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1);
+      s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values));
+      s->func(s->context, (void*)head2, sizeof(head2));
+   }
+
+   // Encode 8x8 macroblocks
+   {
+      static const unsigned short fillBits[] = {0x7F, 7};
+      int DCY=0, DCU=0, DCV=0;
+      int bitBuf=0, bitCnt=0;
+      // comp == 2 is grey+alpha (alpha is ignored)
+      int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0;
+      const unsigned char *dataR = (const unsigned char *)data;
+      const unsigned char *dataG = dataR + ofsG;
+      const unsigned char *dataB = dataR + ofsB;
+      int x, y, pos;
+      if(subsample) {
+         for(y = 0; y < height; y += 16) {
+            for(x = 0; x < width; x += 16) {
+               float Y[256], U[256], V[256];
+               for(row = y, pos = 0; row < y+16; ++row) {
+                  // row >= height => use last input row
+                  int clamped_row = (row < height) ? row : height - 1;
+                  int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
+                  for(col = x; col < x+16; ++col, ++pos) {
+                     // if col >= width => use pixel from last input column
+                     int p = base_p + ((col < width) ? col : (width-1))*comp;
+                     float r = dataR[p], g = dataG[p], b = dataB[p];
+                     Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128;
+                     U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b;
+                     V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b;
+                  }
+               }
+               DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0,   16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
+               DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8,   16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
+               DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
+               DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT);
+
+               // subsample U,V
+               {
+                  float subU[64], subV[64];
+                  int yy, xx;
+                  for(yy = 0, pos = 0; yy < 8; ++yy) {
+                     for(xx = 0; xx < 8; ++xx, ++pos) {
+                        int j = yy*32+xx*2;
+                        subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f;
+                        subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f;
+                     }
+                  }
+                  DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
+                  DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
+               }
+            }
+         }
+      } else {
+         for(y = 0; y < height; y += 8) {
+            for(x = 0; x < width; x += 8) {
+               float Y[64], U[64], V[64];
+               for(row = y, pos = 0; row < y+8; ++row) {
+                  // row >= height => use last input row
+                  int clamped_row = (row < height) ? row : height - 1;
+                  int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp;
+                  for(col = x; col < x+8; ++col, ++pos) {
+                     // if col >= width => use pixel from last input column
+                     int p = base_p + ((col < width) ? col : (width-1))*comp;
+                     float r = dataR[p], g = dataG[p], b = dataB[p];
+                     Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128;
+                     U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b;
+                     V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b;
+                  }
+               }
+
+               DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y,  DCY, YDC_HT, YAC_HT);
+               DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
+               DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
+            }
+         }
+      }
+
+      // Do the bit alignment of the EOI marker
+      stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits);
+   }
+
+   // EOI
+   stbiw__putc(s, 0xFF);
+   stbiw__putc(s, 0xD9);
+
+   return 1;
+}
+
+STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality)
+{
+   stbi__write_context s;
+   stbi__start_write_callbacks(&s, func, context);
+   return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality);
+}
+
+
+#ifndef STBI_WRITE_NO_STDIO
+STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality)
+{
+   stbi__write_context s;
+   if (stbi__start_write_file(&s,filename)) {
+//      printf("start writing jpg-core\n");
+      int r = stbi_write_jpg_core(&s, x, y, comp, data, quality);
+//      printf("start writing ending file\n");
+      stbi__end_write_file(&s);
+      return r;
+   } else
+      return 0;
+}
+#endif
+
+#endif // STB_IMAGE_WRITE_IMPLEMENTATION
+
+/* Revision history
+      1.14  (2020-02-02) updated JPEG writer to downsample chroma channels
+      1.13
+      1.12
+      1.11  (2019-08-11)
+
+      1.10  (2019-02-07)
+             support utf8 filenames in Windows; fix warnings and platform ifdefs
+      1.09  (2018-02-11)
+             fix typo in zlib quality API, improve STB_I_W_STATIC in C++
+      1.08  (2018-01-29)
+             add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter
+      1.07  (2017-07-24)
+             doc fix
+      1.06 (2017-07-23)
+             writing JPEG (using Jon Olick's code)
+      1.05   ???
+      1.04 (2017-03-03)
+             monochrome BMP expansion
+      1.03   ???
+      1.02 (2016-04-02)
+             avoid allocating large structures on the stack
+      1.01 (2016-01-16)
+             STBIW_REALLOC_SIZED: support allocators with no realloc support
+             avoid race-condition in crc initialization
+             minor compile issues
+      1.00 (2015-09-14)
+             installable file IO function
+      0.99 (2015-09-13)
+             warning fixes; TGA rle support
+      0.98 (2015-04-08)
+             added STBIW_MALLOC, STBIW_ASSERT etc
+      0.97 (2015-01-18)
+             fixed HDR asserts, rewrote HDR rle logic
+      0.96 (2015-01-17)
+             add HDR output
+             fix monochrome BMP
+      0.95 (2014-08-17)
+		       add monochrome TGA output
+      0.94 (2014-05-31)
+             rename private functions to avoid conflicts with stb_image.h
+      0.93 (2014-05-27)
+             warning fixes
+      0.92 (2010-08-01)
+             casts to unsigned char to fix warnings
+      0.91 (2010-07-17)
+             first public release
+      0.90   first internal release
+*/
+
+/*
+------------------------------------------------------------------------------
+This software is available under 2 licenses -- choose whichever you prefer.
+------------------------------------------------------------------------------
+ALTERNATIVE A - MIT License
+Copyright (c) 2017 Sean Barrett
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+------------------------------------------------------------------------------
+ALTERNATIVE B - Public Domain (www.unlicense.org)
+This is free and unencumbered software released into the public domain.
+Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+software, either in source code form or as a compiled binary, for any purpose,
+commercial or non-commercial, and by any means.
+In jurisdictions that recognize copyright laws, the author or authors of this
+software dedicate any and all copyright interest in the software to the public
+domain. We make this dedication for the benefit of the public at large and to
+the detriment of our heirs and successors. We intend this dedication to be an
+overt act of relinquishment in perpetuity of all present and future rights to
+this software under copyright law.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+------------------------------------------------------------------------------
+*/

+ 41 - 0
code/lib/jomjol_logfile/ClassLogFile.cpp

@@ -0,0 +1,41 @@
+#include "ClassLogFile.h"
+#include "time_sntp.h"
+
+ClassLogFile LogFile("/sdcard/log.txt");
+
+void ClassLogFile::WriteToFile(std::string info, bool _time)
+{
+    FILE* pFile;
+    std::string zwtime;
+
+
+    pFile = fopen(logfile.c_str(), "a+");
+
+    if (_time)
+    {
+        time_t rawtime;
+        struct tm* timeinfo;
+        char buffer[80];
+
+        time(&rawtime);
+        timeinfo = localtime(&rawtime);
+
+        strftime(buffer, 80, "%Y-%m-%d_%H-%M-%S", timeinfo);
+
+        zwtime = std::string(buffer);
+        info = zwtime + ": " + info;
+    }
+    fputs(info.c_str(), pFile);
+    fputs("\n", pFile);
+
+    fclose(pFile);    
+}
+
+ClassLogFile::ClassLogFile(std::string _logfile)
+{
+    logfile = _logfile;
+}
+
+ClassLogFile::~ClassLogFile()
+{
+}

+ 17 - 0
code/lib/jomjol_logfile/ClassLogFile.h

@@ -0,0 +1,17 @@
+
+#include <string>
+
+
+class ClassLogFile
+{
+private:
+    /* data */
+    std::string logfile;
+public:
+    ClassLogFile(std::string _logfile);
+    ~ClassLogFile();
+
+    void WriteToFile(std::string info, bool _time = true);
+};
+
+extern ClassLogFile LogFile;

+ 290 - 0
code/lib/jomjol_tfliteclass/CTfLiteClass.cpp

@@ -0,0 +1,290 @@
+#include "CTfLiteClass.h"
+
+#include "bitmap_image.hpp"
+
+#include <sys/stat.h>
+
+float CTfLiteClass::GetOutputValue(int nr)
+{
+    TfLiteTensor* output2 = this->interpreter->output(0);
+
+    int numeroutput = output2->dims->data[1];
+    if ((nr+1) > numeroutput)
+      return -1000;
+
+    return output2->data.f[nr];
+}
+
+
+int CTfLiteClass::GetClassFromImage(std::string _fn)
+{
+    if (!LoadInputImage(_fn))
+      return -1000;
+
+    Invoke();
+    return GetOutClassification();
+//    return 0;
+}
+
+int CTfLiteClass::GetOutClassification()
+{
+  TfLiteTensor* output2 = interpreter->output(0);
+
+  float zw_max = 0;
+  float zw;
+  int zw_class = -1;
+
+  if (output2 == NULL)
+    return -1;
+
+  int numeroutput = output2->dims->data[1];
+  for (int i = 0; i < numeroutput; ++i)
+  {
+    zw = output2->data.f[i];
+    if (zw > zw_max)
+    {
+        zw_max = zw;
+        zw_class = i;
+    }
+  }
+//  printf("Result Ziffer: %d\n", zw_class);       
+  return zw_class;
+}
+
+void CTfLiteClass::GetInputDimension(bool silent = false)
+{
+  TfLiteTensor* input2 = this->interpreter->input(0);
+
+  int numdim = input2->dims->size;
+  if (!silent)  printf("NumDimension: %d\n", numdim);  
+
+  int sizeofdim;
+  for (int j = 0; j < numdim; ++j)
+  {
+    sizeofdim = input2->dims->data[j];
+    if (!silent) printf("SizeOfDimension %d: %d\n", j, sizeofdim);  
+    if (j == 1) im_height = sizeofdim;
+    if (j == 2) im_width = sizeofdim;
+    if (j == 3) im_channel = sizeofdim;
+  }
+}
+
+
+void CTfLiteClass::GetOutPut()
+{
+  TfLiteTensor* output2 = this->interpreter->output(0);
+
+  int numdim = output2->dims->size;
+  printf("NumDimension: %d\n", numdim);  
+
+  int sizeofdim;
+  for (int j = 0; j < numdim; ++j)
+  {
+    sizeofdim = output2->dims->data[j];
+    printf("SizeOfDimension %d: %d\n", j, sizeofdim);  
+  }
+
+
+  float fo;
+
+  // Process the inference results.
+  int numeroutput = output2->dims->data[1];
+  for (int i = 0; i < numeroutput; ++i)
+  {
+   fo = output2->data.f[i];
+    printf("Result %d: %f\n", i, fo);  
+  }
+}
+
+void CTfLiteClass::Invoke()
+{
+    interpreter->Invoke();
+//    printf("Invoke Done.\n");
+}
+
+/*   mit CImageBasis --> funktioniert nicht, keine Ahnung warum !!!
+
+bool CTfLiteClass::LoadInputImage(std::string _fn)
+{
+  
+    CImageBasis *cib = new CImageBasis(_fn);
+
+    GetInputDimension(true);
+
+    printf("TFlite load image size: %d x %d x %d\n", cib->getWidth(), cib->getHeight(), cib->getChannels());
+//    printf("TFlite expected image size: %d x %d x %d\n", im_width, im_height, im_channel);
+
+    if ((cib->getWidth() != im_width) || (cib->getHeight() != im_height) || (cib->getChannels() != im_channel))
+    {
+      printf("Input Imagesize Wrong.\n");
+      return false;
+    }
+
+    input_i = 0;
+    float* input_data_ptr = (interpreter->input(0))->data.f;
+    uint8_t ui8;
+    unsigned char zw;
+
+    for (int y = 0; y < cib->getHeight(); ++y)
+        for (int x = 0; x < cib->getWidth(); ++x)
+        {
+        printf("CIB: ");
+            for (int ch = 0; ch < cib->getChannels(); ++ch)
+            {
+                ui8 = cib->GetPixelColor(x, y, ch);
+                printf("%f ", (float) ui8);
+                *(input_data_ptr) = (float) ui8;
+                input_data_ptr++;
+            }
+          printf("\n");
+        }
+    delete cib;
+    return true;
+}
+*/
+
+bool CTfLiteClass::LoadInputImage(std::string _fn)
+{
+    bitmap_image image(_fn);
+    unsigned int w = image.width();
+    unsigned int h = image.height();
+    unsigned char red, green, blue;
+
+    input_i = 0;
+    float* input_data_ptr = (interpreter->input(0))->data.f;
+
+    for (int y = 0; y < h; ++y)
+        for (int x = 0; x < w; ++x)
+            {
+                red = image.red_channel(x, y);
+                green = image.green_channel(x, y);
+                blue = image.blue_channel(x, y);
+                *(input_data_ptr) = (float) red;
+                input_data_ptr++;
+                *(input_data_ptr) = (float) green;
+                input_data_ptr++;
+                *(input_data_ptr) = (float) blue;
+                input_data_ptr++;
+
+                printf("BMP: %f %f %f\n", (float) red, (float) green, (float) blue);
+
+            }
+    return true;
+}
+
+
+void CTfLiteClass::MakeAllocate()
+{
+/*    
+  this->micro_op_resolver.AddBuiltin(
+                                tflite::BuiltinOperator_RESHAPE,
+                                tflite::ops::micro::Register_RESHAPE());
+  this->micro_op_resolver.AddBuiltin(tflite::BuiltinOperator_CONV_2D,
+                               tflite::ops::micro::Register_CONV_2D());
+  this->micro_op_resolver.AddBuiltin(tflite::BuiltinOperator_FULLY_CONNECTED,
+                               tflite::ops::micro::Register_FULLY_CONNECTED());
+  this->micro_op_resolver.AddBuiltin(tflite::BuiltinOperator_SOFTMAX,
+                               tflite::ops::micro::Register_SOFTMAX());
+  this->micro_op_resolver.AddBuiltin(tflite::BuiltinOperator_DEPTHWISE_CONV_2D,
+                               tflite::ops::micro::Register_DEPTHWISE_CONV_2D());
+
+
+    this->interpreter = new tflite::MicroInterpreter(this->model, this->micro_op_resolver, this->tensor_arena, this->kTensorArenaSize, this->error_reporter);
+*/
+
+
+    static tflite::ops::micro::AllOpsResolver resolver;
+    this->interpreter = new tflite::MicroInterpreter(this->model, resolver, this->tensor_arena, this->kTensorArenaSize, this->error_reporter);
+
+    TfLiteStatus allocate_status = this->interpreter->AllocateTensors();
+    if (allocate_status != kTfLiteOk) {
+        TF_LITE_REPORT_ERROR(error_reporter, "AllocateTensors() failed");
+    this->GetInputDimension();   
+    return;
+  }
+
+    printf("Allocate Done.\n");
+}
+
+void CTfLiteClass::GetInputTensorSize(){
+    float *zw = this->input;
+    int test = sizeof(zw);
+    printf("Input Tensor Dimension: %d\n", test);       
+
+    printf("Input Tensor Dimension: %d\n", test);   
+}
+
+long CTfLiteClass::GetFileSize(std::string filename)
+{
+    struct stat stat_buf;
+    long rc = stat(filename.c_str(), &stat_buf);
+    return rc == 0 ? stat_buf.st_size : -1;
+}
+
+
+unsigned char* CTfLiteClass::ReadFileToCharArray(std::string _fn)
+{
+    long size;
+    
+    size = this->GetFileSize(_fn);
+
+    if (size == -1)
+    {
+		printf("\nFile existiert nicht.\n");
+        return NULL;
+    }
+
+
+    unsigned char *result = (unsigned char*) malloc(size);
+  
+	if(result != NULL) {
+//		printf("\nSpeicher ist reserviert\n");
+        FILE* f = fopen(_fn.c_str(), "rb");     // vorher  nur "r"
+        fread(result, 1, size, f);
+        fclose(f);        
+	}else {
+		printf("\nKein freier Speicher vorhanden.\n");
+	}    
+
+
+    return result;
+}
+
+void CTfLiteClass::LoadModel(std::string _fn){
+
+
+    this->error_reporter = new tflite::MicroErrorReporter;
+
+    unsigned char *rd;
+    rd = this->ReadFileToCharArray(_fn.c_str());
+//    printf("loadedfile: %d", (int) rd);
+
+    this->model = tflite::GetModel(rd);
+    free(rd);
+    TFLITE_MINIMAL_CHECK(model != nullptr); 
+    printf("tfile Loaded.\n");  
+
+}
+
+
+
+CTfLiteClass::CTfLiteClass()
+{
+//    this->accessSD = _accessSD;
+    this->model = nullptr;
+    this->interpreter = nullptr;
+    this->input = nullptr;
+    this->output = nullptr;  
+    this->kTensorArenaSize = 600 * 1024;
+    this->tensor_arena = new uint8_t[kTensorArenaSize];    
+
+//      micro_op_resolver.AddBuiltin(tflite::BuiltinOperator_CONV_2D,
+//                               tflite::ops::micro::Register_CONV_2D());
+}
+
+CTfLiteClass::~CTfLiteClass()
+{
+  delete this->tensor_arena;
+}        
+
+

+ 72 - 0
code/lib/jomjol_tfliteclass/CTfLiteClass.h

@@ -0,0 +1,72 @@
+#pragma once
+
+#ifndef __CFINDTEMPLATE
+#define __CFINGTEMPLATE
+
+#define TFLITE_MINIMAL_CHECK(x)                              \
+  if (!(x)) {                                                \
+    fprintf(stderr, "Error at %s:%d\n", __FILE__, __LINE__); \
+    exit(1);                                                 \
+  }
+
+//#include "CAccessSD.h"
+#include "CFindTemplate.h"
+
+#include "tensorflow/lite/micro/kernels/all_ops_resolver.h"
+#include "tensorflow/lite/micro/micro_error_reporter.h"
+#include "tensorflow/lite/micro/micro_interpreter.h"
+#include "tensorflow/lite/schema/schema_generated.h"
+#include "tensorflow/lite/version.h"
+#include "tensorflow/lite/micro/kernels/micro_ops.h"
+#include "esp_err.h"
+#include "esp_log.h"
+
+//extern CAccessSDClass accessSD;
+
+class CTfLiteClass
+{
+    protected:
+//        CAccessSDClass *accessSD;
+
+        tflite::ErrorReporter* error_reporter;
+        
+        const tflite::Model* model;
+        tflite::MicroInterpreter* interpreter;
+//        TfLiteTensor* input = nullptr;
+        TfLiteTensor* output = nullptr;     
+        static tflite::ops::micro::AllOpsResolver *resolver; 
+
+        tflite::MicroOpResolver<5> micro_op_resolver;
+  
+
+        int kTensorArenaSize;
+        uint8_t *tensor_arena;
+
+        float* input;
+        int input_i;
+
+        int im_height, im_width, im_channel;
+
+        long GetFileSize(std::string filename);
+        unsigned char* ReadFileToCharArray(std::string _fn);
+        
+    public:
+//        CTfLiteClass(CAccessSDClass *_accessSD);
+        CTfLiteClass();
+        ~CTfLiteClass();        
+        void LoadModel(std::string _fn);
+        void MakeAllocate();
+        void GetInputTensorSize();
+        bool LoadInputImage(std::string _fn);
+        void Invoke();
+        void GetOutPut();
+        int GetOutClassification();
+        int GetClassFromImage(std::string _fn);
+
+        float GetOutputValue(int nr);
+        void GetInputDimension(bool silent);
+
+};
+
+
+#endif

+ 137 - 0
code/lib/jomjol_time_sntp/time_sntp.cpp

@@ -0,0 +1,137 @@
+#include "time_sntp.h"
+
+/* LwIP SNTP example
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+#include <string>
+#include <time.h>
+#include <sys/time.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+#include "esp_system.h"
+#include "esp_event.h"
+#include "esp_log.h"
+#include "esp_attr.h"
+#include "esp_sleep.h"
+#include "nvs_flash.h"
+#include "protocol_examples_common.h"
+#include "esp_sntp.h"
+
+static const char *TAG = "sntp";
+
+RTC_DATA_ATTR int boot_count = 0;
+
+/* Variable holding number of times ESP32 restarted since first boot.
+ * It is placed into RTC memory using RTC_DATA_ATTR and
+ * maintains its value when ESP32 wakes from deep sleep.
+ */
+
+static void obtain_time(void);
+static void initialize_sntp(void);
+
+
+void time_sync_notification_cb(struct timeval *tv)
+{
+    ESP_LOGI(TAG, "Notification of a time synchronization event");
+}
+
+std::string gettimestring(const char * frm)
+{
+    time_t now;
+    struct tm timeinfo;
+    time(&now);
+    localtime_r(&now, &timeinfo);
+    // Is time set? If not, tm_year will be (1970 - 1900).
+    if (timeinfo.tm_year < (2016 - 1900)) {
+        ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
+        obtain_time();
+        // update 'now' variable with current time
+        time(&now);
+    }
+    char strftime_buf[64];
+
+    setenv("TZ", "UTC-2", 1);
+    tzset();
+    localtime_r(&now, &timeinfo);
+    strftime(strftime_buf, sizeof(strftime_buf), frm, &timeinfo);
+
+    std::string result(strftime_buf);
+    return result;
+}
+
+void setup_time(void)
+{
+    ++boot_count;
+    ESP_LOGI(TAG, "Boot count: %d", boot_count);
+
+    time_t now;
+    struct tm timeinfo;
+    time(&now);
+    localtime_r(&now, &timeinfo);
+    // Is time set? If not, tm_year will be (1970 - 1900).
+    if (timeinfo.tm_year < (2016 - 1900)) {
+        ESP_LOGI(TAG, "Time is not set yet. Connecting to WiFi and getting time over NTP.");
+        obtain_time();
+        // update 'now' variable with current time
+        time(&now);
+    }
+    char strftime_buf[64];
+
+/*
+    // Set timezone to Eastern Standard Time and print local time
+    setenv("TZ", "EST5EDT,M3.2.0/2,M11.1.0", 1);
+    tzset();
+    localtime_r(&now, &timeinfo);
+    strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
+    ESP_LOGI(TAG, "The current date/time in New York is: %s", strftime_buf);
+
+    // Set timezone to China Standard Time
+    setenv("TZ", "CST-8", 1);
+    tzset();
+    localtime_r(&now, &timeinfo);
+    strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
+    ESP_LOGI(TAG, "The current date/time in Shanghai is: %s", strftime_buf);
+*/
+    // Set timezone to Berlin Standard Time
+    setenv("TZ", "UTC+9", 1);
+    tzset();
+    localtime_r(&now, &timeinfo);
+    strftime(strftime_buf, sizeof(strftime_buf), "%c", &timeinfo);
+    ESP_LOGI(TAG, "The current date/time in Berlin is: %s", strftime_buf);
+
+    strftime(strftime_buf, sizeof(strftime_buf), "%Y-%m-%d_%H:%M", &timeinfo);
+    ESP_LOGI(TAG, "The current date/time in Berlin is: %s", strftime_buf);
+
+    std::string zw = gettimestring("%Y%m%d-%H%M%S");
+    printf("time %s\n", zw.c_str());
+}
+
+static void obtain_time(void)
+{
+    initialize_sntp();
+
+    // wait for time to be set
+    time_t now = 0;
+    struct tm timeinfo = {};
+    int retry = 0;
+    const int retry_count = 10;
+    while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < retry_count) {
+        ESP_LOGI(TAG, "Waiting for system time to be set... (%d/%d)", retry, retry_count);
+        vTaskDelay(2000 / portTICK_PERIOD_MS);
+    }
+    time(&now);
+    localtime_r(&now, &timeinfo);
+}
+
+static void initialize_sntp(void)
+{
+    ESP_LOGI(TAG, "Initializing SNTP");
+    sntp_setoperatingmode(SNTP_OPMODE_POLL);
+    sntp_setservername(0, "pool.ntp.org");
+    sntp_set_time_sync_notification_cb(time_sync_notification_cb);
+    sntp_init();
+}

+ 20 - 0
code/lib/jomjol_time_sntp/time_sntp.h

@@ -0,0 +1,20 @@
+#include <string>
+#include <time.h>
+#include <sys/time.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+#include "esp_system.h"
+#include "esp_event.h"
+#include "esp_log.h"
+#include "esp_attr.h"
+#include "esp_sleep.h"
+#include "nvs_flash.h"
+#include "esp_sntp.h"
+
+
+extern int boot_count;
+
+void setup_time(void);
+
+std::string gettimestring(const char * frm);

+ 580 - 0
code/lib/sensors/ov2640.c

@@ -0,0 +1,580 @@
+/*
+ * This file is part of the OpenMV project.
+ * Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
+ * This work is licensed under the MIT license, see the file LICENSE for details.
+ *
+ * OV2640 driver.
+ *
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include "sccb.h"
+#include "ov2640.h"
+#include "ov2640_regs.h"
+#include "ov2640_settings.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
+#include "esp32-hal-log.h"
+#else
+#include "esp_log.h"
+static const char* TAG = "ov2640";
+#endif
+
+static volatile ov2640_bank_t reg_bank = BANK_MAX;
+static int set_bank(sensor_t *sensor, ov2640_bank_t bank)
+{
+    int res = 0;
+    if (bank != reg_bank) {
+        reg_bank = bank;
+        res = SCCB_Write(sensor->slv_addr, BANK_SEL, bank);
+    }
+    return res;
+}
+
+static int write_regs(sensor_t *sensor, const uint8_t (*regs)[2])
+{
+    int i=0, res = 0;
+    while (regs[i][0]) {
+        if (regs[i][0] == BANK_SEL) {
+            res = set_bank(sensor, regs[i][1]);
+        } else {
+            res = SCCB_Write(sensor->slv_addr, regs[i][0], regs[i][1]);
+        }
+        if (res) {
+            return res;
+        }
+        i++;
+    }
+    return res;
+}
+
+static int write_reg(sensor_t *sensor, ov2640_bank_t bank, uint8_t reg, uint8_t value)
+{
+    int ret = set_bank(sensor, bank);
+    if(!ret) {
+        ret = SCCB_Write(sensor->slv_addr, reg, value);
+    }
+    return ret;
+}
+
+static int set_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t offset, uint8_t mask, uint8_t value)
+{
+    int ret = 0;
+    uint8_t c_value, new_value;
+
+    ret = set_bank(sensor, bank);
+    if(ret) {
+        return ret;
+    }
+    c_value = SCCB_Read(sensor->slv_addr, reg);
+    new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset);
+    ret = SCCB_Write(sensor->slv_addr, reg, new_value);
+    return ret;
+}
+
+static int read_reg(sensor_t *sensor, ov2640_bank_t bank, uint8_t reg)
+{
+    if(set_bank(sensor, bank)){
+        return 0;
+    }
+    return SCCB_Read(sensor->slv_addr, reg);
+}
+
+static uint8_t get_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t offset, uint8_t mask)
+{
+    return (read_reg(sensor, bank, reg) >> offset) & mask;
+}
+
+static int write_reg_bits(sensor_t *sensor, uint8_t bank, uint8_t reg, uint8_t mask, int enable)
+{
+    return set_reg_bits(sensor, bank, reg, 0, mask, enable?mask:0);
+}
+
+#define WRITE_REGS_OR_RETURN(regs) ret = write_regs(sensor, regs); if(ret){return ret;}
+#define WRITE_REG_OR_RETURN(bank, reg, val) ret = write_reg(sensor, bank, reg, val); if(ret){return ret;}
+#define SET_REG_BITS_OR_RETURN(bank, reg, offset, mask, val) ret = set_reg_bits(sensor, bank, reg, offset, mask, val); if(ret){return ret;}
+
+static int reset(sensor_t *sensor)
+{
+    int ret = 0;
+    WRITE_REG_OR_RETURN(BANK_SENSOR, COM7, COM7_SRST);
+    vTaskDelay(10 / portTICK_PERIOD_MS);
+    WRITE_REGS_OR_RETURN(ov2640_settings_cif);
+    return ret;
+}
+
+static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
+{
+    int ret = 0;
+    sensor->pixformat = pixformat;
+    switch (pixformat) {
+    case PIXFORMAT_RGB565:
+    case PIXFORMAT_RGB888:
+        WRITE_REGS_OR_RETURN(ov2640_settings_rgb565);
+        break;
+    case PIXFORMAT_YUV422:
+    case PIXFORMAT_GRAYSCALE:
+        WRITE_REGS_OR_RETURN(ov2640_settings_yuv422);
+        break;
+    case PIXFORMAT_JPEG:
+        WRITE_REGS_OR_RETURN(ov2640_settings_jpeg3);
+        break;
+    default:
+        ret = -1;
+        break;
+    }
+    if(!ret) {
+        vTaskDelay(10 / portTICK_PERIOD_MS);
+    }
+
+    return ret;
+}
+
+static int set_window(sensor_t *sensor, ov2640_sensor_mode_t mode, int offset_x, int offset_y, int max_x, int max_y, int w, int h){
+    int ret = 0;
+    const uint8_t (*regs)[2];
+    ov2640_clk_t c;
+    c.reserved = 0;
+
+    max_x /= 4;
+    max_y /= 4;
+    w /= 4;
+    h /= 4;
+    uint8_t win_regs[][2] = {
+        {BANK_SEL, BANK_DSP},
+        {HSIZE, max_x & 0xFF},
+        {VSIZE, max_y & 0xFF},
+        {XOFFL, offset_x & 0xFF},
+        {YOFFL, offset_y & 0xFF},
+        {VHYX, ((max_y >> 1) & 0X80) | ((offset_y >> 4) & 0X70) | ((max_x >> 5) & 0X08) | ((offset_y >> 8) & 0X07)},
+        {TEST, (max_x >> 2) & 0X80},
+        {ZMOW, (w)&0xFF},
+        {ZMOH, (h)&0xFF},
+        {ZMHH, ((h>>6)&0x04)|((w>>8)&0x03)},
+        {0, 0}
+    };
+
+    c.pclk_auto = 0;
+    c.pclk_div = 8;
+    c.clk_2x = 0;
+    c.clk_div = 0;
+
+    if(sensor->pixformat != PIXFORMAT_JPEG){
+        c.pclk_auto = 1;
+        c.clk_div = 7;
+    }
+
+    if (mode == OV2640_MODE_CIF) {
+        regs = ov2640_settings_to_cif;
+        if(sensor->pixformat != PIXFORMAT_JPEG){
+            c.clk_div = 3;
+        }
+    } else if (mode == OV2640_MODE_SVGA) {
+        regs = ov2640_settings_to_svga;
+    } else {
+        regs = ov2640_settings_to_uxga;
+        c.pclk_div = 12;
+    }
+
+    WRITE_REG_OR_RETURN(BANK_DSP, R_BYPASS, R_BYPASS_DSP_BYPAS);
+    WRITE_REGS_OR_RETURN(regs);
+    WRITE_REGS_OR_RETURN(win_regs);
+    WRITE_REG_OR_RETURN(BANK_SENSOR, CLKRC, c.clk);
+    WRITE_REG_OR_RETURN(BANK_DSP, R_DVP_SP, c.pclk);
+    WRITE_REG_OR_RETURN(BANK_DSP, R_BYPASS, R_BYPASS_DSP_EN);
+
+    vTaskDelay(10 / portTICK_PERIOD_MS);
+    //required when changing resolution
+    set_pixformat(sensor, sensor->pixformat);
+
+    return ret;
+}
+
+static int set_framesize(sensor_t *sensor, framesize_t framesize)
+{
+    int ret = 0;
+    uint16_t w = resolution[framesize].width;
+    uint16_t h = resolution[framesize].height;
+    aspect_ratio_t ratio = resolution[framesize].aspect_ratio;
+    uint16_t max_x = ratio_table[ratio].max_x;
+    uint16_t max_y = ratio_table[ratio].max_y;
+    uint16_t offset_x = ratio_table[ratio].offset_x;
+    uint16_t offset_y = ratio_table[ratio].offset_y;
+    ov2640_sensor_mode_t mode = OV2640_MODE_UXGA;
+
+    sensor->status.framesize = framesize;
+
+
+
+    if (framesize <= FRAMESIZE_CIF) {
+        mode = OV2640_MODE_CIF;
+        max_x /= 4;
+        max_y /= 4;
+        offset_x /= 4;
+        offset_y /= 4;
+        if(max_y > 296){
+            max_y = 296;
+        }
+    } else if (framesize <= FRAMESIZE_SVGA) {
+        mode = OV2640_MODE_SVGA;
+        max_x /= 2;
+        max_y /= 2;
+        offset_x /= 2;
+        offset_y /= 2;
+    }
+
+    ret = set_window(sensor, mode, offset_x, offset_y, max_x, max_y, w, h);
+    return ret;
+}
+
+static int set_contrast(sensor_t *sensor, int level)
+{
+    int ret=0;
+    level += 3;
+    if (level <= 0 || level > NUM_CONTRAST_LEVELS) {
+        return -1;
+    }
+    sensor->status.contrast = level-3;
+    for (int i=0; i<7; i++) {
+        WRITE_REG_OR_RETURN(BANK_DSP, contrast_regs[0][i], contrast_regs[level][i]);
+    }
+    return ret;
+}
+
+static int set_brightness(sensor_t *sensor, int level)
+{
+    int ret=0;
+    level += 3;
+    if (level <= 0 || level > NUM_BRIGHTNESS_LEVELS) {
+        return -1;
+    }
+    sensor->status.brightness = level-3;
+    for (int i=0; i<5; i++) {
+        WRITE_REG_OR_RETURN(BANK_DSP, brightness_regs[0][i], brightness_regs[level][i]);
+    }
+    return ret;
+}
+
+static int set_saturation(sensor_t *sensor, int level)
+{
+    int ret=0;
+    level += 3;
+    if (level <= 0 || level > NUM_SATURATION_LEVELS) {
+        return -1;
+    }
+    sensor->status.saturation = level-3;
+    for (int i=0; i<5; i++) {
+        WRITE_REG_OR_RETURN(BANK_DSP, saturation_regs[0][i], saturation_regs[level][i]);
+    }
+    return ret;
+}
+
+static int set_special_effect(sensor_t *sensor, int effect)
+{
+    int ret=0;
+    effect++;
+    if (effect <= 0 || effect > NUM_SPECIAL_EFFECTS) {
+        return -1;
+    }
+    sensor->status.special_effect = effect-1;
+    for (int i=0; i<5; i++) {
+        WRITE_REG_OR_RETURN(BANK_DSP, special_effects_regs[0][i], special_effects_regs[effect][i]);
+    }
+    return ret;
+}
+
+static int set_wb_mode(sensor_t *sensor, int mode)
+{
+    int ret=0;
+    if (mode < 0 || mode > NUM_WB_MODES) {
+        return -1;
+    }
+    sensor->status.wb_mode = mode;
+    SET_REG_BITS_OR_RETURN(BANK_DSP, 0XC7, 6, 1, mode?1:0);
+    if(mode) {
+        for (int i=0; i<3; i++) {
+            WRITE_REG_OR_RETURN(BANK_DSP, wb_modes_regs[0][i], wb_modes_regs[mode][i]);
+        }
+    }
+    return ret;
+}
+
+static int set_ae_level(sensor_t *sensor, int level)
+{
+    int ret=0;
+    level += 3;
+    if (level <= 0 || level > NUM_AE_LEVELS) {
+        return -1;
+    }
+    sensor->status.ae_level = level-3;
+    for (int i=0; i<3; i++) {
+        WRITE_REG_OR_RETURN(BANK_SENSOR, ae_levels_regs[0][i], ae_levels_regs[level][i]);
+    }
+    return ret;
+}
+
+static int set_quality(sensor_t *sensor, int quality)
+{
+    if(quality < 0) {
+        quality = 0;
+    } else if(quality > 63) {
+        quality = 63;
+    }
+    sensor->status.quality = quality;
+    return write_reg(sensor, BANK_DSP, QS, quality);
+}
+
+static int set_agc_gain(sensor_t *sensor, int gain)
+{
+    if(gain < 0) {
+        gain = 0;
+    } else if(gain > 30) {
+        gain = 30;
+    }
+    sensor->status.agc_gain = gain;
+    return write_reg(sensor, BANK_SENSOR, GAIN, agc_gain_tbl[gain]);
+}
+
+static int set_gainceiling_sensor(sensor_t *sensor, gainceiling_t gainceiling)
+{
+    sensor->status.gainceiling = gainceiling;
+    //return write_reg(sensor, BANK_SENSOR, COM9, COM9_AGC_SET(gainceiling));
+    return set_reg_bits(sensor, BANK_SENSOR, COM9, 5, 7, gainceiling);
+}
+
+static int set_aec_value(sensor_t *sensor, int value)
+{
+    if(value < 0) {
+        value = 0;
+    } else if(value > 1200) {
+        value = 1200;
+    }
+    sensor->status.aec_value = value;
+    return set_reg_bits(sensor, BANK_SENSOR, REG04, 0, 3, value & 0x3)
+           || write_reg(sensor, BANK_SENSOR, AEC, (value >> 2) & 0xFF)
+           || set_reg_bits(sensor, BANK_SENSOR, REG45, 0, 0x3F, value >> 10);
+}
+
+static int set_aec2(sensor_t *sensor, int enable)
+{
+    sensor->status.aec2 = enable;
+    return set_reg_bits(sensor, BANK_DSP, CTRL0, 6, 1, enable?0:1);
+}
+
+static int set_colorbar(sensor_t *sensor, int enable)
+{
+    sensor->status.colorbar = enable;
+    return write_reg_bits(sensor, BANK_SENSOR, COM7, COM7_COLOR_BAR, enable?1:0);
+}
+
+static int set_agc_sensor(sensor_t *sensor, int enable)
+{
+    sensor->status.agc = enable;
+    return write_reg_bits(sensor, BANK_SENSOR, COM8, COM8_AGC_EN, enable?1:0);
+}
+
+static int set_aec_sensor(sensor_t *sensor, int enable)
+{
+    sensor->status.aec = enable;
+    return write_reg_bits(sensor, BANK_SENSOR, COM8, COM8_AEC_EN, enable?1:0);
+}
+
+static int set_hmirror_sensor(sensor_t *sensor, int enable)
+{
+    sensor->status.hmirror = enable;
+    return write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_HFLIP_IMG, enable?1:0);
+}
+
+static int set_vflip_sensor(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    sensor->status.vflip = enable;
+    ret = write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_VREF_EN, enable?1:0);
+    return ret & write_reg_bits(sensor, BANK_SENSOR, REG04, REG04_VFLIP_IMG, enable?1:0);
+}
+
+static int set_raw_gma_dsp(sensor_t *sensor, int enable)
+{
+    sensor->status.raw_gma = enable;
+    return set_reg_bits(sensor, BANK_DSP, CTRL1, 5, 1, enable?1:0);
+}
+
+static int set_awb_dsp(sensor_t *sensor, int enable)
+{
+    sensor->status.awb = enable;
+    return set_reg_bits(sensor, BANK_DSP, CTRL1, 3, 1, enable?1:0);
+}
+
+static int set_awb_gain_dsp(sensor_t *sensor, int enable)
+{
+    sensor->status.awb_gain = enable;
+    return set_reg_bits(sensor, BANK_DSP, CTRL1, 2, 1, enable?1:0);
+}
+
+static int set_lenc_dsp(sensor_t *sensor, int enable)
+{
+    sensor->status.lenc = enable;
+    return set_reg_bits(sensor, BANK_DSP, CTRL1, 1, 1, enable?1:0);
+}
+
+static int set_dcw_dsp(sensor_t *sensor, int enable)
+{
+    sensor->status.dcw = enable;
+    return set_reg_bits(sensor, BANK_DSP, CTRL2, 5, 1, enable?1:0);
+}
+
+static int set_bpc_dsp(sensor_t *sensor, int enable)
+{
+    sensor->status.bpc = enable;
+    return set_reg_bits(sensor, BANK_DSP, CTRL3, 7, 1, enable?1:0);
+}
+
+static int set_wpc_dsp(sensor_t *sensor, int enable)
+{
+    sensor->status.wpc = enable;
+    return set_reg_bits(sensor, BANK_DSP, CTRL3, 6, 1, enable?1:0);
+}
+
+//unsupported
+static int set_sharpness(sensor_t *sensor, int level)
+{
+   return -1;
+}
+
+static int set_denoise(sensor_t *sensor, int level)
+{
+   return -1;
+}
+
+static int get_reg(sensor_t *sensor, int reg, int mask)
+{
+    int ret = read_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF);
+    if(ret > 0){
+        ret &= mask;
+    }
+    return ret;
+}
+
+static int set_reg(sensor_t *sensor, int reg, int mask, int value)
+{
+    int ret = 0;
+    ret = read_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF);
+    if(ret < 0){
+        return ret;
+    }
+    value = (ret & ~mask) | (value & mask);
+    ret = write_reg(sensor, (reg >> 8) & 0x01, reg & 0xFF, value);
+    return ret;
+}
+
+static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning)
+{
+    return set_window(sensor, (ov2640_sensor_mode_t)startX, offsetX, offsetY, totalX, totalY, outputX, outputY);
+}
+
+static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div)
+{
+    return -1;
+}
+
+esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz);
+static int set_xclk(sensor_t *sensor, int timer, int xclk)
+{
+    int ret = 0;
+    sensor->xclk_freq_hz = xclk * 1000000U;
+    ret = xclk_timer_conf(timer, sensor->xclk_freq_hz);
+    return ret;
+}
+
+static int init_status(sensor_t *sensor){
+    sensor->status.brightness = 0;
+    sensor->status.contrast = 0;
+    sensor->status.saturation = 0;
+    sensor->status.ae_level = 0;
+    sensor->status.special_effect = 0;
+    sensor->status.wb_mode = 0;
+
+    sensor->status.agc_gain = 30;
+    int agc_gain = read_reg(sensor, BANK_SENSOR, GAIN);
+    for (int i=0; i<30; i++){
+        if(agc_gain >= agc_gain_tbl[i] && agc_gain < agc_gain_tbl[i+1]){
+            sensor->status.agc_gain = i;
+            break;
+        }
+    }
+
+    sensor->status.aec_value = ((uint16_t)get_reg_bits(sensor, BANK_SENSOR, REG45, 0, 0x3F) << 10)
+                             | ((uint16_t)read_reg(sensor, BANK_SENSOR, AEC) << 2)
+                             | get_reg_bits(sensor, BANK_SENSOR, REG04, 0, 3);//0 - 1200
+    sensor->status.quality = read_reg(sensor, BANK_DSP, QS);
+    sensor->status.gainceiling = get_reg_bits(sensor, BANK_SENSOR, COM9, 5, 7);
+
+    sensor->status.awb = get_reg_bits(sensor, BANK_DSP, CTRL1, 3, 1);
+    sensor->status.awb_gain = get_reg_bits(sensor, BANK_DSP, CTRL1, 2, 1);
+    sensor->status.aec = get_reg_bits(sensor, BANK_SENSOR, COM8, 0, 1);
+    sensor->status.aec2 = get_reg_bits(sensor, BANK_DSP, CTRL0, 6, 1);
+    sensor->status.agc = get_reg_bits(sensor, BANK_SENSOR, COM8, 2, 1);
+    sensor->status.bpc = get_reg_bits(sensor, BANK_DSP, CTRL3, 7, 1);
+    sensor->status.wpc = get_reg_bits(sensor, BANK_DSP, CTRL3, 6, 1);
+    sensor->status.raw_gma = get_reg_bits(sensor, BANK_DSP, CTRL1, 5, 1);
+    sensor->status.lenc = get_reg_bits(sensor, BANK_DSP, CTRL1, 1, 1);
+    sensor->status.hmirror = get_reg_bits(sensor, BANK_SENSOR, REG04, 7, 1);
+    sensor->status.vflip = get_reg_bits(sensor, BANK_SENSOR, REG04, 6, 1);
+    sensor->status.dcw = get_reg_bits(sensor, BANK_DSP, CTRL2, 5, 1);
+    sensor->status.colorbar = get_reg_bits(sensor, BANK_SENSOR, COM7, 1, 1);
+
+    sensor->status.sharpness = 0;//not supported
+    sensor->status.denoise = 0;
+    return 0;
+}
+
+int ov2640_init(sensor_t *sensor)
+{
+    sensor->reset = reset;
+    sensor->init_status = init_status;
+    sensor->set_pixformat = set_pixformat;
+    sensor->set_framesize = set_framesize;
+    sensor->set_contrast  = set_contrast;
+    sensor->set_brightness= set_brightness;
+    sensor->set_saturation= set_saturation;
+
+    sensor->set_quality = set_quality;
+    sensor->set_colorbar = set_colorbar;
+
+    sensor->set_gainceiling = set_gainceiling_sensor;
+    sensor->set_gain_ctrl = set_agc_sensor;
+    sensor->set_exposure_ctrl = set_aec_sensor;
+    sensor->set_hmirror = set_hmirror_sensor;
+    sensor->set_vflip = set_vflip_sensor;
+
+    sensor->set_whitebal = set_awb_dsp;
+    sensor->set_aec2 = set_aec2;
+    sensor->set_aec_value = set_aec_value;
+    sensor->set_special_effect = set_special_effect;
+    sensor->set_wb_mode = set_wb_mode;
+    sensor->set_ae_level = set_ae_level;
+
+    sensor->set_dcw = set_dcw_dsp;
+    sensor->set_bpc = set_bpc_dsp;
+    sensor->set_wpc = set_wpc_dsp;
+    sensor->set_awb_gain = set_awb_gain_dsp;
+    sensor->set_agc_gain = set_agc_gain;
+
+    sensor->set_raw_gma = set_raw_gma_dsp;
+    sensor->set_lenc = set_lenc_dsp;
+
+    //not supported
+    sensor->set_sharpness = set_sharpness;
+    sensor->set_denoise = set_denoise;
+
+    sensor->get_reg = get_reg;
+    sensor->set_reg = set_reg;
+    sensor->set_res_raw = set_res_raw;
+    sensor->set_pll = _set_pll;
+    sensor->set_xclk = set_xclk;
+    ESP_LOGD(TAG, "OV2640 Attached");
+    return 0;
+}

+ 13 - 0
code/lib/sensors/ov2640.h

@@ -0,0 +1,13 @@
+/*
+ * This file is part of the OpenMV project.
+ * Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
+ * This work is licensed under the MIT license, see the file LICENSE for details.
+ *
+ * OV2640 driver.
+ *
+ */
+#ifndef __OV2640_H__
+#define __OV2640_H__
+#include "sensor.h"
+int ov2640_init(sensor_t *sensor);
+#endif // __OV2640_H__

+ 216 - 0
code/lib/sensors/ov2640_regs.h

@@ -0,0 +1,216 @@
+/*
+ * This file is part of the OpenMV project.
+ * Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
+ * This work is licensed under the MIT license, see the file LICENSE for details.
+ *
+ * OV2640 register definitions.
+ */
+#ifndef __REG_REGS_H__
+#define __REG_REGS_H__
+/* DSP register bank FF=0x00*/
+#define R_BYPASS            0x05
+#define QS                  0x44
+#define CTRLI               0x50
+#define HSIZE               0x51
+#define VSIZE               0x52
+#define XOFFL               0x53
+#define YOFFL               0x54
+#define VHYX                0x55
+#define DPRP                0x56
+#define TEST                0x57
+#define ZMOW                0x5A
+#define ZMOH                0x5B
+#define ZMHH                0x5C
+#define BPADDR              0x7C
+#define BPDATA              0x7D
+#define CTRL2               0x86
+#define CTRL3               0x87
+#define SIZEL               0x8C
+#define HSIZE8              0xC0
+#define VSIZE8              0xC1
+#define CTRL0               0xC2
+#define CTRL1               0xC3
+#define R_DVP_SP            0xD3
+#define IMAGE_MODE          0xDA
+#define RESET               0xE0
+#define MS_SP               0xF0
+#define SS_ID               0xF7
+#define SS_CTRL             0xF7
+#define MC_BIST             0xF9
+#define MC_AL               0xFA
+#define MC_AH               0xFB
+#define MC_D                0xFC
+#define P_CMD               0xFD
+#define P_STATUS            0xFE
+#define BANK_SEL            0xFF
+
+#define CTRLI_LP_DP         0x80
+#define CTRLI_ROUND         0x40
+
+#define CTRL0_AEC_EN        0x80
+#define CTRL0_AEC_SEL       0x40
+#define CTRL0_STAT_SEL      0x20
+#define CTRL0_VFIRST        0x10
+#define CTRL0_YUV422        0x08
+#define CTRL0_YUV_EN        0x04
+#define CTRL0_RGB_EN        0x02
+#define CTRL0_RAW_EN        0x01
+
+#define CTRL2_DCW_EN        0x20
+#define CTRL2_SDE_EN        0x10
+#define CTRL2_UV_ADJ_EN     0x08
+#define CTRL2_UV_AVG_EN     0x04
+#define CTRL2_CMX_EN        0x01
+
+#define CTRL3_BPC_EN        0x80
+#define CTRL3_WPC_EN        0x40
+
+#define R_DVP_SP_AUTO_MODE  0x80
+
+#define R_BYPASS_DSP_EN         0x00
+#define R_BYPASS_DSP_BYPAS      0x01
+
+#define IMAGE_MODE_Y8_DVP_EN    0x40
+#define IMAGE_MODE_JPEG_EN      0x10
+#define IMAGE_MODE_YUV422       0x00
+#define IMAGE_MODE_RAW10        0x04
+#define IMAGE_MODE_RGB565       0x08
+#define IMAGE_MODE_HREF_VSYNC   0x02
+#define IMAGE_MODE_LBYTE_FIRST  0x01
+
+#define RESET_MICROC            0x40
+#define RESET_SCCB              0x20
+#define RESET_JPEG              0x10
+#define RESET_DVP               0x04
+#define RESET_IPU               0x02
+#define RESET_CIF               0x01
+
+#define MC_BIST_RESET           0x80
+#define MC_BIST_BOOT_ROM_SEL    0x40
+#define MC_BIST_12KB_SEL        0x20
+#define MC_BIST_12KB_MASK       0x30
+#define MC_BIST_512KB_SEL       0x08
+#define MC_BIST_512KB_MASK      0x0C
+#define MC_BIST_BUSY_BIT_R      0x02
+#define MC_BIST_MC_RES_ONE_SH_W 0x02
+#define MC_BIST_LAUNCH          0x01
+
+
+typedef enum {
+    BANK_DSP, BANK_SENSOR, BANK_MAX
+} ov2640_bank_t;
+
+/* Sensor register bank FF=0x01*/
+#define GAIN                0x00
+#define COM1                0x03
+#define REG04               0x04
+#define REG08               0x08
+#define COM2                0x09
+#define REG_PID             0x0A
+#define REG_VER             0x0B
+#define COM3                0x0C
+#define COM4                0x0D
+#define AEC                 0x10
+#define CLKRC               0x11
+#define COM7                0x12
+#define COM8                0x13
+#define COM9                0x14 /* AGC gain ceiling */
+#define COM10               0x15
+#define HSTART              0x17
+#define HSTOP               0x18
+#define VSTART              0x19
+#define VSTOP               0x1A
+#define MIDH                0x1C
+#define MIDL                0x1D
+#define AEW                 0x24
+#define AEB                 0x25
+#define VV                  0x26
+#define REG2A               0x2A
+#define FRARL               0x2B
+#define ADDVSL              0x2D
+#define ADDVSH              0x2E
+#define YAVG                0x2F
+#define HSDY                0x30
+#define HEDY                0x31
+#define REG32               0x32
+#define ARCOM2              0x34
+#define REG45               0x45
+#define FLL                 0x46
+#define FLH                 0x47
+#define COM19               0x48
+#define ZOOMS               0x49
+#define COM22               0x4B
+#define COM25               0x4E
+#define BD50                0x4F
+#define BD60                0x50
+#define REG5D               0x5D
+#define REG5E               0x5E
+#define REG5F               0x5F
+#define REG60               0x60
+#define HISTO_LOW           0x61
+#define HISTO_HIGH          0x62
+
+#define REG04_DEFAULT       0x28
+#define REG04_HFLIP_IMG     0x80
+#define REG04_VFLIP_IMG     0x40
+#define REG04_VREF_EN       0x10
+#define REG04_HREF_EN       0x08
+#define REG04_SET(x)        (REG04_DEFAULT|x)
+
+#define COM2_STDBY          0x10
+#define COM2_OUT_DRIVE_1x   0x00
+#define COM2_OUT_DRIVE_2x   0x01
+#define COM2_OUT_DRIVE_3x   0x02
+#define COM2_OUT_DRIVE_4x   0x03
+
+#define COM3_DEFAULT        0x38
+#define COM3_BAND_50Hz      0x04
+#define COM3_BAND_60Hz      0x00
+#define COM3_BAND_AUTO      0x02
+#define COM3_BAND_SET(x)    (COM3_DEFAULT|x)
+
+#define COM7_SRST           0x80
+#define COM7_RES_UXGA       0x00 /* UXGA */
+#define COM7_RES_SVGA       0x40 /* SVGA */
+#define COM7_RES_CIF        0x20 /* CIF  */
+#define COM7_ZOOM_EN        0x04 /* Enable Zoom */
+#define COM7_COLOR_BAR      0x02 /* Enable Color Bar Test */
+
+#define COM8_DEFAULT        0xC0
+#define COM8_BNDF_EN        0x20 /* Enable Banding filter */
+#define COM8_AGC_EN         0x04 /* AGC Auto/Manual control selection */
+#define COM8_AEC_EN         0x01 /* Auto/Manual Exposure control */
+#define COM8_SET(x)         (COM8_DEFAULT|x)
+
+#define COM9_DEFAULT        0x08
+#define COM9_AGC_GAIN_2x    0x00 /* AGC:    2x */
+#define COM9_AGC_GAIN_4x    0x01 /* AGC:    4x */
+#define COM9_AGC_GAIN_8x    0x02 /* AGC:    8x */
+#define COM9_AGC_GAIN_16x   0x03 /* AGC:   16x */
+#define COM9_AGC_GAIN_32x   0x04 /* AGC:   32x */
+#define COM9_AGC_GAIN_64x   0x05 /* AGC:   64x */
+#define COM9_AGC_GAIN_128x  0x06 /* AGC:  128x */
+#define COM9_AGC_SET(x)     (COM9_DEFAULT|(x<<5))
+
+#define COM10_HREF_EN       0x80 /* HSYNC changes to HREF */
+#define COM10_HSYNC_EN      0x40 /* HREF changes to HSYNC */
+#define COM10_PCLK_FREE     0x20 /* PCLK output option: free running PCLK */
+#define COM10_PCLK_EDGE     0x10 /* Data is updated at the rising edge of PCLK */
+#define COM10_HREF_NEG      0x08 /* HREF negative */
+#define COM10_VSYNC_NEG     0x02 /* VSYNC negative */
+#define COM10_HSYNC_NEG     0x01 /* HSYNC negative */
+
+#define CTRL1_AWB           0x08 /* Enable AWB */
+
+#define VV_AGC_TH_SET(h,l)  ((h<<4)|(l&0x0F))
+
+#define REG32_UXGA          0x36
+#define REG32_SVGA          0x09
+#define REG32_CIF           0x89
+
+#define CLKRC_2X            0x80
+#define CLKRC_2X_UXGA       (0x01 | CLKRC_2X)
+#define CLKRC_2X_SVGA       CLKRC_2X
+#define CLKRC_2X_CIF        CLKRC_2X
+
+#endif //__REG_REGS_H__

+ 485 - 0
code/lib/sensors/ov2640_settings.h

@@ -0,0 +1,485 @@
+// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+#ifndef _OV2640_SETTINGS_H_
+#define _OV2640_SETTINGS_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "esp_attr.h"
+#include "ov2640_regs.h"
+
+typedef enum {
+    OV2640_MODE_UXGA, OV2640_MODE_SVGA, OV2640_MODE_CIF, OV2640_MODE_MAX
+} ov2640_sensor_mode_t;
+
+typedef struct {
+        union {
+                struct {
+                        uint8_t pclk_div:7;
+                        uint8_t pclk_auto:1;
+                };
+                uint8_t pclk;
+        };
+        union {
+                struct {
+                        uint8_t clk_div:6;
+                        uint8_t reserved:1;
+                        uint8_t clk_2x:1;
+                };
+                uint8_t clk;
+        };
+} ov2640_clk_t;
+
+typedef struct {
+        uint16_t offset_x;
+        uint16_t offset_y;
+        uint16_t max_x;
+        uint16_t max_y;
+} ov2640_ratio_settings_t;
+
+static const DRAM_ATTR ov2640_ratio_settings_t ratio_table[] = {
+    // ox,  oy,   mx,   my
+    {   0,   0, 1600, 1200 }, //4x3
+    {   8,  72, 1584, 1056 }, //3x2
+    {   0, 100, 1600, 1000 }, //16x10
+    {   0, 120, 1600,  960 }, //5x3
+    {   0, 150, 1600,  900 }, //16x9
+    {   2, 258, 1596,  684 }, //21x9
+    {  50,   0, 1500, 1200 }, //5x4
+    { 200,   0, 1200, 1200 }, //1x1
+    { 462,   0,  676, 1200 }  //9x16
+};
+
+// 30fps@24MHz
+const DRAM_ATTR uint8_t ov2640_settings_cif[][2] = {
+    {BANK_SEL, BANK_DSP},
+    {0x2c, 0xff},
+    {0x2e, 0xdf},
+    {BANK_SEL, BANK_SENSOR},
+    {0x3c, 0x32},
+    {CLKRC, 0x01},
+    {COM2, COM2_OUT_DRIVE_3x},
+    {REG04, REG04_DEFAULT},
+    {COM8, COM8_DEFAULT | COM8_BNDF_EN | COM8_AGC_EN | COM8_AEC_EN},
+    {COM9, COM9_AGC_SET(COM9_AGC_GAIN_8x)},
+    {0x2c, 0x0c},
+    {0x33, 0x78},
+    {0x3a, 0x33},
+    {0x3b, 0xfB},
+    {0x3e, 0x00},
+    {0x43, 0x11},
+    {0x16, 0x10},
+    {0x39, 0x92},
+    {0x35, 0xda},
+    {0x22, 0x1a},
+    {0x37, 0xc3},
+    {0x23, 0x00},
+    {ARCOM2, 0xc0},
+    {0x06, 0x88},
+    {0x07, 0xc0},
+    {COM4, 0x87},
+    {0x0e, 0x41},
+    {0x4c, 0x00},
+    {0x4a, 0x81},
+    {0x21, 0x99},
+    {AEW, 0x40},
+    {AEB, 0x38},
+    {VV, VV_AGC_TH_SET(8,2)},
+    {0x5c, 0x00},
+    {0x63, 0x00},
+    {HISTO_LOW, 0x70},
+    {HISTO_HIGH, 0x80},
+    {0x7c, 0x05},
+    {0x20, 0x80},
+    {0x28, 0x30},
+    {0x6c, 0x00},
+    {0x6d, 0x80},
+    {0x6e, 0x00},
+    {0x70, 0x02},
+    {0x71, 0x94},
+    {0x73, 0xc1},
+    {0x3d, 0x34},
+    {0x5a, 0x57},
+    {BD50, 0xbb},
+    {BD60, 0x9c},
+    {COM7, COM7_RES_CIF},
+    {HSTART, 0x11},
+    {HSTOP, 0x43},
+    {VSTART, 0x00},
+    {VSTOP, 0x25},
+    {REG32, 0x89},
+    {0x37, 0xc0},
+    {BD50, 0xca},
+    {BD60, 0xa8},
+    {0x6d, 0x00},
+    {0x3d, 0x38},
+    {BANK_SEL, BANK_DSP},
+    {0xe5, 0x7f},
+    {MC_BIST, MC_BIST_RESET | MC_BIST_BOOT_ROM_SEL},
+    {0x41, 0x24},
+    {RESET, RESET_JPEG | RESET_DVP},
+    {0x76, 0xff},
+    {0x33, 0xa0},
+    {0x42, 0x20},
+    {0x43, 0x18},
+    {0x4c, 0x00},
+    {CTRL3, CTRL3_WPC_EN | 0x10 },
+    {0x88, 0x3f},
+    {0xd7, 0x03},
+    {0xd9, 0x10},
+    {R_DVP_SP, R_DVP_SP_AUTO_MODE | 0x02},
+    {0xc8, 0x08},
+    {0xc9, 0x80},
+    {BPADDR, 0x00},
+    {BPDATA, 0x00},
+    {BPADDR, 0x03},
+    {BPDATA, 0x48},
+    {BPDATA, 0x48},
+    {BPADDR, 0x08},
+    {BPDATA, 0x20},
+    {BPDATA, 0x10},
+    {BPDATA, 0x0e},
+    {0x90, 0x00},
+    {0x91, 0x0e},
+    {0x91, 0x1a},
+    {0x91, 0x31},
+    {0x91, 0x5a},
+    {0x91, 0x69},
+    {0x91, 0x75},
+    {0x91, 0x7e},
+    {0x91, 0x88},
+    {0x91, 0x8f},
+    {0x91, 0x96},
+    {0x91, 0xa3},
+    {0x91, 0xaf},
+    {0x91, 0xc4},
+    {0x91, 0xd7},
+    {0x91, 0xe8},
+    {0x91, 0x20},
+    {0x92, 0x00},
+    {0x93, 0x06},
+    {0x93, 0xe3},
+    {0x93, 0x05},
+    {0x93, 0x05},
+    {0x93, 0x00},
+    {0x93, 0x04},
+    {0x93, 0x00},
+    {0x93, 0x00},
+    {0x93, 0x00},
+    {0x93, 0x00},
+    {0x93, 0x00},
+    {0x93, 0x00},
+    {0x93, 0x00},
+    {0x96, 0x00},
+    {0x97, 0x08},
+    {0x97, 0x19},
+    {0x97, 0x02},
+    {0x97, 0x0c},
+    {0x97, 0x24},
+    {0x97, 0x30},
+    {0x97, 0x28},
+    {0x97, 0x26},
+    {0x97, 0x02},
+    {0x97, 0x98},
+    {0x97, 0x80},
+    {0x97, 0x00},
+    {0x97, 0x00},
+    {0xa4, 0x00},
+    {0xa8, 0x00},
+    {0xc5, 0x11},
+    {0xc6, 0x51},
+    {0xbf, 0x80},
+    {0xc7, 0x10},
+    {0xb6, 0x66},
+    {0xb8, 0xA5},
+    {0xb7, 0x64},
+    {0xb9, 0x7C},
+    {0xb3, 0xaf},
+    {0xb4, 0x97},
+    {0xb5, 0xFF},
+    {0xb0, 0xC5},
+    {0xb1, 0x94},
+    {0xb2, 0x0f},
+    {0xc4, 0x5c},
+    {CTRL1, 0xfd},
+    {0x7f, 0x00},
+    {0xe5, 0x1f},
+    {0xe1, 0x67},
+    {0xdd, 0x7f},
+    {IMAGE_MODE, 0x00},
+    {RESET, 0x00},
+    {R_BYPASS, R_BYPASS_DSP_EN},
+    {0, 0}
+};
+
+const DRAM_ATTR uint8_t ov2640_settings_to_cif[][2] = {
+    {BANK_SEL, BANK_SENSOR},
+    {COM7, COM7_RES_CIF},
+
+    //Set the sensor output window
+    {COM1, 0x0A},
+    {REG32, REG32_CIF},
+    {HSTART, 0x11},
+    {HSTOP, 0x43},
+    {VSTART, 0x00},
+    {VSTOP, 0x25},
+
+    //{CLKRC, 0x00},
+    {BD50, 0xca},
+    {BD60, 0xa8},
+    {0x5a, 0x23},
+    {0x6d, 0x00},
+    {0x3d, 0x38},
+    {0x39, 0x92},
+    {0x35, 0xda},
+    {0x22, 0x1a},
+    {0x37, 0xc3},
+    {0x23, 0x00},
+    {ARCOM2, 0xc0},
+    {0x06, 0x88},
+    {0x07, 0xc0},
+    {COM4, 0x87},
+    {0x0e, 0x41},
+    {0x4c, 0x00},
+    {BANK_SEL, BANK_DSP},
+    {RESET, RESET_DVP},
+
+    //Set the sensor resolution (UXGA, SVGA, CIF)
+    {HSIZE8, 0x32},
+    {VSIZE8, 0x25},
+    {SIZEL, 0x00},
+
+    //Set the image window size >= output size
+    {HSIZE, 0x64},
+    {VSIZE, 0x4a},
+    {XOFFL, 0x00},
+    {YOFFL, 0x00},
+    {VHYX, 0x00},
+    {TEST, 0x00},
+
+    {CTRL2, CTRL2_DCW_EN | 0x1D},
+    {CTRLI, CTRLI_LP_DP | 0x00},
+    //{R_DVP_SP, 0x08},
+    {0, 0}
+};
+
+const DRAM_ATTR uint8_t ov2640_settings_to_svga[][2] = {
+    {BANK_SEL, BANK_SENSOR},
+    {COM7, COM7_RES_SVGA},
+
+    //Set the sensor output window
+    {COM1, 0x0A},
+    {REG32, REG32_SVGA},
+    {HSTART, 0x11},
+    {HSTOP, 0x43},
+    {VSTART, 0x00},
+    {VSTOP, 0x4b},
+
+    //{CLKRC, 0x00},
+    {0x37, 0xc0},
+    {BD50, 0xca},
+    {BD60, 0xa8},
+    {0x5a, 0x23},
+    {0x6d, 0x00},
+    {0x3d, 0x38},
+    {0x39, 0x92},
+    {0x35, 0xda},
+    {0x22, 0x1a},
+    {0x37, 0xc3},
+    {0x23, 0x00},
+    {ARCOM2, 0xc0},
+    {0x06, 0x88},
+    {0x07, 0xc0},
+    {COM4, 0x87},
+    {0x0e, 0x41},
+    {0x42, 0x03},
+    {0x4c, 0x00},
+    {BANK_SEL, BANK_DSP},
+    {RESET, RESET_DVP},
+
+    //Set the sensor resolution (UXGA, SVGA, CIF)
+    {HSIZE8, 0x64},
+    {VSIZE8, 0x4B},
+    {SIZEL, 0x00},
+
+    //Set the image window size >= output size
+    {HSIZE, 0xC8},
+    {VSIZE, 0x96},
+    {XOFFL, 0x00},
+    {YOFFL, 0x00},
+    {VHYX, 0x00},
+    {TEST, 0x00},
+
+    {CTRL2, CTRL2_DCW_EN | 0x1D},
+    {CTRLI, CTRLI_LP_DP | 0x00},
+    //{R_DVP_SP, 0x08},
+    {0, 0}
+};
+
+const DRAM_ATTR uint8_t ov2640_settings_to_uxga[][2] = {
+    {BANK_SEL, BANK_SENSOR},
+    {COM7, COM7_RES_UXGA},
+
+    //Set the sensor output window
+    {COM1, 0x0F},
+    {REG32, REG32_UXGA},
+    {HSTART, 0x11},
+    {HSTOP, 0x75},
+    {VSTART, 0x01},
+    {VSTOP, 0x97},
+
+    //{CLKRC, 0x00},
+    {0x3d, 0x34},
+    {BD50, 0xbb},
+    {BD60, 0x9c},
+    {0x5a, 0x57},
+    {0x6d, 0x80},
+    {0x39, 0x82},
+    {0x23, 0x00},
+    {0x07, 0xc0},
+    {0x4c, 0x00},
+    {0x35, 0x88},
+    {0x22, 0x0a},
+    {0x37, 0x40},
+    {ARCOM2, 0xa0},
+    {0x06, 0x02},
+    {COM4, 0xb7},
+    {0x0e, 0x01},
+    {0x42, 0x83},
+    {BANK_SEL, BANK_DSP},
+    {RESET, RESET_DVP},
+
+    //Set the sensor resolution (UXGA, SVGA, CIF)
+    {HSIZE8, 0xc8},
+    {VSIZE8, 0x96},
+    {SIZEL, 0x00},
+
+    //Set the image window size >= output size
+    {HSIZE, 0x90},
+    {VSIZE, 0x2c},
+    {XOFFL, 0x00},
+    {YOFFL, 0x00},
+    {VHYX, 0x88},
+    {TEST, 0x00},
+
+    {CTRL2, CTRL2_DCW_EN | 0x1d},
+    {CTRLI, 0x00},
+    //{R_DVP_SP, 0x06},
+    {0, 0}
+};
+
+const DRAM_ATTR uint8_t ov2640_settings_jpeg3[][2] = {
+    {BANK_SEL, BANK_DSP},
+    {RESET, RESET_JPEG | RESET_DVP},
+    {IMAGE_MODE, IMAGE_MODE_JPEG_EN | IMAGE_MODE_HREF_VSYNC},
+    {0xD7, 0x03},
+    {0xE1, 0x77},
+    {0xE5, 0x1F},
+    {0xD9, 0x10},
+    {0xDF, 0x80},
+    {0x33, 0x80},
+    {0x3C, 0x10},
+    {0xEB, 0x30},
+    {0xDD, 0x7F},
+    {RESET, 0x00},
+    {0, 0}
+};
+
+static const uint8_t ov2640_settings_yuv422[][2] = {
+    {BANK_SEL, BANK_DSP},
+    {RESET, RESET_DVP},
+    {IMAGE_MODE, IMAGE_MODE_YUV422},
+    {0xD7, 0x01},
+    {0xE1, 0x67},
+    {RESET, 0x00},
+    {0, 0},
+};
+
+static const uint8_t ov2640_settings_rgb565[][2] = {
+    {BANK_SEL, BANK_DSP},
+    {RESET, RESET_DVP},
+    {IMAGE_MODE, IMAGE_MODE_RGB565},
+    {0xD7, 0x03},
+    {0xE1, 0x77},
+    {RESET, 0x00},
+    {0, 0},
+};
+
+#define NUM_BRIGHTNESS_LEVELS (5)
+static const uint8_t brightness_regs[NUM_BRIGHTNESS_LEVELS + 1][5] = {
+    {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA },
+    {0x00, 0x04, 0x09, 0x00, 0x00 }, /* -2 */
+    {0x00, 0x04, 0x09, 0x10, 0x00 }, /* -1 */
+    {0x00, 0x04, 0x09, 0x20, 0x00 }, /*  0 */
+    {0x00, 0x04, 0x09, 0x30, 0x00 }, /* +1 */
+    {0x00, 0x04, 0x09, 0x40, 0x00 }, /* +2 */
+};
+
+#define NUM_CONTRAST_LEVELS (5)
+static const uint8_t contrast_regs[NUM_CONTRAST_LEVELS + 1][7] = {
+    {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA, BPDATA, BPDATA },
+    {0x00, 0x04, 0x07, 0x20, 0x18, 0x34, 0x06 }, /* -2 */
+    {0x00, 0x04, 0x07, 0x20, 0x1c, 0x2a, 0x06 }, /* -1 */
+    {0x00, 0x04, 0x07, 0x20, 0x20, 0x20, 0x06 }, /*  0 */
+    {0x00, 0x04, 0x07, 0x20, 0x24, 0x16, 0x06 }, /* +1 */
+    {0x00, 0x04, 0x07, 0x20, 0x28, 0x0c, 0x06 }, /* +2 */
+};
+
+#define NUM_SATURATION_LEVELS (5)
+static const uint8_t saturation_regs[NUM_SATURATION_LEVELS + 1][5] = {
+    {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA },
+    {0x00, 0x02, 0x03, 0x28, 0x28 }, /* -2 */
+    {0x00, 0x02, 0x03, 0x38, 0x38 }, /* -1 */
+    {0x00, 0x02, 0x03, 0x48, 0x48 }, /*  0 */
+    {0x00, 0x02, 0x03, 0x58, 0x58 }, /* +1 */
+    {0x00, 0x02, 0x03, 0x68, 0x68 }, /* +2 */
+};
+
+#define NUM_SPECIAL_EFFECTS (7)
+static const uint8_t special_effects_regs[NUM_SPECIAL_EFFECTS + 1][5] = {
+    {BPADDR, BPDATA, BPADDR, BPDATA, BPDATA },
+    {0x00, 0X00, 0x05, 0X80, 0X80 }, /* no effect */
+    {0x00, 0X40, 0x05, 0X80, 0X80 }, /* negative */
+    {0x00, 0X18, 0x05, 0X80, 0X80 }, /* black and white */
+    {0x00, 0X18, 0x05, 0X40, 0XC0 }, /* reddish */
+    {0x00, 0X18, 0x05, 0X40, 0X40 }, /* greenish */
+    {0x00, 0X18, 0x05, 0XA0, 0X40 }, /* blue */
+    {0x00, 0X18, 0x05, 0X40, 0XA6 }, /* retro */
+};
+
+#define NUM_WB_MODES (4)
+static const uint8_t wb_modes_regs[NUM_WB_MODES + 1][3] = {
+    {0XCC, 0XCD, 0XCE },
+    {0x5E, 0X41, 0x54 }, /* sunny */
+    {0x65, 0X41, 0x4F }, /* cloudy */
+    {0x52, 0X41, 0x66 }, /* office */
+    {0x42, 0X3F, 0x71 }, /* home */
+};
+
+#define NUM_AE_LEVELS (5)
+static const uint8_t ae_levels_regs[NUM_AE_LEVELS + 1][3] = {
+    { AEW,  AEB,  VV  },
+    {0x20, 0X18, 0x60 },
+    {0x34, 0X1C, 0x00 },
+    {0x3E, 0X38, 0x81 },
+    {0x48, 0X40, 0x81 },
+    {0x58, 0X50, 0x92 },
+};
+
+const uint8_t agc_gain_tbl[31] = {
+    0x00, 0x10, 0x18, 0x30, 0x34, 0x38, 0x3C, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7A, 0x7C, 0x7E, 0xF0,
+    0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF
+};
+
+#endif /* _OV2640_SETTINGS_H_ */

+ 1033 - 0
code/lib/sensors/ov3660.c

@@ -0,0 +1,1033 @@
+/*
+ * This file is part of the OpenMV project.
+ * Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
+ * This work is licensed under the MIT license, see the file LICENSE for details.
+ *
+ * OV3660 driver.
+ *
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include "sccb.h"
+#include "ov3660.h"
+#include "ov3660_regs.h"
+#include "ov3660_settings.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
+#include "esp32-hal-log.h"
+#else
+#include "esp_log.h"
+static const char *TAG = "ov3660";
+#endif
+
+//#define REG_DEBUG_ON
+
+static int read_reg(uint8_t slv_addr, const uint16_t reg){
+    int ret = SCCB_Read16(slv_addr, reg);
+#ifdef REG_DEBUG_ON
+    if (ret < 0) {
+        ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret);
+    }
+#endif
+    return ret;
+}
+
+static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask){
+    return (read_reg(slv_addr, reg) & mask) == mask;
+}
+
+static int read_reg16(uint8_t slv_addr, const uint16_t reg){
+    int ret = 0, ret2 = 0;
+    ret = read_reg(slv_addr, reg);
+    if (ret >= 0) {
+        ret = (ret & 0xFF) << 8;
+        ret2 = read_reg(slv_addr, reg+1);
+        if (ret2 < 0) {
+            ret = ret2;
+        } else {
+            ret |= ret2 & 0xFF;
+        }
+    }
+    return ret;
+}
+
+
+static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value){
+    int ret = 0;
+#ifndef REG_DEBUG_ON
+    ret = SCCB_Write16(slv_addr, reg, value);
+#else
+    int old_value = read_reg(slv_addr, reg);
+    if (old_value < 0) {
+        return old_value;
+    }
+    if ((uint8_t)old_value != value) {
+        ESP_LOGI(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value);
+        ret = SCCB_Write16(slv_addr, reg, value);
+    } else {
+        ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value);
+        ret = SCCB_Write16(slv_addr, reg, value);//maybe not?
+    }
+    if (ret < 0) {
+        ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret);
+    }
+#endif
+    return ret;
+}
+
+static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value)
+{
+    int ret = 0;
+    uint8_t c_value, new_value;
+    ret = read_reg(slv_addr, reg);
+    if(ret < 0) {
+        return ret;
+    }
+    c_value = ret;
+    new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset);
+    ret = write_reg(slv_addr, reg, new_value);
+    return ret;
+}
+
+static int write_regs(uint8_t slv_addr, const uint16_t (*regs)[2])
+{
+    int i = 0, ret = 0;
+    while (!ret && regs[i][0] != REGLIST_TAIL) {
+        if (regs[i][0] == REG_DLY) {
+            vTaskDelay(regs[i][1] / portTICK_PERIOD_MS);
+        } else {
+            ret = write_reg(slv_addr, regs[i][0], regs[i][1]);
+        }
+        i++;
+    }
+    return ret;
+}
+
+static int write_reg16(uint8_t slv_addr, const uint16_t reg, uint16_t value)
+{
+    if (write_reg(slv_addr, reg, value >> 8) || write_reg(slv_addr, reg + 1, value)) {
+        return -1;
+    }
+    return 0;
+}
+
+static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value, uint16_t y_value)
+{
+    if (write_reg16(slv_addr, reg, x_value) || write_reg16(slv_addr, reg + 2, y_value)) {
+        return -1;
+    }
+    return 0;
+}
+
+#define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, enable?mask:0)
+
+static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sys_div, int pll_pre_div, bool pll_root_2x, int pll_seld5, bool pclk_manual, int pclk_div)
+{
+    const int pll_pre_div2x_map[] = { 2, 3, 4, 6 };//values are multiplied by two to avoid floats
+    const int pll_seld52x_map[] = { 2, 2, 4, 5 };
+
+    if(!pll_sys_div) {
+        pll_sys_div = 1;
+    }
+
+    int pll_pre_div2x = pll_pre_div2x_map[pll_pre_div];
+    int pll_root_div = pll_root_2x?2:1;
+    int pll_seld52x = pll_seld52x_map[pll_seld5];
+
+    int VCO = (xclk / 1000) * pll_multiplier * pll_root_div * 2 / pll_pre_div2x;
+    int PLLCLK = pll_bypass?(xclk):(VCO * 1000 * 2 / pll_sys_div / pll_seld52x);
+    int PCLK = PLLCLK / 2 / ((pclk_manual && pclk_div)?pclk_div:1);
+    int SYSCLK = PLLCLK / 4;
+
+    ESP_LOGD(TAG, "Calculated VCO: %d Hz, PLLCLK: %d Hz, SYSCLK: %d Hz, PCLK: %d Hz", VCO*1000, PLLCLK, SYSCLK, PCLK);
+    return SYSCLK;
+}
+
+static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sys_div, uint8_t pre_div, bool root_2x, uint8_t seld5, bool pclk_manual, uint8_t pclk_div){
+    int ret = 0;
+    if(multiplier > 31 || sys_div > 15 || pre_div > 3 || pclk_div > 31 || seld5 > 3){
+        ESP_LOGE(TAG, "Invalid arguments");
+        return -1;
+    }
+
+    calc_sysclk(sensor->xclk_freq_hz, bypass, multiplier, sys_div, pre_div, root_2x, seld5, pclk_manual, pclk_div);
+
+    ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL0, bypass?0x80:0x00);
+    if (ret == 0) {
+        ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL1, multiplier & 0x1f);
+    }
+    if (ret == 0) {
+        ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL2, 0x10 | (sys_div & 0x0f));
+    }
+    if (ret == 0) {
+        ret = write_reg(sensor->slv_addr, SC_PLLS_CTRL3, (pre_div & 0x3) << 4 | seld5 | (root_2x?0x40:0x00));
+    }
+    if (ret == 0) {
+        ret = write_reg(sensor->slv_addr, PCLK_RATIO, pclk_div & 0x1f);
+    }
+    if (ret == 0) {
+        ret = write_reg(sensor->slv_addr, VFIFO_CTRL0C, pclk_manual?0x22:0x20);
+    }
+    if(ret){
+        ESP_LOGE(TAG, "set_sensor_pll FAILED!");
+    }
+    return ret;
+}
+
+static int set_ae_level(sensor_t *sensor, int level);
+
+static int reset(sensor_t *sensor)
+{
+    int ret = 0;
+    // Software Reset: clear all registers and reset them to their default values
+    ret = write_reg(sensor->slv_addr, SYSTEM_CTROL0, 0x82);
+    if(ret){
+        ESP_LOGE(TAG, "Software Reset FAILED!");
+        return ret;
+    }
+    vTaskDelay(100 / portTICK_PERIOD_MS);
+    ret = write_regs(sensor->slv_addr, sensor_default_regs);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Camera defaults loaded");
+        ret = set_ae_level(sensor, 0);
+        vTaskDelay(100 / portTICK_PERIOD_MS);
+    }
+    return ret;
+}
+
+static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
+{
+    int ret = 0;
+    const uint16_t (*regs)[2];
+
+    switch (pixformat) {
+    case PIXFORMAT_YUV422:
+        regs = sensor_fmt_yuv422;
+        break;
+
+    case PIXFORMAT_GRAYSCALE:
+        regs = sensor_fmt_grayscale;
+        break;
+
+    case PIXFORMAT_RGB565:
+    case PIXFORMAT_RGB888:
+        regs = sensor_fmt_rgb565;
+        break;
+
+    case PIXFORMAT_JPEG:
+        regs = sensor_fmt_jpeg;
+        break;
+
+    case PIXFORMAT_RAW:
+        regs = sensor_fmt_raw;
+        break;
+
+    default:
+        ESP_LOGE(TAG, "Unsupported pixformat: %u", pixformat);
+        return -1;
+    }
+
+    ret = write_regs(sensor->slv_addr, regs);
+    if(ret == 0) {
+        sensor->pixformat = pixformat;
+        ESP_LOGD(TAG, "Set pixformat to: %u", pixformat);
+    }
+    return ret;
+}
+
+static int set_image_options(sensor_t *sensor)
+{
+    int ret = 0;
+    uint8_t reg20 = 0;
+    uint8_t reg21 = 0;
+    uint8_t reg4514 = 0;
+    uint8_t reg4514_test = 0;
+
+    // compression
+    if (sensor->pixformat == PIXFORMAT_JPEG) {
+        reg21 |= 0x20;
+    }
+
+    // binning
+    if (sensor->status.binning) {
+        reg20 |= 0x01;
+        reg21 |= 0x01;
+        reg4514_test |= 4;
+    } else {
+        reg20 |= 0x40;
+    }
+
+    // V-Flip
+    if (sensor->status.vflip) {
+        reg20 |= 0x06;
+        reg4514_test |= 1;
+    }
+
+    // H-Mirror
+    if (sensor->status.hmirror) {
+        reg21 |= 0x06;
+        reg4514_test |= 2;
+    }
+
+    switch (reg4514_test) {
+        //no binning
+        case 0: reg4514 = 0x88; break;//normal
+        case 1: reg4514 = 0x88; break;//v-flip
+        case 2: reg4514 = 0xbb; break;//h-mirror
+        case 3: reg4514 = 0xbb; break;//v-flip+h-mirror
+        //binning
+        case 4: reg4514 = 0xaa; break;//normal
+        case 5: reg4514 = 0xbb; break;//v-flip
+        case 6: reg4514 = 0xbb; break;//h-mirror
+        case 7: reg4514 = 0xaa; break;//v-flip+h-mirror
+    }
+
+    if(write_reg(sensor->slv_addr, TIMING_TC_REG20, reg20)
+        || write_reg(sensor->slv_addr, TIMING_TC_REG21, reg21)
+        || write_reg(sensor->slv_addr, 0x4514, reg4514)){
+        ESP_LOGE(TAG, "Setting Image Options Failed");
+        ret = -1;
+    }
+
+    if (sensor->status.binning) {
+        ret  = write_reg(sensor->slv_addr, 0x4520, 0x0b)
+            || write_reg(sensor->slv_addr, X_INCREMENT, 0x31)//odd:3, even: 1
+            || write_reg(sensor->slv_addr, Y_INCREMENT, 0x31);//odd:3, even: 1
+    } else {
+        ret  = write_reg(sensor->slv_addr, 0x4520, 0xb0)
+            || write_reg(sensor->slv_addr, X_INCREMENT, 0x11)//odd:1, even: 1
+            || write_reg(sensor->slv_addr, Y_INCREMENT, 0x11);//odd:1, even: 1
+    }
+
+    ESP_LOGD(TAG, "Set Image Options: Compression: %u, Binning: %u, V-Flip: %u, H-Mirror: %u, Reg-4514: 0x%02x",
+        sensor->pixformat == PIXFORMAT_JPEG, sensor->status.binning, sensor->status.vflip, sensor->status.hmirror, reg4514);
+    return ret;
+}
+
+static int set_framesize(sensor_t *sensor, framesize_t framesize)
+{
+    int ret = 0;
+    framesize_t old_framesize = sensor->status.framesize;
+    sensor->status.framesize = framesize;
+
+    if(framesize > FRAMESIZE_QXGA){
+        ESP_LOGE(TAG, "Invalid framesize: %u", framesize);
+        return -1;
+    }
+    uint16_t w = resolution[framesize].width;
+    uint16_t h = resolution[framesize].height;
+    aspect_ratio_t ratio = resolution[sensor->status.framesize].aspect_ratio;
+    ratio_settings_t settings = ratio_table[ratio];
+
+    sensor->status.binning = (w <= (settings.max_width / 2) && h <= (settings.max_height / 2));
+    sensor->status.scale = !((w == settings.max_width && h == settings.max_height)
+        || (w == (settings.max_width / 2) && h == (settings.max_height / 2)));
+
+    ret  = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, settings.start_x, settings.start_y)
+        || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, settings.end_x, settings.end_y)
+        || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, w, h);
+
+    if (ret) {
+        goto fail;
+    }
+
+    if (sensor->status.binning) {
+        ret  = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x, (settings.total_y / 2) + 1)
+            || write_addr_reg(sensor->slv_addr, X_OFFSET_H, 8, 2);
+    } else {
+        ret  = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x, settings.total_y)
+            || write_addr_reg(sensor->slv_addr, X_OFFSET_H, 16, 6);
+    }
+
+    if (ret == 0) {
+        ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, sensor->status.scale);
+    }
+
+    if (ret == 0) {
+        ret = set_image_options(sensor);
+    }
+
+    if (ret) {
+        goto fail;
+    }
+
+    if (sensor->pixformat == PIXFORMAT_JPEG) {
+        if (framesize == FRAMESIZE_QXGA) {
+            //40MHz SYSCLK and 10MHz PCLK
+            ret = set_pll(sensor, false, 24, 1, 3, false, 0, true, 8);
+        } else {
+            //50MHz SYSCLK and 10MHz PCLK
+            ret = set_pll(sensor, false, 30, 1, 3, false, 0, true, 10);
+        }
+    } else {
+        if (framesize > FRAMESIZE_CIF) {
+            //10MHz SYSCLK and 10MHz PCLK (6.19 FPS)
+            ret = set_pll(sensor, false, 2, 1, 0, false, 0, true, 2);
+        } else {
+            //25MHz SYSCLK and 10MHz PCLK (15.45 FPS)
+            ret = set_pll(sensor, false, 5, 1, 0, false, 0, true, 5);
+        }
+    }
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set framesize to: %ux%u", w, h);
+    }
+    return ret;
+
+fail:
+    sensor->status.framesize = old_framesize;
+    ESP_LOGE(TAG, "Setting framesize to: %ux%u failed", w, h);
+    return ret;
+}
+
+static int set_hmirror(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    sensor->status.hmirror = enable;
+    ret = set_image_options(sensor);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set h-mirror to: %d", enable);
+    }
+    return ret;
+}
+
+static int set_vflip(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    sensor->status.vflip = enable;
+    ret = set_image_options(sensor);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set v-flip to: %d", enable);
+    }
+    return ret;
+}
+
+static int set_quality(sensor_t *sensor, int qs)
+{
+    int ret = 0;
+    ret = write_reg(sensor->slv_addr, COMPRESSION_CTRL07, qs & 0x3f);
+    if (ret == 0) {
+        sensor->status.quality = qs;
+        ESP_LOGD(TAG, "Set quality to: %d", qs);
+    }
+    return ret;
+}
+
+static int set_colorbar(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR, enable);
+    if (ret == 0) {
+        sensor->status.colorbar = enable;
+        ESP_LOGD(TAG, "Set colorbar to: %d", enable);
+    }
+    return ret;
+}
+
+static int set_gain_ctrl(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN, !enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set gain_ctrl to: %d", enable);
+        sensor->status.agc = enable;
+    }
+    return ret;
+}
+
+static int set_exposure_ctrl(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN, !enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set exposure_ctrl to: %d", enable);
+        sensor->status.aec = enable;
+    }
+    return ret;
+}
+
+static int set_whitebal(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x01, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set awb to: %d", enable);
+        sensor->status.awb = enable;
+    }
+    return ret;
+}
+
+//Advanced AWB
+static int set_dcw_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, 0x5183, 0x80, !enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set dcw to: %d", enable);
+        sensor->status.dcw = enable;
+    }
+    return ret;
+}
+
+//night mode enable
+static int set_aec2(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, 0x3a00, 0x04, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set aec2 to: %d", enable);
+        sensor->status.aec2 = enable;
+    }
+    return ret;
+}
+
+static int set_bpc_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x04, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set bpc to: %d", enable);
+        sensor->status.bpc = enable;
+    }
+    return ret;
+}
+
+static int set_wpc_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x02, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set wpc to: %d", enable);
+        sensor->status.wpc = enable;
+    }
+    return ret;
+}
+
+//Gamma enable
+static int set_raw_gma_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x20, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set raw_gma to: %d", enable);
+        sensor->status.raw_gma = enable;
+    }
+    return ret;
+}
+
+static int set_lenc_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x80, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set lenc to: %d", enable);
+        sensor->status.lenc = enable;
+    }
+    return ret;
+}
+
+static int get_agc_gain(sensor_t *sensor)
+{
+    int ra = read_reg(sensor->slv_addr, 0x350a);
+    if (ra < 0) {
+        return 0;
+    }
+    int rb = read_reg(sensor->slv_addr, 0x350b);
+    if (rb < 0) {
+        return 0;
+    }
+    int res = (rb & 0xF0) >> 4 | (ra & 0x03) << 4;
+    if (rb & 0x0F) {
+        res += 1;
+    }
+    return res;
+}
+
+//real gain
+static int set_agc_gain(sensor_t *sensor, int gain)
+{
+    int ret = 0;
+    if(gain < 0) {
+        gain = 0;
+    } else if(gain > 64) {
+        gain = 64;
+    }
+
+    //gain value is 6.4 bits float
+    //in order to use the max range, we deduct 1/16
+    int gainv = gain << 4;
+    if(gainv){
+        gainv -= 1;
+    }
+
+    ret = write_reg(sensor->slv_addr, 0x350a, gainv >> 8) || write_reg(sensor->slv_addr, 0x350b, gainv & 0xff);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set agc_gain to: %d", gain);
+        sensor->status.agc_gain = gain;
+    }
+    return ret;
+}
+
+static int get_aec_value(sensor_t *sensor)
+{
+    int ra = read_reg(sensor->slv_addr, 0x3500);
+    if (ra < 0) {
+        return 0;
+    }
+    int rb = read_reg(sensor->slv_addr, 0x3501);
+    if (rb < 0) {
+        return 0;
+    }
+    int rc = read_reg(sensor->slv_addr, 0x3502);
+    if (rc < 0) {
+        return 0;
+    }
+    int res = (ra & 0x0F) << 12 | (rb & 0xFF) << 4 | (rc & 0xF0) >> 4;
+    return res;
+}
+
+static int set_aec_value(sensor_t *sensor, int value)
+{
+    int ret = 0, max_val = 0;
+    max_val = read_reg16(sensor->slv_addr, 0x380e);
+    if (max_val < 0) {
+        ESP_LOGE(TAG, "Could not read max aec_value");
+        return -1;
+    }
+    if (value > max_val) {
+        value =max_val;
+    }
+
+    ret =  write_reg(sensor->slv_addr, 0x3500, (value >> 12) & 0x0F)
+        || write_reg(sensor->slv_addr, 0x3501, (value >> 4) & 0xFF)
+        || write_reg(sensor->slv_addr, 0x3502, (value << 4) & 0xF0);
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set aec_value to: %d / %d", value, max_val);
+        sensor->status.aec_value = value;
+    }
+    return ret;
+}
+
+static int set_ae_level(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    if (level < -5 || level > 5) {
+        return -1;
+    }
+    //good targets are between 5 and 115
+    int target_level = ((level + 5) * 10) + 5;
+
+    int level_high, level_low;
+    int fast_high, fast_low;
+
+    level_low = target_level * 23 / 25; //0.92 (0.46)
+    level_high = target_level * 27 / 25; //1.08 (2.08)
+
+    fast_low = level_low >> 1;
+    fast_high = level_high << 1;
+
+    if(fast_high>255) {
+        fast_high = 255;
+    }
+
+    ret =  write_reg(sensor->slv_addr, 0x3a0f, level_high)
+        || write_reg(sensor->slv_addr, 0x3a10, level_low)
+        || write_reg(sensor->slv_addr, 0x3a1b, level_high)
+        || write_reg(sensor->slv_addr, 0x3a1e, level_low)
+        || write_reg(sensor->slv_addr, 0x3a11, fast_high)
+        || write_reg(sensor->slv_addr, 0x3a1f, fast_low);
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set ae_level to: %d", level);
+        sensor->status.ae_level = level;
+    }
+    return ret;
+}
+
+static int set_wb_mode(sensor_t *sensor, int mode)
+{
+    int ret = 0;
+    if (mode < 0 || mode > 4) {
+        return -1;
+    }
+
+    ret = write_reg(sensor->slv_addr, 0x3406, (mode != 0));
+    if (ret) {
+        return ret;
+    }
+    switch (mode) {
+        case 1://Sunny
+            ret  = write_reg16(sensor->slv_addr, 0x3400, 0x5e0) //AWB R GAIN
+                || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN
+                || write_reg16(sensor->slv_addr, 0x3404, 0x540);//AWB B GAIN
+            break;
+        case 2://Cloudy
+            ret  = write_reg16(sensor->slv_addr, 0x3400, 0x650) //AWB R GAIN
+                || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN
+                || write_reg16(sensor->slv_addr, 0x3404, 0x4f0);//AWB B GAIN
+            break;
+        case 3://Office
+            ret  = write_reg16(sensor->slv_addr, 0x3400, 0x520) //AWB R GAIN
+                || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN
+                || write_reg16(sensor->slv_addr, 0x3404, 0x660);//AWB B GAIN
+            break;
+        case 4://HOME
+            ret  = write_reg16(sensor->slv_addr, 0x3400, 0x420) //AWB R GAIN
+                || write_reg16(sensor->slv_addr, 0x3402, 0x3f0) //AWB G GAIN
+                || write_reg16(sensor->slv_addr, 0x3404, 0x710);//AWB B GAIN
+            break;
+        default://AUTO
+            break;
+    }
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set wb_mode to: %d", mode);
+        sensor->status.wb_mode = mode;
+    }
+    return ret;
+}
+
+static int set_awb_gain_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    int old_mode = sensor->status.wb_mode;
+    int mode = enable?old_mode:0;
+
+    ret = set_wb_mode(sensor, mode);
+
+    if (ret == 0) {
+        sensor->status.wb_mode = old_mode;
+        ESP_LOGD(TAG, "Set awb_gain to: %d", enable);
+        sensor->status.awb_gain = enable;
+    }
+    return ret;
+}
+
+static int set_special_effect(sensor_t *sensor, int effect)
+{
+    int ret=0;
+    if (effect < 0 || effect > 6) {
+        return -1;
+    }
+
+    uint8_t * regs = (uint8_t *)sensor_special_effects[effect];
+    ret =  write_reg(sensor->slv_addr, 0x5580, regs[0])
+        || write_reg(sensor->slv_addr, 0x5583, regs[1])
+        || write_reg(sensor->slv_addr, 0x5584, regs[2])
+        || write_reg(sensor->slv_addr, 0x5003, regs[3]);
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set special_effect to: %d", effect);
+        sensor->status.special_effect = effect;
+    }
+    return ret;
+}
+
+static int set_brightness(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    uint8_t value = 0;
+    bool negative = false;
+
+    switch (level) {
+        case 3:
+            value = 0x30;
+            break;
+        case 2:
+            value = 0x20;
+            break;
+        case 1:
+            value = 0x10;
+            break;
+        case -1:
+            value = 0x10;
+            negative = true;
+            break;
+        case -2:
+            value = 0x20;
+            negative = true;
+            break;
+        case -3:
+            value = 0x30;
+            negative = true;
+            break;
+        default: // 0
+            break;
+    }
+
+    ret = write_reg(sensor->slv_addr, 0x5587, value);
+    if (ret == 0) {
+        ret = write_reg_bits(sensor->slv_addr, 0x5588, 0x08, negative);
+    }
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set brightness to: %d", level);
+        sensor->status.brightness = level;
+    }
+    return ret;
+}
+
+static int set_contrast(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    if(level > 3 || level < -3) {
+        return -1;
+    }
+    ret = write_reg(sensor->slv_addr, 0x5586, (level + 4) << 3);
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set contrast to: %d", level);
+        sensor->status.contrast = level;
+    }
+    return ret;
+}
+
+static int set_saturation(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    if(level > 4 || level < -4) {
+        return -1;
+    }
+
+    uint8_t * regs = (uint8_t *)sensor_saturation_levels[level+4];
+    for(int i=0; i<11; i++) {
+        ret = write_reg(sensor->slv_addr, 0x5381 + i, regs[i]);
+        if (ret) {
+            break;
+        }
+    }
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set saturation to: %d", level);
+        sensor->status.saturation = level;
+    }
+    return ret;
+}
+
+static int set_sharpness(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    if(level > 3 || level < -3) {
+        return -1;
+    }
+
+    uint8_t mt_offset_2 = (level + 3) * 8;
+    uint8_t mt_offset_1 = mt_offset_2 + 1;
+
+    ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x40, false)//0x40 means auto
+        || write_reg(sensor->slv_addr, 0x5300, 0x10)
+        || write_reg(sensor->slv_addr, 0x5301, 0x10)
+        || write_reg(sensor->slv_addr, 0x5302, mt_offset_1)
+        || write_reg(sensor->slv_addr, 0x5303, mt_offset_2)
+        || write_reg(sensor->slv_addr, 0x5309, 0x10)
+        || write_reg(sensor->slv_addr, 0x530a, 0x10)
+        || write_reg(sensor->slv_addr, 0x530b, 0x04)
+        || write_reg(sensor->slv_addr, 0x530c, 0x06);
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set sharpness to: %d", level);
+        sensor->status.sharpness = level;
+    }
+    return ret;
+}
+
+static int set_gainceiling(sensor_t *sensor, gainceiling_t level)
+{
+    int ret = 0, l = (int)level;
+
+    ret = write_reg(sensor->slv_addr, 0x3A18, (l >> 8) & 3)
+       || write_reg(sensor->slv_addr, 0x3A19, l & 0xFF);
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set gainceiling to: %d", l);
+        sensor->status.gainceiling = l;
+    }
+    return ret;
+}
+
+static int get_denoise(sensor_t *sensor)
+{
+    if (!check_reg_mask(sensor->slv_addr, 0x5308, 0x10)) {
+        return 0;
+    }
+    return (read_reg(sensor->slv_addr, 0x5306) / 4) + 1;
+}
+
+static int set_denoise(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    if (level < 0 || level > 8) {
+        return -1;
+    }
+
+    ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x10, level > 0);
+    if (ret == 0 && level > 0) {
+        ret = write_reg(sensor->slv_addr, 0x5306, (level - 1) * 4);
+    }
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set denoise to: %d", level);
+        sensor->status.denoise = level;
+    }
+    return ret;
+}
+
+static int get_reg(sensor_t *sensor, int reg, int mask)
+{
+    int ret = 0, ret2 = 0;
+    if(mask > 0xFF){
+        ret = read_reg16(sensor->slv_addr, reg);
+        if(ret >= 0 && mask > 0xFFFF){
+            ret2 = read_reg(sensor->slv_addr, reg+2);
+            if(ret2 >= 0){
+                ret = (ret << 8) | ret2 ;
+            } else {
+                ret = ret2;
+            }
+        }
+    } else {
+        ret = read_reg(sensor->slv_addr, reg);
+    }
+    if(ret > 0){
+        ret &= mask;
+    }
+    return ret;
+}
+
+static int set_reg(sensor_t *sensor, int reg, int mask, int value)
+{
+    int ret = 0, ret2 = 0;
+    if(mask > 0xFF){
+        ret = read_reg16(sensor->slv_addr, reg);
+        if(ret >= 0 && mask > 0xFFFF){
+            ret2 = read_reg(sensor->slv_addr, reg+2);
+            if(ret2 >= 0){
+                ret = (ret << 8) | ret2 ;
+            } else {
+                ret = ret2;
+            }
+        }
+    } else {
+        ret = read_reg(sensor->slv_addr, reg);
+    }
+    if(ret < 0){
+        return ret;
+    }
+    value = (ret & ~mask) | (value & mask);
+    if(mask > 0xFFFF){
+        ret = write_reg16(sensor->slv_addr, reg, value >> 8);
+        if(ret >= 0){
+            ret = write_reg(sensor->slv_addr, reg+2, value & 0xFF);
+        }
+    } else if(mask > 0xFF){
+        ret = write_reg16(sensor->slv_addr, reg, value);
+    } else {
+        ret = write_reg(sensor->slv_addr, reg, value);
+    }
+    return ret;
+}
+
+static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning)
+{
+    int ret = 0;
+    ret  = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, startX, startY)
+        || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, endX, endY)
+        || write_addr_reg(sensor->slv_addr, X_OFFSET_H, offsetX, offsetY)
+        || write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, totalX, totalY)
+        || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, outputX, outputY)
+        || write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, scale);
+    if(!ret){
+        sensor->status.scale = scale;
+        sensor->status.binning = binning;
+        ret = set_image_options(sensor);
+    }
+    return ret;
+}
+
+static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div)
+{
+    return set_pll(sensor, bypass > 0, multiplier, sys_div, pre_div, root_2x > 0, seld5, pclk_manual > 0, pclk_div);
+}
+
+esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz);
+static int set_xclk(sensor_t *sensor, int timer, int xclk)
+{
+    int ret = 0;
+    sensor->xclk_freq_hz = xclk * 1000000U;
+    ret = xclk_timer_conf(timer, sensor->xclk_freq_hz);
+    return ret;
+}
+
+static int init_status(sensor_t *sensor)
+{
+    sensor->status.brightness = 0;
+    sensor->status.contrast = 0;
+    sensor->status.saturation = 0;
+    sensor->status.sharpness = (read_reg(sensor->slv_addr, 0x5303) / 8) - 3;
+    sensor->status.denoise = get_denoise(sensor);
+    sensor->status.ae_level = 0;
+    sensor->status.gainceiling = read_reg16(sensor->slv_addr, 0x3A18) & 0x3FF;
+    sensor->status.awb = check_reg_mask(sensor->slv_addr, ISP_CONTROL_01, 0x01);
+    sensor->status.dcw = !check_reg_mask(sensor->slv_addr, 0x5183, 0x80);
+    sensor->status.agc = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN);
+    sensor->status.aec = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN);
+    sensor->status.hmirror = check_reg_mask(sensor->slv_addr, TIMING_TC_REG21, TIMING_TC_REG21_HMIRROR);
+    sensor->status.vflip = check_reg_mask(sensor->slv_addr, TIMING_TC_REG20, TIMING_TC_REG20_VFLIP);
+    sensor->status.colorbar = check_reg_mask(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR);
+    sensor->status.bpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x04);
+    sensor->status.wpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x02);
+    sensor->status.raw_gma = check_reg_mask(sensor->slv_addr, 0x5000, 0x20);
+    sensor->status.lenc = check_reg_mask(sensor->slv_addr, 0x5000, 0x80);
+    sensor->status.quality = read_reg(sensor->slv_addr, COMPRESSION_CTRL07) & 0x3f;
+    sensor->status.special_effect = 0;
+    sensor->status.wb_mode = 0;
+    sensor->status.awb_gain = check_reg_mask(sensor->slv_addr, 0x3406, 0x01);
+    sensor->status.agc_gain = get_agc_gain(sensor);
+    sensor->status.aec_value = get_aec_value(sensor);
+    sensor->status.aec2 = check_reg_mask(sensor->slv_addr, 0x3a00, 0x04);
+    return 0;
+}
+
+int ov3660_init(sensor_t *sensor)
+{
+    sensor->reset = reset;
+    sensor->set_pixformat = set_pixformat;
+    sensor->set_framesize = set_framesize;
+    sensor->set_contrast = set_contrast;
+    sensor->set_brightness = set_brightness;
+    sensor->set_saturation = set_saturation;
+    sensor->set_sharpness = set_sharpness;
+    sensor->set_gainceiling = set_gainceiling;
+    sensor->set_quality = set_quality;
+    sensor->set_colorbar = set_colorbar;
+    sensor->set_gain_ctrl = set_gain_ctrl;
+    sensor->set_exposure_ctrl = set_exposure_ctrl;
+    sensor->set_whitebal = set_whitebal;
+    sensor->set_hmirror = set_hmirror;
+    sensor->set_vflip = set_vflip;
+    sensor->init_status = init_status;
+    sensor->set_aec2 = set_aec2;
+    sensor->set_aec_value = set_aec_value;
+    sensor->set_special_effect = set_special_effect;
+    sensor->set_wb_mode = set_wb_mode;
+    sensor->set_ae_level = set_ae_level;
+    sensor->set_dcw = set_dcw_dsp;
+    sensor->set_bpc = set_bpc_dsp;
+    sensor->set_wpc = set_wpc_dsp;
+    sensor->set_awb_gain = set_awb_gain_dsp;
+    sensor->set_agc_gain = set_agc_gain;
+    sensor->set_raw_gma = set_raw_gma_dsp;
+    sensor->set_lenc = set_lenc_dsp;
+    sensor->set_denoise = set_denoise;
+
+    sensor->get_reg = get_reg;
+    sensor->set_reg = set_reg;
+    sensor->set_res_raw = set_res_raw;
+    sensor->set_pll = _set_pll;
+    sensor->set_xclk = set_xclk;
+    return 0;
+}

+ 16 - 0
code/lib/sensors/ov3660.h

@@ -0,0 +1,16 @@
+/*
+ * This file is part of the OpenMV project.
+ * Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
+ * This work is licensed under the MIT license, see the file LICENSE for details.
+ *
+ * OV3660 driver.
+ *
+ */
+#ifndef __OV3660_H__
+#define __OV3660_H__
+
+#include "sensor.h"
+
+int ov3660_init(sensor_t *sensor);
+
+#endif // __OV3660_H__

+ 211 - 0
code/lib/sensors/ov3660_regs.h

@@ -0,0 +1,211 @@
+/*
+ * OV3660 register definitions.
+ */
+#ifndef __OV3660_REG_REGS_H__
+#define __OV3660_REG_REGS_H__
+
+/* system control registers */
+#define SYSTEM_CTROL0   0x3008  // Bit[7]: Software reset 
+                                // Bit[6]: Software power down 
+                                // Bit[5]: Reserved 
+                                // Bit[4]: SRB clock SYNC enable 
+                                // Bit[3]: Isolation suspend select 
+                                // Bit[2:0]: Not used
+
+/* output format control registers */
+#define FORMAT_CTRL     0x501F // Format select
+                                // Bit[2:0]:
+                                //  000: YUV422
+                                //  001: RGB
+                                //  010: Dither
+                                //  011: RAW after DPC
+                                //  101: RAW after CIP
+
+/* format control registers */
+#define FORMAT_CTRL00   0x4300
+
+/* frame control registers */
+#define FRAME_CTRL01    0x4201  // Control Passed Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode
+                                // Bit[7:4]: Not used
+                                // Bit[3:0]: Frame ON number
+#define FRAME_CTRL02    0x4202  // Control Masked Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode
+                                // Bit[7:4]: Not used
+                                // BIT[3:0]: Frame OFF number
+
+/* ISP top control registers */
+#define PRE_ISP_TEST_SETTING_1  0x503D  // Bit[7]: Test enable
+                                        //         0: Test disable
+                                        //         1: Color bar enable
+                                        // Bit[6]: Rolling
+                                        // Bit[5]: Transparent
+                                        // Bit[4]: Square black and white
+                                        // Bit[3:2]: Color bar style
+                                        //         00: Standard 8 color bar
+                                        //         01: Gradual change at vertical mode 1
+                                        //         10: Gradual change at horizontal
+                                        //         11: Gradual change at vertical mode 2
+                                        // Bit[1:0]: Test select
+                                        //         00: Color bar
+                                        //         01: Random data
+                                        //         10: Square data
+                                        //         11: Black image
+
+//exposure = {0x3500[3:0], 0x3501[7:0], 0x3502[7:0]} / 16 × tROW
+
+/* AEC/AGC control functions */
+#define AEC_PK_MANUAL   0x3503  // AEC Manual Mode Control
+                                // Bit[7:6]: Reserved
+                                // Bit[5]: Gain delay option
+                                //         Valid when 0x3503[4]=1’b0
+                                //         0: Delay one frame latch
+                                //         1: One frame latch
+                                // Bit[4:2]: Reserved
+                                // Bit[1]: AGC manual
+                                //         0: Auto enable
+                                //         1: Manual enable
+                                // Bit[0]: AEC manual
+                                //         0: Auto enable
+                                //         1: Manual enable
+
+//gain = {0x350A[1:0], 0x350B[7:0]} / 16
+
+/* mirror and flip registers */
+#define TIMING_TC_REG20 0x3820  // Timing Control Register
+                                // Bit[2:1]: Vertical flip enable
+                                //         00: Normal
+                                //         11: Vertical flip
+                                // Bit[0]: Vertical binning enable
+#define TIMING_TC_REG21 0x3821  // Timing Control Register
+                                // Bit[5]: Compression Enable
+                                // Bit[2:1]: Horizontal mirror enable
+                                //         00: Normal
+                                //         11: Horizontal mirror
+                                // Bit[0]: Horizontal binning enable
+
+#define CLOCK_POL_CONTROL 0x4740// Bit[5]: PCLK polarity 0: active low
+                                //          1: active high
+                                // Bit[3]: Gate PCLK under VSYNC
+                                // Bit[2]: Gate PCLK under HREF
+                                // Bit[1]: HREF polarity
+                                //          0: active low
+                                //          1: active high
+                                // Bit[0] VSYNC polarity
+                                //          0: active low
+                                //          1: active high
+#define DRIVE_CAPABILITY 0x302c // Bit[7:6]:
+                                //          00: 1x
+                                //          01: 2x
+                                //          10: 3x
+                                //          11: 4x
+
+
+#define X_ADDR_ST_H     0x3800 //Bit[3:0]: X address start[11:8]
+#define X_ADDR_ST_L     0x3801 //Bit[7:0]: X address start[7:0]
+#define Y_ADDR_ST_H     0x3802 //Bit[2:0]: Y address start[10:8]
+#define Y_ADDR_ST_L     0x3803 //Bit[7:0]: Y address start[7:0]
+#define X_ADDR_END_H    0x3804 //Bit[3:0]: X address end[11:8]
+#define X_ADDR_END_L    0x3805 //Bit[7:0]:
+#define Y_ADDR_END_H    0x3806 //Bit[2:0]: Y address end[10:8]
+#define Y_ADDR_END_L    0x3807 //Bit[7:0]:
+// Size after scaling
+#define X_OUTPUT_SIZE_H 0x3808 //Bit[3:0]: DVP output horizontal width[11:8]
+#define X_OUTPUT_SIZE_L 0x3809 //Bit[7:0]:
+#define Y_OUTPUT_SIZE_H 0x380a //Bit[2:0]: DVP output vertical height[10:8]
+#define Y_OUTPUT_SIZE_L 0x380b //Bit[7:0]:
+#define X_TOTAL_SIZE_H  0x380c //Bit[3:0]: Total horizontal size[11:8]
+#define X_TOTAL_SIZE_L  0x380d //Bit[7:0]:
+#define Y_TOTAL_SIZE_H  0x380e //Bit[7:0]: Total vertical size[15:8]
+#define Y_TOTAL_SIZE_L  0x380f //Bit[7:0]:
+#define X_OFFSET_H      0x3810 //Bit[3:0]: ISP horizontal offset[11:8]
+#define X_OFFSET_L      0x3811 //Bit[7:0]:
+#define Y_OFFSET_H      0x3812 //Bit[2:0]: ISP vertical offset[10:8]
+#define Y_OFFSET_L      0x3813 //Bit[7:0]:
+#define X_INCREMENT     0x3814 //Bit[7:4]: Horizontal odd subsample increment
+                               //Bit[3:0]: Horizontal even subsample increment
+#define Y_INCREMENT     0x3815 //Bit[7:4]: Vertical odd subsample increment
+                               //Bit[3:0]: Vertical even subsample increment
+// Size before scaling
+//#define X_INPUT_SIZE    (X_ADDR_END - X_ADDR_ST + 1 - (2 * X_OFFSET))
+//#define Y_INPUT_SIZE    (Y_ADDR_END - Y_ADDR_ST + 1 - (2 * Y_OFFSET))
+
+#define ISP_CONTROL_01   0x5001 // Bit[5]: Scale enable
+                                //          0: Disable
+                                //          1: Enable
+
+#define SCALE_CTRL_1     0x5601 // Bit[6:4]: HDIV RW
+                                //          DCW scale times
+                                //          000: DCW 1 time
+                                //          001: DCW 2 times
+                                //          010: DCW 4 times
+                                //          100: DCW 8 times
+                                //          101: DCW 16 times
+                                //          Others: DCW 16 times
+                                // Bit[2:0]: VDIV RW
+                                //          DCW scale times
+                                //          000: DCW 1 time
+                                //          001: DCW 2 times
+                                //          010: DCW 4 times
+                                //          100: DCW 8 times
+                                //          101: DCW 16 times
+                                //          Others: DCW 16 times
+
+#define SCALE_CTRL_2     0x5602 // X_SCALE High Bits
+#define SCALE_CTRL_3     0x5603 // X_SCALE Low Bits
+#define SCALE_CTRL_4     0x5604 // Y_SCALE High Bits
+#define SCALE_CTRL_5     0x5605 // Y_SCALE Low Bits
+#define SCALE_CTRL_6     0x5606 // Bit[3:0]: V Offset
+
+#define PCLK_RATIO       0x3824 // Bit[4:0]: PCLK ratio manual
+#define VFIFO_CTRL0C     0x460C // Bit[1]: PCLK manual enable
+                                //          0: Auto
+                                //          1: Manual by PCLK_RATIO
+
+#define VFIFO_X_SIZE_H   0x4602
+#define VFIFO_X_SIZE_L   0x4603
+#define VFIFO_Y_SIZE_H   0x4604
+#define VFIFO_Y_SIZE_L   0x4605
+
+#define SC_PLLS_CTRL0    0x303a // Bit[7]: PLLS bypass
+#define SC_PLLS_CTRL1    0x303b // Bit[4:0]: PLLS multiplier
+#define SC_PLLS_CTRL2    0x303c // Bit[6:4]: PLLS charge pump control
+                                // Bit[3:0]: PLLS system divider
+#define SC_PLLS_CTRL3    0x303d // Bit[5:4]: PLLS pre-divider
+                                //          00: 1
+                                //          01: 1.5
+                                //          10: 2
+                                //          11: 3
+                                // Bit[2]: PLLS root-divider - 1
+                                // Bit[1:0]: PLLS seld5
+                                //          00: 1
+                                //          01: 1
+                                //          10: 2
+                                //          11: 2.5
+
+#define COMPRESSION_CTRL00 0x4400 //
+#define COMPRESSION_CTRL01 0x4401 //
+#define COMPRESSION_CTRL02 0x4402 //
+#define COMPRESSION_CTRL03 0x4403 //
+#define COMPRESSION_CTRL04 0x4404 //
+#define COMPRESSION_CTRL05 0x4405 //
+#define COMPRESSION_CTRL06 0x4406 //
+#define COMPRESSION_CTRL07 0x4407 // Bit[5:0]: QS
+#define COMPRESSION_ISI_CTRL 0x4408 //
+#define COMPRESSION_CTRL09 0x4409 //
+#define COMPRESSION_CTRL0a 0x440a //
+#define COMPRESSION_CTRL0b 0x440b //
+#define COMPRESSION_CTRL0c 0x440c //
+#define COMPRESSION_CTRL0d 0x440d //
+#define COMPRESSION_CTRL0E 0x440e //
+
+/**
+ * @brief register value
+ */
+#define TEST_COLOR_BAR  0xC0    /* Enable Color Bar roling Test */
+
+#define AEC_PK_MANUAL_AGC_MANUALEN  0x02    /* Enable AGC Manual enable */
+#define AEC_PK_MANUAL_AEC_MANUALEN  0x01    /* Enable AEC Manual enable */
+
+#define TIMING_TC_REG20_VFLIP   0x06 /* Vertical flip enable */
+#define TIMING_TC_REG21_HMIRROR 0x06 /* Horizontal mirror enable */
+
+#endif // __OV3660_REG_REGS_H__

+ 318 - 0
code/lib/sensors/ov3660_settings.h

@@ -0,0 +1,318 @@
+#ifndef _OV3660_SETTINGS_H_
+#define _OV3660_SETTINGS_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "esp_attr.h"
+#include "ov3660_regs.h"
+
+static const ratio_settings_t ratio_table[] = {
+    //  mw,   mh,  sx,  sy,   ex,   ey, ox, oy,   tx,   ty
+    { 2048, 1536,   0,   0, 2079, 1547, 16, 6, 2300, 1564 }, //4x3
+    { 1920, 1280,  64, 128, 2015, 1419, 16, 6, 2172, 1436 }, //3x2
+    { 2048, 1280,   0, 128, 2079, 1419, 16, 6, 2300, 1436 }, //16x10
+    { 1920, 1152,  64, 192, 2015, 1355, 16, 6, 2172, 1372 }, //5x3
+    { 1920, 1080,  64, 242, 2015, 1333, 16, 6, 2172, 1322 }, //16x9
+    { 2048,  880,   0, 328, 2079, 1219, 16, 6, 2300, 1236 }, //21x9
+    { 1920, 1536,  64,   0, 2015, 1547, 16, 6, 2172, 1564 }, //5x4
+    { 1536, 1536, 256,   0, 1823, 1547, 16, 6, 2044, 1564 }, //1x1
+    {  864, 1536, 592,   0, 1487, 1547, 16, 6, 2044, 1564 }  //9x16
+};
+
+#define REG_DLY 0xffff
+#define REGLIST_TAIL 0x0000
+
+static const DRAM_ATTR uint16_t sensor_default_regs[][2] = {
+    {SYSTEM_CTROL0, 0x82},  // software reset
+    {REG_DLY, 10}, // delay 10ms
+
+    {0x3103, 0x13},
+    {SYSTEM_CTROL0, 0x42},
+    {0x3017, 0xff},
+    {0x3018, 0xff},
+    {DRIVE_CAPABILITY, 0xc3},
+    {CLOCK_POL_CONTROL, 0x21},
+
+    {0x3611, 0x01},
+    {0x3612, 0x2d},
+
+    {0x3032, 0x00},
+    {0x3614, 0x80},
+    {0x3618, 0x00},
+    {0x3619, 0x75},
+    {0x3622, 0x80},
+    {0x3623, 0x00},
+    {0x3624, 0x03},
+    {0x3630, 0x52},
+    {0x3632, 0x07},
+    {0x3633, 0xd2},
+    {0x3704, 0x80},
+    {0x3708, 0x66},
+    {0x3709, 0x12},
+    {0x370b, 0x12},
+    {0x3717, 0x00},
+    {0x371b, 0x60},
+    {0x371c, 0x00},
+    {0x3901, 0x13},
+
+    {0x3600, 0x08},
+    {0x3620, 0x43},
+    {0x3702, 0x20},
+    {0x3739, 0x48},
+    {0x3730, 0x20},
+    {0x370c, 0x0c},
+
+    {0x3a18, 0x00},
+    {0x3a19, 0xf8},
+
+    {0x3000, 0x10},
+    {0x3004, 0xef},
+
+    {0x6700, 0x05},
+    {0x6701, 0x19},
+    {0x6702, 0xfd},
+    {0x6703, 0xd1},
+    {0x6704, 0xff},
+    {0x6705, 0xff},
+
+    {0x3c01, 0x80},
+    {0x3c00, 0x04},
+    {0x3a08, 0x00}, {0x3a09, 0x62}, //50Hz Band Width Step (10bit)
+    {0x3a0e, 0x08}, //50Hz Max Bands in One Frame (6 bit)
+    {0x3a0a, 0x00}, {0x3a0b, 0x52}, //60Hz Band Width Step (10bit)
+    {0x3a0d, 0x09}, //60Hz Max Bands in One Frame (6 bit)
+
+    {0x3a00, 0x3a},//night mode off
+    {0x3a14, 0x09},
+    {0x3a15, 0x30},
+    {0x3a02, 0x09},
+    {0x3a03, 0x30},
+
+    {COMPRESSION_CTRL0E, 0x08},
+    {0x4520, 0x0b},
+    {0x460b, 0x37},
+    {0x4713, 0x02},
+    {0x471c, 0xd0},
+    {0x5086, 0x00},
+
+    {0x5002, 0x00},
+    {0x501f, 0x00},
+
+    {SYSTEM_CTROL0, 0x02},
+
+    {0x5180, 0xff},
+    {0x5181, 0xf2},
+    {0x5182, 0x00},
+    {0x5183, 0x14},
+    {0x5184, 0x25},
+    {0x5185, 0x24},
+    {0x5186, 0x16},
+    {0x5187, 0x16},
+    {0x5188, 0x16},
+    {0x5189, 0x68},
+    {0x518a, 0x60},
+    {0x518b, 0xe0},
+    {0x518c, 0xb2},
+    {0x518d, 0x42},
+    {0x518e, 0x35},
+    {0x518f, 0x56},
+    {0x5190, 0x56},
+    {0x5191, 0xf8},
+    {0x5192, 0x04},
+    {0x5193, 0x70},
+    {0x5194, 0xf0},
+    {0x5195, 0xf0},
+    {0x5196, 0x03},
+    {0x5197, 0x01},
+    {0x5198, 0x04},
+    {0x5199, 0x12},
+    {0x519a, 0x04},
+    {0x519b, 0x00},
+    {0x519c, 0x06},
+    {0x519d, 0x82},
+    {0x519e, 0x38},
+
+    {0x5381, 0x1d},
+    {0x5382, 0x60},
+    {0x5383, 0x03},
+    {0x5384, 0x0c},
+    {0x5385, 0x78},
+    {0x5386, 0x84},
+    {0x5387, 0x7d},
+    {0x5388, 0x6b},
+    {0x5389, 0x12},
+    {0x538a, 0x01},
+    {0x538b, 0x98},
+
+    {0x5480, 0x01},
+//    {0x5481, 0x05},
+//    {0x5482, 0x09},
+//    {0x5483, 0x10},
+//    {0x5484, 0x3a},
+//    {0x5485, 0x4c},
+//    {0x5486, 0x5a},
+//    {0x5487, 0x68},
+//    {0x5488, 0x74},
+//    {0x5489, 0x80},
+//    {0x548a, 0x8e},
+//    {0x548b, 0xa4},
+//    {0x548c, 0xb4},
+//    {0x548d, 0xc8},
+//    {0x548e, 0xde},
+//    {0x548f, 0xf0},
+//    {0x5490, 0x15},
+
+    {0x5000, 0xa7},
+    {0x5800, 0x0C},
+    {0x5801, 0x09},
+    {0x5802, 0x0C},
+    {0x5803, 0x0C},
+    {0x5804, 0x0D},
+    {0x5805, 0x17},
+    {0x5806, 0x06},
+    {0x5807, 0x05},
+    {0x5808, 0x04},
+    {0x5809, 0x06},
+    {0x580a, 0x09},
+    {0x580b, 0x0E},
+    {0x580c, 0x05},
+    {0x580d, 0x01},
+    {0x580e, 0x01},
+    {0x580f, 0x01},
+    {0x5810, 0x05},
+    {0x5811, 0x0D},
+    {0x5812, 0x05},
+    {0x5813, 0x01},
+    {0x5814, 0x01},
+    {0x5815, 0x01},
+    {0x5816, 0x05},
+    {0x5817, 0x0D},
+    {0x5818, 0x08},
+    {0x5819, 0x06},
+    {0x581a, 0x05},
+    {0x581b, 0x07},
+    {0x581c, 0x0B},
+    {0x581d, 0x0D},
+    {0x581e, 0x12},
+    {0x581f, 0x0D},
+    {0x5820, 0x0E},
+    {0x5821, 0x10},
+    {0x5822, 0x10},
+    {0x5823, 0x1E},
+    {0x5824, 0x53},
+    {0x5825, 0x15},
+    {0x5826, 0x05},
+    {0x5827, 0x14},
+    {0x5828, 0x54},
+    {0x5829, 0x25},
+    {0x582a, 0x33},
+    {0x582b, 0x33},
+    {0x582c, 0x34},
+    {0x582d, 0x16},
+    {0x582e, 0x24},
+    {0x582f, 0x41},
+    {0x5830, 0x50},
+    {0x5831, 0x42},
+    {0x5832, 0x15},
+    {0x5833, 0x25},
+    {0x5834, 0x34},
+    {0x5835, 0x33},
+    {0x5836, 0x24},
+    {0x5837, 0x26},
+    {0x5838, 0x54},
+    {0x5839, 0x25},
+    {0x583a, 0x15},
+    {0x583b, 0x25},
+    {0x583c, 0x53},
+    {0x583d, 0xCF},
+
+    {0x3a0f, 0x30},
+    {0x3a10, 0x28},
+    {0x3a1b, 0x30},
+    {0x3a1e, 0x28},
+    {0x3a11, 0x60},
+    {0x3a1f, 0x14},
+
+    {0x5302, 0x28},
+    {0x5303, 0x20},
+
+    {0x5306, 0x1c}, //de-noise offset 1
+    {0x5307, 0x28}, //de-noise offset 2
+
+    {0x4002, 0xc5},
+    {0x4003, 0x81},
+    {0x4005, 0x12},
+
+    {0x5688, 0x11},
+    {0x5689, 0x11},
+    {0x568a, 0x11},
+    {0x568b, 0x11},
+    {0x568c, 0x11},
+    {0x568d, 0x11},
+    {0x568e, 0x11},
+    {0x568f, 0x11},
+
+    {0x5580, 0x06},
+    {0x5588, 0x00},
+    {0x5583, 0x40},
+    {0x5584, 0x2c},
+
+    {ISP_CONTROL_01, 0x83}, // turn color matrix, awb and SDE
+    {REGLIST_TAIL, 0x00}, // tail
+};
+
+static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = {
+    {FORMAT_CTRL, 0x00}, // YUV422
+    {FORMAT_CTRL00, 0x30}, // YUYV
+    {0x3002, 0x00},//0x1c to 0x00 !!!
+    {0x3006, 0xff},//0xc3 to 0xff !!!
+    {0x471c, 0x50},//0xd0 to 0x50 !!!
+    {REGLIST_TAIL, 0x00}, // tail
+};
+
+static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = {
+    {FORMAT_CTRL00, 0x00}, // RAW
+    {REGLIST_TAIL, 0x00}
+};
+
+static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = {
+    {FORMAT_CTRL, 0x00}, // YUV422
+    {FORMAT_CTRL00, 0x10}, // Y8
+    {REGLIST_TAIL, 0x00}
+};
+
+static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = {
+    {FORMAT_CTRL, 0x00}, // YUV422
+    {FORMAT_CTRL00, 0x30}, // YUYV
+    {REGLIST_TAIL, 0x00}
+};
+
+static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = {
+    {FORMAT_CTRL, 0x01}, // RGB
+    {FORMAT_CTRL00, 0x61}, // RGB565 (BGR)
+    {REGLIST_TAIL, 0x00}
+};
+
+static const DRAM_ATTR uint8_t sensor_saturation_levels[9][11] = {
+    {0x1d, 0x60, 0x03, 0x07, 0x48, 0x4f, 0x4b, 0x40, 0x0b, 0x01, 0x98},//-4
+    {0x1d, 0x60, 0x03, 0x08, 0x54, 0x5c, 0x58, 0x4b, 0x0d, 0x01, 0x98},//-3
+    {0x1d, 0x60, 0x03, 0x0a, 0x60, 0x6a, 0x64, 0x56, 0x0e, 0x01, 0x98},//-2
+    {0x1d, 0x60, 0x03, 0x0b, 0x6c, 0x77, 0x70, 0x60, 0x10, 0x01, 0x98},//-1
+    {0x1d, 0x60, 0x03, 0x0c, 0x78, 0x84, 0x7d, 0x6b, 0x12, 0x01, 0x98},//0
+    {0x1d, 0x60, 0x03, 0x0d, 0x84, 0x91, 0x8a, 0x76, 0x14, 0x01, 0x98},//+1
+    {0x1d, 0x60, 0x03, 0x0e, 0x90, 0x9e, 0x96, 0x80, 0x16, 0x01, 0x98},//+2
+    {0x1d, 0x60, 0x03, 0x10, 0x9c, 0xac, 0xa2, 0x8b, 0x17, 0x01, 0x98},//+3
+    {0x1d, 0x60, 0x03, 0x11, 0xa8, 0xb9, 0xaf, 0x96, 0x19, 0x01, 0x98},//+4
+};
+
+static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = {
+    {0x06, 0x40, 0x2c, 0x08},//Normal
+    {0x46, 0x40, 0x28, 0x08},//Negative
+    {0x1e, 0x80, 0x80, 0x08},//Grayscale
+    {0x1e, 0x80, 0xc0, 0x08},//Red Tint
+    {0x1e, 0x60, 0x60, 0x08},//Green Tint
+    {0x1e, 0xa0, 0x40, 0x08},//Blue Tint
+    {0x1e, 0x40, 0xa0, 0x08},//Sepia
+};
+
+#endif

+ 1105 - 0
code/lib/sensors/ov5640.c

@@ -0,0 +1,1105 @@
+/*
+ * This file is part of the OpenMV project.
+ * Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
+ * This work is licensed under the MIT license, see the file LICENSE for details.
+ *
+ * OV3660 driver.
+ *
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include "sccb.h"
+#include "ov5640.h"
+#include "ov5640_regs.h"
+#include "ov5640_settings.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
+#include "esp32-hal-log.h"
+#else
+#include "esp_log.h"
+static const char *TAG = "ov5640";
+#endif
+
+//#define REG_DEBUG_ON
+
+static int read_reg(uint8_t slv_addr, const uint16_t reg){
+    int ret = SCCB_Read16(slv_addr, reg);
+#ifdef REG_DEBUG_ON
+    if (ret < 0) {
+        ESP_LOGE(TAG, "READ REG 0x%04x FAILED: %d", reg, ret);
+    }
+#endif
+    return ret;
+}
+
+static int check_reg_mask(uint8_t slv_addr, uint16_t reg, uint8_t mask){
+    return (read_reg(slv_addr, reg) & mask) == mask;
+}
+
+static int read_reg16(uint8_t slv_addr, const uint16_t reg){
+    int ret = 0, ret2 = 0;
+    ret = read_reg(slv_addr, reg);
+    if (ret >= 0) {
+        ret = (ret & 0xFF) << 8;
+        ret2 = read_reg(slv_addr, reg+1);
+        if (ret2 < 0) {
+            ret = ret2;
+        } else {
+            ret |= ret2 & 0xFF;
+        }
+    }
+    return ret;
+}
+
+//static void dump_reg(sensor_t *sensor, const uint16_t reg){
+//    int v = SCCB_Read16(sensor->slv_addr, reg);
+//    if(v < 0){
+//        ets_printf("  0x%04x: FAIL[%d]\n", reg, v);
+//    } else {
+//        ets_printf("  0x%04x: 0x%02X\n", reg, v);
+//    }
+//}
+//
+//static void dump_range(sensor_t *sensor, const char * name, const uint16_t start_reg, const uint16_t end_reg){
+//    ets_printf("%s: 0x%04x - 0x%04X\n", name, start_reg, end_reg);
+//    for(uint16_t reg = start_reg; reg <= end_reg; reg++){
+//        dump_reg(sensor, reg);
+//    }
+//}
+//
+//static void dump_regs(sensor_t *sensor){
+////    dump_range(sensor, "All Regs", 0x3000, 0x6100);
+////    dump_range(sensor, "system and IO pad control", 0x3000, 0x3052);
+////    dump_range(sensor, "SCCB control", 0x3100, 0x3108);
+////    dump_range(sensor, "SRB control", 0x3200, 0x3211);
+////    dump_range(sensor, "AWB gain control", 0x3400, 0x3406);
+////    dump_range(sensor, "AEC/AGC control", 0x3500, 0x350D);
+////    dump_range(sensor, "VCM control", 0x3600, 0x3606);
+////    dump_range(sensor, "timing control", 0x3800, 0x3821);
+////    dump_range(sensor, "AEC/AGC power down domain control", 0x3A00, 0x3A25);
+////    dump_range(sensor, "strobe control", 0x3B00, 0x3B0C);
+////    dump_range(sensor, "50/60Hz detector control", 0x3C00, 0x3C1E);
+////    dump_range(sensor, "OTP control", 0x3D00, 0x3D21);
+////    dump_range(sensor, "MC control", 0x3F00, 0x3F0D);
+////    dump_range(sensor, "BLC control", 0x4000, 0x4033);
+////    dump_range(sensor, "frame control", 0x4201, 0x4202);
+////    dump_range(sensor, "format control", 0x4300, 0x430D);
+////    dump_range(sensor, "JPEG control", 0x4400, 0x4431);
+////    dump_range(sensor, "VFIFO control", 0x4600, 0x460D);
+////    dump_range(sensor, "DVP control", 0x4709, 0x4745);
+////    dump_range(sensor, "MIPI control", 0x4800, 0x4837);
+////    dump_range(sensor, "ISP frame control", 0x4901, 0x4902);
+////    dump_range(sensor, "ISP top control", 0x5000, 0x5063);
+////    dump_range(sensor, "AWB control", 0x5180, 0x51D0);
+////    dump_range(sensor, "CIP control", 0x5300, 0x530F);
+////    dump_range(sensor, "CMX control", 0x5380, 0x538B);
+////    dump_range(sensor, "gamma control", 0x5480, 0x5490);
+////    dump_range(sensor, "SDE control", 0x5580, 0x558C);
+////    dump_range(sensor, "scale control", 0x5600, 0x5606);
+////    dump_range(sensor, "AVG control", 0x5680, 0x56A2);
+////    dump_range(sensor, "LENC control", 0x5800, 0x5849);
+////    dump_range(sensor, "AFC control", 0x6000, 0x603F);
+//}
+
+static int write_reg(uint8_t slv_addr, const uint16_t reg, uint8_t value){
+    int ret = 0;
+#ifndef REG_DEBUG_ON
+    ret = SCCB_Write16(slv_addr, reg, value);
+#else
+    int old_value = read_reg(slv_addr, reg);
+    if (old_value < 0) {
+        return old_value;
+    }
+    if ((uint8_t)old_value != value) {
+        ESP_LOGI(TAG, "NEW REG 0x%04x: 0x%02x to 0x%02x", reg, (uint8_t)old_value, value);
+        ret = SCCB_Write16(slv_addr, reg, value);
+    } else {
+        ESP_LOGD(TAG, "OLD REG 0x%04x: 0x%02x", reg, (uint8_t)old_value);
+        ret = SCCB_Write16(slv_addr, reg, value);//maybe not?
+    }
+    if (ret < 0) {
+        ESP_LOGE(TAG, "WRITE REG 0x%04x FAILED: %d", reg, ret);
+    }
+#endif
+    return ret;
+}
+
+static int set_reg_bits(uint8_t slv_addr, uint16_t reg, uint8_t offset, uint8_t mask, uint8_t value)
+{
+    int ret = 0;
+    uint8_t c_value, new_value;
+    ret = read_reg(slv_addr, reg);
+    if(ret < 0) {
+        return ret;
+    }
+    c_value = ret;
+    new_value = (c_value & ~(mask << offset)) | ((value & mask) << offset);
+    ret = write_reg(slv_addr, reg, new_value);
+    return ret;
+}
+
+static int write_regs(uint8_t slv_addr, const uint16_t (*regs)[2])
+{
+    int i = 0, ret = 0;
+    while (!ret && regs[i][0] != REGLIST_TAIL) {
+        if (regs[i][0] == REG_DLY) {
+            vTaskDelay(regs[i][1] / portTICK_PERIOD_MS);
+        } else {
+            ret = write_reg(slv_addr, regs[i][0], regs[i][1]);
+        }
+        i++;
+    }
+    return ret;
+}
+
+static int write_reg16(uint8_t slv_addr, const uint16_t reg, uint16_t value)
+{
+    if (write_reg(slv_addr, reg, value >> 8) || write_reg(slv_addr, reg + 1, value)) {
+        return -1;
+    }
+    return 0;
+}
+
+static int write_addr_reg(uint8_t slv_addr, const uint16_t reg, uint16_t x_value, uint16_t y_value)
+{
+    if (write_reg16(slv_addr, reg, x_value) || write_reg16(slv_addr, reg + 2, y_value)) {
+        return -1;
+    }
+    return 0;
+}
+
+#define write_reg_bits(slv_addr, reg, mask, enable) set_reg_bits(slv_addr, reg, 0, mask, (enable)?(mask):0)
+
+static int calc_sysclk(int xclk, bool pll_bypass, int pll_multiplier, int pll_sys_div, int pre_div, bool root_2x, int pclk_root_div, bool pclk_manual, int pclk_div)
+{
+    const float pll_pre_div2x_map[] = { 1, 1, 2, 3, 4, 1.5, 6, 2.5, 8};
+    const int pll_pclk_root_div_map[] = { 1, 2, 4, 8 };
+
+    if(!pll_sys_div) {
+        pll_sys_div = 1;
+    }
+
+    float pll_pre_div = pll_pre_div2x_map[pre_div];
+    unsigned int root_2x_div = root_2x?2:1;
+    unsigned int pll_pclk_root_div = pll_pclk_root_div_map[pclk_root_div];
+
+    unsigned int REFIN = xclk / pll_pre_div;
+
+    unsigned int VCO = REFIN * pll_multiplier / root_2x_div;
+
+    unsigned int PLL_CLK = pll_bypass?(xclk):(VCO / pll_sys_div * 2 / 5);//5 here is 10bit mode / 2, for 8bit it should be 4 (reg 0x3034)
+
+    unsigned int PCLK = PLL_CLK / pll_pclk_root_div / ((pclk_manual && pclk_div)?pclk_div:2);
+
+    unsigned int SYSCLK = PLL_CLK / 4;
+
+    ESP_LOGD(TAG, "Calculated XVCLK: %d Hz, REFIN: %u Hz, VCO: %u Hz, PLL_CLK: %u Hz, SYSCLK: %u Hz, PCLK: %u Hz", xclk, REFIN, VCO, PLL_CLK, SYSCLK, PCLK);
+    return SYSCLK;
+}
+
+static int set_pll(sensor_t *sensor, bool bypass, uint8_t multiplier, uint8_t sys_div, uint8_t pre_div, bool root_2x, uint8_t pclk_root_div, bool pclk_manual, uint8_t pclk_div){
+    int ret = 0;
+    if(multiplier > 252 || multiplier < 4 || sys_div > 15 || pre_div > 8 || pclk_div > 31 || pclk_root_div > 3){
+        ESP_LOGE(TAG, "Invalid arguments");
+        return -1;
+    }
+    if(multiplier > 127){
+        multiplier &= 0xFE;//only even integers above 127
+    }
+
+    calc_sysclk(sensor->xclk_freq_hz, bypass, multiplier, sys_div, pre_div, root_2x, pclk_root_div, pclk_manual, pclk_div);
+
+    ret = write_reg(sensor->slv_addr, 0x3039, bypass?0x80:0x00);
+    if (ret == 0) {
+        ret = write_reg(sensor->slv_addr, 0x3034, 0x1A);//10bit mode
+    }
+    if (ret == 0) {
+        ret = write_reg(sensor->slv_addr, 0x3035, 0x01 | ((sys_div & 0x0f) << 4));
+    }
+    if (ret == 0) {
+        ret = write_reg(sensor->slv_addr, 0x3036, multiplier & 0xff);
+    }
+    if (ret == 0) {
+        ret = write_reg(sensor->slv_addr, 0x3037, (pre_div & 0xf) | (root_2x?0x10:0x00));
+    }
+    if (ret == 0) {
+        ret = write_reg(sensor->slv_addr, 0x3108, (pclk_root_div & 0x3) << 4 | 0x06);
+    }
+    if (ret == 0) {
+        ret = write_reg(sensor->slv_addr, 0x3824, pclk_div & 0x1f);
+    }
+    if (ret == 0) {
+        ret = write_reg(sensor->slv_addr, 0x460C, pclk_manual?0x22:0x20);
+    }
+    if (ret == 0) {
+        ret = write_reg(sensor->slv_addr, 0x3103, 0x13);// system clock from pll, bit[1]
+    }
+    if(ret){
+        ESP_LOGE(TAG, "set_sensor_pll FAILED!");
+    }
+    return ret;
+}
+
+static int set_ae_level(sensor_t *sensor, int level);
+
+static int reset(sensor_t *sensor)
+{
+    //dump_regs(sensor);
+    vTaskDelay(100 / portTICK_PERIOD_MS);
+    int ret = 0;
+    // Software Reset: clear all registers and reset them to their default values
+    ret = write_reg(sensor->slv_addr, SYSTEM_CTROL0, 0x82);
+    if(ret){
+        ESP_LOGE(TAG, "Software Reset FAILED!");
+        return ret;
+    }
+    vTaskDelay(100 / portTICK_PERIOD_MS);
+    ret = write_regs(sensor->slv_addr, sensor_default_regs);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Camera defaults loaded");
+        vTaskDelay(100 / portTICK_PERIOD_MS);
+        //write_regs(sensor->slv_addr, sensor_regs_awb0);
+        //write_regs(sensor->slv_addr, sensor_regs_gamma1);
+    }
+    return ret;
+}
+
+static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
+{
+    int ret = 0;
+    const uint16_t (*regs)[2];
+
+    switch (pixformat) {
+    case PIXFORMAT_YUV422:
+        regs = sensor_fmt_yuv422;
+        break;
+
+    case PIXFORMAT_GRAYSCALE:
+        regs = sensor_fmt_grayscale;
+        break;
+
+    case PIXFORMAT_RGB565:
+    case PIXFORMAT_RGB888:
+        regs = sensor_fmt_rgb565;
+        break;
+
+    case PIXFORMAT_JPEG:
+        regs = sensor_fmt_jpeg;
+        break;
+
+    case PIXFORMAT_RAW:
+        regs = sensor_fmt_raw;
+        break;
+
+    default:
+        ESP_LOGE(TAG, "Unsupported pixformat: %u", pixformat);
+        return -1;
+    }
+
+    ret = write_regs(sensor->slv_addr, regs);
+    if(ret == 0) {
+        sensor->pixformat = pixformat;
+        ESP_LOGD(TAG, "Set pixformat to: %u", pixformat);
+    }
+    return ret;
+}
+
+static int set_image_options(sensor_t *sensor)
+{
+    int ret = 0;
+    uint8_t reg20 = 0;
+    uint8_t reg21 = 0;
+    uint8_t reg4514 = 0;
+    uint8_t reg4514_test = 0;
+
+    // compression
+    if (sensor->pixformat == PIXFORMAT_JPEG) {
+        reg21 |= 0x20;
+    }
+
+    // binning
+    if (!sensor->status.binning) {
+        reg20 |= 0x40;
+    } else {
+        reg20 |= 0x01;
+        reg21 |= 0x01;
+        reg4514_test |= 4;
+    }
+
+    // V-Flip
+    if (sensor->status.vflip) {
+        reg20 |= 0x06;
+        reg4514_test |= 1;
+    }
+
+    // H-Mirror
+    if (sensor->status.hmirror) {
+        reg21 |= 0x06;
+        reg4514_test |= 2;
+    }
+
+    switch (reg4514_test) {
+        //no binning
+        case 0: reg4514 = 0x88; break;//normal
+        case 1: reg4514 = 0x00; break;//v-flip
+        case 2: reg4514 = 0xbb; break;//h-mirror
+        case 3: reg4514 = 0x00; break;//v-flip+h-mirror
+        //binning
+        case 4: reg4514 = 0xaa; break;//normal
+        case 5: reg4514 = 0xbb; break;//v-flip
+        case 6: reg4514 = 0xbb; break;//h-mirror
+        case 7: reg4514 = 0xaa; break;//v-flip+h-mirror
+    }
+
+    if(write_reg(sensor->slv_addr, TIMING_TC_REG20, reg20)
+        || write_reg(sensor->slv_addr, TIMING_TC_REG21, reg21)
+        || write_reg(sensor->slv_addr, 0x4514, reg4514)){
+        ESP_LOGE(TAG, "Setting Image Options Failed");
+        return -1;
+    }
+
+    if (!sensor->status.binning) {
+        ret  = write_reg(sensor->slv_addr, 0x4520, 0x10)
+            || write_reg(sensor->slv_addr, X_INCREMENT, 0x11)//odd:1, even: 1
+            || write_reg(sensor->slv_addr, Y_INCREMENT, 0x11);//odd:1, even: 1
+    } else {
+        ret  = write_reg(sensor->slv_addr, 0x4520, 0x0b)
+            || write_reg(sensor->slv_addr, X_INCREMENT, 0x31)//odd:3, even: 1
+            || write_reg(sensor->slv_addr, Y_INCREMENT, 0x31);//odd:3, even: 1
+    }
+
+    ESP_LOGD(TAG, "Set Image Options: Compression: %u, Binning: %u, V-Flip: %u, H-Mirror: %u, Reg-4514: 0x%02x",
+        sensor->pixformat == PIXFORMAT_JPEG, sensor->status.binning, sensor->status.vflip, sensor->status.hmirror, reg4514);
+    return ret;
+}
+
+static int set_framesize(sensor_t *sensor, framesize_t framesize)
+{
+    int ret = 0;
+    framesize_t old_framesize = sensor->status.framesize;
+    sensor->status.framesize = framesize;
+
+    if(framesize > FRAMESIZE_QSXGA){
+        ESP_LOGE(TAG, "Invalid framesize: %u", framesize);
+        return -1;
+    }
+    uint16_t w = resolution[framesize].width;
+    uint16_t h = resolution[framesize].height;
+    aspect_ratio_t ratio = resolution[framesize].aspect_ratio;
+    ratio_settings_t settings = ratio_table[ratio];
+
+    sensor->status.binning = (w <= (settings.max_width / 2) && h <= (settings.max_height / 2));
+    sensor->status.scale = !((w == settings.max_width && h == settings.max_height)
+        || (w == (settings.max_width / 2) && h == (settings.max_height / 2)));
+
+    ret  = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, settings.start_x, settings.start_y)
+        || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, settings.end_x, settings.end_y)
+        || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, w, h);
+
+    if (ret) {
+        goto fail;
+    }
+
+    if (!sensor->status.binning) {
+        ret  = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x, settings.total_y)
+            || write_addr_reg(sensor->slv_addr, X_OFFSET_H, settings.offset_x, settings.offset_y);
+    } else {
+        if (w > 920) {
+            ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, settings.total_x - 200, settings.total_y / 2);
+        } else {
+            ret = write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, 2060, settings.total_y / 2);
+        }
+        if (ret == 0) {
+            ret = write_addr_reg(sensor->slv_addr, X_OFFSET_H, settings.offset_x / 2, settings.offset_y / 2);
+        }
+    }
+
+    if (ret == 0) {
+        ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, sensor->status.scale);
+    }
+
+    if (ret == 0) {
+        ret = set_image_options(sensor);
+    }
+
+    if (ret) {
+        goto fail;
+    }
+
+    if (sensor->pixformat == PIXFORMAT_JPEG) {
+        //10MHz PCLK
+        uint8_t sys_mul = 200;
+        if(framesize < FRAMESIZE_QVGA){
+            sys_mul = 160;
+        } else if(framesize < FRAMESIZE_XGA){
+            sys_mul = 180;
+        }
+        ret = set_pll(sensor, false, sys_mul, 4, 2, false, 2, true, 4);
+    } else {
+        ret = set_pll(sensor, false, 10, 1, 1, false, 1, true, 4);
+    }
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set framesize to: %ux%u", w, h);
+    }
+    return ret;
+
+fail:
+    sensor->status.framesize = old_framesize;
+    ESP_LOGE(TAG, "Setting framesize to: %ux%u failed", w, h);
+    return ret;
+}
+
+static int set_hmirror(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    sensor->status.hmirror = enable;
+    ret = set_image_options(sensor);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set h-mirror to: %d", enable);
+    }
+    return ret;
+}
+
+static int set_vflip(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    sensor->status.vflip = enable;
+    ret = set_image_options(sensor);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set v-flip to: %d", enable);
+    }
+    return ret;
+}
+
+static int set_quality(sensor_t *sensor, int qs)
+{
+    int ret = 0;
+    ret = write_reg(sensor->slv_addr, COMPRESSION_CTRL07, qs & 0x3f);
+    if (ret == 0) {
+        sensor->status.quality = qs;
+        ESP_LOGD(TAG, "Set quality to: %d", qs);
+    }
+    return ret;
+}
+
+static int set_colorbar(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR, enable);
+    if (ret == 0) {
+        sensor->status.colorbar = enable;
+        ESP_LOGD(TAG, "Set colorbar to: %d", enable);
+    }
+    return ret;
+}
+
+static int set_gain_ctrl(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN, !enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set gain_ctrl to: %d", enable);
+        sensor->status.agc = enable;
+    }
+    return ret;
+}
+
+static int set_exposure_ctrl(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN, !enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set exposure_ctrl to: %d", enable);
+        sensor->status.aec = enable;
+    }
+    return ret;
+}
+
+static int set_whitebal(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x01, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set awb to: %d", enable);
+        sensor->status.awb = enable;
+    }
+    return ret;
+}
+
+//Advanced AWB
+static int set_dcw_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, 0x5183, 0x80, !enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set dcw to: %d", enable);
+        sensor->status.dcw = enable;
+    }
+    return ret;
+}
+
+//night mode enable
+static int set_aec2(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, 0x3a00, 0x04, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set aec2 to: %d", enable);
+        sensor->status.aec2 = enable;
+    }
+    return ret;
+}
+
+static int set_bpc_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x04, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set bpc to: %d", enable);
+        sensor->status.bpc = enable;
+    }
+    return ret;
+}
+
+static int set_wpc_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x02, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set wpc to: %d", enable);
+        sensor->status.wpc = enable;
+    }
+    return ret;
+}
+
+//Gamma enable
+static int set_raw_gma_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x20, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set raw_gma to: %d", enable);
+        sensor->status.raw_gma = enable;
+    }
+    return ret;
+}
+
+static int set_lenc_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = write_reg_bits(sensor->slv_addr, 0x5000, 0x80, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set lenc to: %d", enable);
+        sensor->status.lenc = enable;
+    }
+    return ret;
+}
+
+static int get_agc_gain(sensor_t *sensor)
+{
+    int ra = read_reg(sensor->slv_addr, 0x350a);
+    if (ra < 0) {
+        return 0;
+    }
+    int rb = read_reg(sensor->slv_addr, 0x350b);
+    if (rb < 0) {
+        return 0;
+    }
+    int res = (rb & 0xF0) >> 4 | (ra & 0x03) << 4;
+    if (rb & 0x0F) {
+        res += 1;
+    }
+    return res;
+}
+
+//real gain
+static int set_agc_gain(sensor_t *sensor, int gain)
+{
+    int ret = 0;
+    if(gain < 0) {
+        gain = 0;
+    } else if(gain > 64) {
+        gain = 64;
+    }
+
+    //gain value is 6.4 bits float
+    //in order to use the max range, we deduct 1/16
+    int gainv = gain << 4;
+    if(gainv){
+        gainv -= 1;
+    }
+
+    ret = write_reg(sensor->slv_addr, 0x350a, gainv >> 8) || write_reg(sensor->slv_addr, 0x350b, gainv & 0xff);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set agc_gain to: %d", gain);
+        sensor->status.agc_gain = gain;
+    }
+    return ret;
+}
+
+static int get_aec_value(sensor_t *sensor)
+{
+    int ra = read_reg(sensor->slv_addr, 0x3500);
+    if (ra < 0) {
+        return 0;
+    }
+    int rb = read_reg(sensor->slv_addr, 0x3501);
+    if (rb < 0) {
+        return 0;
+    }
+    int rc = read_reg(sensor->slv_addr, 0x3502);
+    if (rc < 0) {
+        return 0;
+    }
+    int res = (ra & 0x0F) << 12 | (rb & 0xFF) << 4 | (rc & 0xF0) >> 4;
+    return res;
+}
+
+static int set_aec_value(sensor_t *sensor, int value)
+{
+    int ret = 0, max_val = 0;
+    max_val = read_reg16(sensor->slv_addr, 0x380e);
+    if (max_val < 0) {
+        ESP_LOGE(TAG, "Could not read max aec_value");
+        return -1;
+    }
+    if (value > max_val) {
+        value =max_val;
+    }
+
+    ret =  write_reg(sensor->slv_addr, 0x3500, (value >> 12) & 0x0F)
+        || write_reg(sensor->slv_addr, 0x3501, (value >> 4) & 0xFF)
+        || write_reg(sensor->slv_addr, 0x3502, (value << 4) & 0xF0);
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set aec_value to: %d / %d", value, max_val);
+        sensor->status.aec_value = value;
+    }
+    return ret;
+}
+
+static int set_ae_level(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    if (level < -5 || level > 5) {
+        return -1;
+    }
+    //good targets are between 5 and 115
+    int target_level = ((level + 5) * 10) + 5;
+
+    int level_high, level_low;
+    int fast_high, fast_low;
+
+    level_low = target_level * 23 / 25; //0.92 (0.46)
+    level_high = target_level * 27 / 25; //1.08 (2.08)
+
+    fast_low = level_low >> 1;
+    fast_high = level_high << 1;
+
+    if(fast_high>255) {
+        fast_high = 255;
+    }
+
+    ret =  write_reg(sensor->slv_addr, 0x3a0f, level_high)
+        || write_reg(sensor->slv_addr, 0x3a10, level_low)
+        || write_reg(sensor->slv_addr, 0x3a1b, level_high)
+        || write_reg(sensor->slv_addr, 0x3a1e, level_low)
+        || write_reg(sensor->slv_addr, 0x3a11, fast_high)
+        || write_reg(sensor->slv_addr, 0x3a1f, fast_low);
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set ae_level to: %d", level);
+        sensor->status.ae_level = level;
+    }
+    return ret;
+}
+
+static int set_wb_mode(sensor_t *sensor, int mode)
+{
+    int ret = 0;
+    if (mode < 0 || mode > 4) {
+        return -1;
+    }
+
+    ret = write_reg(sensor->slv_addr, 0x3406, (mode != 0));
+    if (ret) {
+        return ret;
+    }
+    switch (mode) {
+        case 1://Sunny
+            ret  = write_reg16(sensor->slv_addr, 0x3400, 0x5e0) //AWB R GAIN
+                || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN
+                || write_reg16(sensor->slv_addr, 0x3404, 0x540);//AWB B GAIN
+            break;
+        case 2://Cloudy
+            ret  = write_reg16(sensor->slv_addr, 0x3400, 0x650) //AWB R GAIN
+                || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN
+                || write_reg16(sensor->slv_addr, 0x3404, 0x4f0);//AWB B GAIN
+            break;
+        case 3://Office
+            ret  = write_reg16(sensor->slv_addr, 0x3400, 0x520) //AWB R GAIN
+                || write_reg16(sensor->slv_addr, 0x3402, 0x410) //AWB G GAIN
+                || write_reg16(sensor->slv_addr, 0x3404, 0x660);//AWB B GAIN
+            break;
+        case 4://HOME
+            ret  = write_reg16(sensor->slv_addr, 0x3400, 0x420) //AWB R GAIN
+                || write_reg16(sensor->slv_addr, 0x3402, 0x3f0) //AWB G GAIN
+                || write_reg16(sensor->slv_addr, 0x3404, 0x710);//AWB B GAIN
+            break;
+        default://AUTO
+            break;
+    }
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set wb_mode to: %d", mode);
+        sensor->status.wb_mode = mode;
+    }
+    return ret;
+}
+
+static int set_awb_gain_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    int old_mode = sensor->status.wb_mode;
+    int mode = enable?old_mode:0;
+
+    ret = set_wb_mode(sensor, mode);
+
+    if (ret == 0) {
+        sensor->status.wb_mode = old_mode;
+        ESP_LOGD(TAG, "Set awb_gain to: %d", enable);
+        sensor->status.awb_gain = enable;
+    }
+    return ret;
+}
+
+static int set_special_effect(sensor_t *sensor, int effect)
+{
+    int ret=0;
+    if (effect < 0 || effect > 6) {
+        return -1;
+    }
+
+    uint8_t * regs = (uint8_t *)sensor_special_effects[effect];
+    ret =  write_reg(sensor->slv_addr, 0x5580, regs[0])
+        || write_reg(sensor->slv_addr, 0x5583, regs[1])
+        || write_reg(sensor->slv_addr, 0x5584, regs[2])
+        || write_reg(sensor->slv_addr, 0x5003, regs[3]);
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set special_effect to: %d", effect);
+        sensor->status.special_effect = effect;
+    }
+    return ret;
+}
+
+static int set_brightness(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    uint8_t value = 0;
+    bool negative = false;
+
+    switch (level) {
+        case 3:
+            value = 0x30;
+            break;
+        case 2:
+            value = 0x20;
+            break;
+        case 1:
+            value = 0x10;
+            break;
+        case -1:
+            value = 0x10;
+            negative = true;
+            break;
+        case -2:
+            value = 0x20;
+            negative = true;
+            break;
+        case -3:
+            value = 0x30;
+            negative = true;
+            break;
+        default: // 0
+            break;
+    }
+
+    ret = write_reg(sensor->slv_addr, 0x5587, value);
+    if (ret == 0) {
+        ret = write_reg_bits(sensor->slv_addr, 0x5588, 0x08, negative);
+    }
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set brightness to: %d", level);
+        sensor->status.brightness = level;
+    }
+    return ret;
+}
+
+static int set_contrast(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    if(level > 3 || level < -3) {
+        return -1;
+    }
+    ret = write_reg(sensor->slv_addr, 0x5586, (level + 4) << 3);
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set contrast to: %d", level);
+        sensor->status.contrast = level;
+    }
+    return ret;
+}
+
+static int set_saturation(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    if(level > 4 || level < -4) {
+        return -1;
+    }
+
+    uint8_t * regs = (uint8_t *)sensor_saturation_levels[level+4];
+    for(int i=0; i<11; i++) {
+        ret = write_reg(sensor->slv_addr, 0x5381 + i, regs[i]);
+        if (ret) {
+            break;
+        }
+    }
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set saturation to: %d", level);
+        sensor->status.saturation = level;
+    }
+    return ret;
+}
+
+static int set_sharpness(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    if(level > 3 || level < -3) {
+        return -1;
+    }
+
+    uint8_t mt_offset_2 = (level + 3) * 8;
+    uint8_t mt_offset_1 = mt_offset_2 + 1;
+
+    ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x40, false)//0x40 means auto
+        || write_reg(sensor->slv_addr, 0x5300, 0x10)
+        || write_reg(sensor->slv_addr, 0x5301, 0x10)
+        || write_reg(sensor->slv_addr, 0x5302, mt_offset_1)
+        || write_reg(sensor->slv_addr, 0x5303, mt_offset_2)
+        || write_reg(sensor->slv_addr, 0x5309, 0x10)
+        || write_reg(sensor->slv_addr, 0x530a, 0x10)
+        || write_reg(sensor->slv_addr, 0x530b, 0x04)
+        || write_reg(sensor->slv_addr, 0x530c, 0x06);
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set sharpness to: %d", level);
+        sensor->status.sharpness = level;
+    }
+    return ret;
+}
+
+static int set_gainceiling(sensor_t *sensor, gainceiling_t level)
+{
+    int ret = 0, l = (int)level;
+
+    ret = write_reg(sensor->slv_addr, 0x3A18, (l >> 8) & 3)
+       || write_reg(sensor->slv_addr, 0x3A19, l & 0xFF);
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set gainceiling to: %d", l);
+        sensor->status.gainceiling = l;
+    }
+    return ret;
+}
+
+static int get_denoise(sensor_t *sensor)
+{
+    if (!check_reg_mask(sensor->slv_addr, 0x5308, 0x10)) {
+        return 0;
+    }
+    return (read_reg(sensor->slv_addr, 0x5306) / 4) + 1;
+}
+
+static int set_denoise(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    if (level < 0 || level > 8) {
+        return -1;
+    }
+
+    ret = write_reg_bits(sensor->slv_addr, 0x5308, 0x10, level > 0);
+    if (ret == 0 && level > 0) {
+        ret = write_reg(sensor->slv_addr, 0x5306, (level - 1) * 4);
+    }
+
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set denoise to: %d", level);
+        sensor->status.denoise = level;
+    }
+    return ret;
+}
+
+static int get_reg(sensor_t *sensor, int reg, int mask)
+{
+    int ret = 0, ret2 = 0;
+    if(mask > 0xFF){
+        ret = read_reg16(sensor->slv_addr, reg);
+        if(ret >= 0 && mask > 0xFFFF){
+            ret2 = read_reg(sensor->slv_addr, reg+2);
+            if(ret2 >= 0){
+                ret = (ret << 8) | ret2 ;
+            } else {
+                ret = ret2;
+            }
+        }
+    } else {
+        ret = read_reg(sensor->slv_addr, reg);
+    }
+    if(ret > 0){
+        ret &= mask;
+    }
+    return ret;
+}
+
+static int set_reg(sensor_t *sensor, int reg, int mask, int value)
+{
+    int ret = 0, ret2 = 0;
+    if(mask > 0xFF){
+        ret = read_reg16(sensor->slv_addr, reg);
+        if(ret >= 0 && mask > 0xFFFF){
+            ret2 = read_reg(sensor->slv_addr, reg+2);
+            if(ret2 >= 0){
+                ret = (ret << 8) | ret2 ;
+            } else {
+                ret = ret2;
+            }
+        }
+    } else {
+        ret = read_reg(sensor->slv_addr, reg);
+    }
+    if(ret < 0){
+        return ret;
+    }
+    value = (ret & ~mask) | (value & mask);
+    if(mask > 0xFFFF){
+        ret = write_reg16(sensor->slv_addr, reg, value >> 8);
+        if(ret >= 0){
+            ret = write_reg(sensor->slv_addr, reg+2, value & 0xFF);
+        }
+    } else if(mask > 0xFF){
+        ret = write_reg16(sensor->slv_addr, reg, value);
+    } else {
+        ret = write_reg(sensor->slv_addr, reg, value);
+    }
+    return ret;
+}
+
+static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning)
+{
+    int ret = 0;
+    ret  = write_addr_reg(sensor->slv_addr, X_ADDR_ST_H, startX, startY)
+        || write_addr_reg(sensor->slv_addr, X_ADDR_END_H, endX, endY)
+        || write_addr_reg(sensor->slv_addr, X_OFFSET_H, offsetX, offsetY)
+        || write_addr_reg(sensor->slv_addr, X_TOTAL_SIZE_H, totalX, totalY)
+        || write_addr_reg(sensor->slv_addr, X_OUTPUT_SIZE_H, outputX, outputY)
+        || write_reg_bits(sensor->slv_addr, ISP_CONTROL_01, 0x20, scale);
+    if(!ret){
+        sensor->status.scale = scale;
+        sensor->status.binning = binning;
+        ret = set_image_options(sensor);
+    }
+    return ret;
+}
+
+static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div)
+{
+    int ret = 0;
+    ret = set_pll(sensor, bypass > 0, multiplier, sys_div, pre_div, root_2x > 0, seld5, pclk_manual > 0, pclk_div);
+    return ret;
+}
+
+esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz);
+static int set_xclk(sensor_t *sensor, int timer, int xclk)
+{
+    int ret = 0;
+    sensor->xclk_freq_hz = xclk * 1000000U;
+    ret = xclk_timer_conf(timer, sensor->xclk_freq_hz);
+    return ret;
+}
+
+static int init_status(sensor_t *sensor)
+{
+    sensor->status.brightness = 0;
+    sensor->status.contrast = 0;
+    sensor->status.saturation = 0;
+    sensor->status.sharpness = (read_reg(sensor->slv_addr, 0x5303) / 8) - 3;
+    sensor->status.denoise = get_denoise(sensor);
+    sensor->status.ae_level = 0;
+    sensor->status.gainceiling = read_reg16(sensor->slv_addr, 0x3A18) & 0x3FF;
+    sensor->status.awb = check_reg_mask(sensor->slv_addr, ISP_CONTROL_01, 0x01);
+    sensor->status.dcw = !check_reg_mask(sensor->slv_addr, 0x5183, 0x80);
+    sensor->status.agc = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AGC_MANUALEN);
+    sensor->status.aec = !check_reg_mask(sensor->slv_addr, AEC_PK_MANUAL, AEC_PK_MANUAL_AEC_MANUALEN);
+    sensor->status.hmirror = check_reg_mask(sensor->slv_addr, TIMING_TC_REG21, TIMING_TC_REG21_HMIRROR);
+    sensor->status.vflip = check_reg_mask(sensor->slv_addr, TIMING_TC_REG20, TIMING_TC_REG20_VFLIP);
+    sensor->status.colorbar = check_reg_mask(sensor->slv_addr, PRE_ISP_TEST_SETTING_1, TEST_COLOR_BAR);
+    sensor->status.bpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x04);
+    sensor->status.wpc = check_reg_mask(sensor->slv_addr, 0x5000, 0x02);
+    sensor->status.raw_gma = check_reg_mask(sensor->slv_addr, 0x5000, 0x20);
+    sensor->status.lenc = check_reg_mask(sensor->slv_addr, 0x5000, 0x80);
+    sensor->status.quality = read_reg(sensor->slv_addr, COMPRESSION_CTRL07) & 0x3f;
+    sensor->status.special_effect = 0;
+    sensor->status.wb_mode = 0;
+    sensor->status.awb_gain = check_reg_mask(sensor->slv_addr, 0x3406, 0x01);
+    sensor->status.agc_gain = get_agc_gain(sensor);
+    sensor->status.aec_value = get_aec_value(sensor);
+    sensor->status.aec2 = check_reg_mask(sensor->slv_addr, 0x3a00, 0x04);
+    return 0;
+}
+
+int ov5640_init(sensor_t *sensor)
+{
+    sensor->reset = reset;
+    sensor->set_pixformat = set_pixformat;
+    sensor->set_framesize = set_framesize;
+    sensor->set_contrast = set_contrast;
+    sensor->set_brightness = set_brightness;
+    sensor->set_saturation = set_saturation;
+    sensor->set_sharpness = set_sharpness;
+    sensor->set_gainceiling = set_gainceiling;
+    sensor->set_quality = set_quality;
+    sensor->set_colorbar = set_colorbar;
+    sensor->set_gain_ctrl = set_gain_ctrl;
+    sensor->set_exposure_ctrl = set_exposure_ctrl;
+    sensor->set_whitebal = set_whitebal;
+    sensor->set_hmirror = set_hmirror;
+    sensor->set_vflip = set_vflip;
+    sensor->init_status = init_status;
+    sensor->set_aec2 = set_aec2;
+    sensor->set_aec_value = set_aec_value;
+    sensor->set_special_effect = set_special_effect;
+    sensor->set_wb_mode = set_wb_mode;
+    sensor->set_ae_level = set_ae_level;
+    sensor->set_dcw = set_dcw_dsp;
+    sensor->set_bpc = set_bpc_dsp;
+    sensor->set_wpc = set_wpc_dsp;
+    sensor->set_awb_gain = set_awb_gain_dsp;
+    sensor->set_agc_gain = set_agc_gain;
+    sensor->set_raw_gma = set_raw_gma_dsp;
+    sensor->set_lenc = set_lenc_dsp;
+    sensor->set_denoise = set_denoise;
+
+    sensor->get_reg = get_reg;
+    sensor->set_reg = set_reg;
+    sensor->set_res_raw = set_res_raw;
+    sensor->set_pll = _set_pll;
+    sensor->set_xclk = set_xclk;
+    return 0;
+}

+ 9 - 0
code/lib/sensors/ov5640.h

@@ -0,0 +1,9 @@
+
+#ifndef __OV5640_H__
+#define __OV5640_H__
+
+#include "sensor.h"
+
+int ov5640_init(sensor_t *sensor);
+
+#endif // __OV5640_H__

+ 213 - 0
code/lib/sensors/ov5640_regs.h

@@ -0,0 +1,213 @@
+/*
+ * OV5640 register definitions.
+ */
+#ifndef __OV5640_REG_REGS_H__
+#define __OV5640_REG_REGS_H__
+
+/* system control registers */
+#define SYSTEM_CTROL0   0x3008  // Bit[7]: Software reset 
+                                // Bit[6]: Software power down 
+                                // Bit[5]: Reserved 
+                                // Bit[4]: SRB clock SYNC enable 
+                                // Bit[3]: Isolation suspend select 
+                                // Bit[2:0]: Not used
+
+#define DRIVE_CAPABILITY 0x302c // Bit[7:6]:
+                                //          00: 1x
+                                //          01: 2x
+                                //          10: 3x
+                                //          11: 4x
+
+#define SC_PLLS_CTRL0    0x303a // Bit[7]: PLLS bypass
+#define SC_PLLS_CTRL1    0x303b // Bit[4:0]: PLLS multiplier
+#define SC_PLLS_CTRL2    0x303c // Bit[6:4]: PLLS charge pump control
+                                // Bit[3:0]: PLLS system divider
+#define SC_PLLS_CTRL3    0x303d // Bit[5:4]: PLLS pre-divider
+                                //          00: 1
+                                //          01: 1.5
+                                //          10: 2
+                                //          11: 3
+                                // Bit[2]: PLLS root-divider - 1
+                                // Bit[1:0]: PLLS seld5
+                                //          00: 1
+                                //          01: 1
+                                //          10: 2
+                                //          11: 2.5
+
+/* AEC/AGC control functions */
+#define AEC_PK_MANUAL   0x3503  // AEC Manual Mode Control
+                                // Bit[7:6]: Reserved
+                                // Bit[5]: Gain delay option
+                                //         Valid when 0x3503[4]=1’b0
+                                //         0: Delay one frame latch
+                                //         1: One frame latch
+                                // Bit[4:2]: Reserved
+                                // Bit[1]: AGC manual
+                                //         0: Auto enable
+                                //         1: Manual enable
+                                // Bit[0]: AEC manual
+                                //         0: Auto enable
+                                //         1: Manual enable
+
+//gain = {0x350A[1:0], 0x350B[7:0]} / 16
+
+
+#define X_ADDR_ST_H     0x3800 //Bit[3:0]: X address start[11:8]
+#define X_ADDR_ST_L     0x3801 //Bit[7:0]: X address start[7:0]
+#define Y_ADDR_ST_H     0x3802 //Bit[2:0]: Y address start[10:8]
+#define Y_ADDR_ST_L     0x3803 //Bit[7:0]: Y address start[7:0]
+#define X_ADDR_END_H    0x3804 //Bit[3:0]: X address end[11:8]
+#define X_ADDR_END_L    0x3805 //Bit[7:0]:
+#define Y_ADDR_END_H    0x3806 //Bit[2:0]: Y address end[10:8]
+#define Y_ADDR_END_L    0x3807 //Bit[7:0]:
+// Size after scaling
+#define X_OUTPUT_SIZE_H 0x3808 //Bit[3:0]: DVP output horizontal width[11:8]
+#define X_OUTPUT_SIZE_L 0x3809 //Bit[7:0]:
+#define Y_OUTPUT_SIZE_H 0x380a //Bit[2:0]: DVP output vertical height[10:8]
+#define Y_OUTPUT_SIZE_L 0x380b //Bit[7:0]:
+#define X_TOTAL_SIZE_H  0x380c //Bit[3:0]: Total horizontal size[11:8]
+#define X_TOTAL_SIZE_L  0x380d //Bit[7:0]:
+#define Y_TOTAL_SIZE_H  0x380e //Bit[7:0]: Total vertical size[15:8]
+#define Y_TOTAL_SIZE_L  0x380f //Bit[7:0]:
+#define X_OFFSET_H      0x3810 //Bit[3:0]: ISP horizontal offset[11:8]
+#define X_OFFSET_L      0x3811 //Bit[7:0]:
+#define Y_OFFSET_H      0x3812 //Bit[2:0]: ISP vertical offset[10:8]
+#define Y_OFFSET_L      0x3813 //Bit[7:0]:
+#define X_INCREMENT     0x3814 //Bit[7:4]: Horizontal odd subsample increment
+                               //Bit[3:0]: Horizontal even subsample increment
+#define Y_INCREMENT     0x3815 //Bit[7:4]: Vertical odd subsample increment
+                               //Bit[3:0]: Vertical even subsample increment
+// Size before scaling
+//#define X_INPUT_SIZE    (X_ADDR_END - X_ADDR_ST + 1 - (2 * X_OFFSET))
+//#define Y_INPUT_SIZE    (Y_ADDR_END - Y_ADDR_ST + 1 - (2 * Y_OFFSET))
+
+/* mirror and flip registers */
+#define TIMING_TC_REG20 0x3820  // Timing Control Register
+                                // Bit[2:1]: Vertical flip enable
+                                //         00: Normal
+                                //         11: Vertical flip
+                                // Bit[0]: Vertical binning enable
+#define TIMING_TC_REG21 0x3821  // Timing Control Register
+                                // Bit[5]: Compression Enable
+                                // Bit[2:1]: Horizontal mirror enable
+                                //         00: Normal
+                                //         11: Horizontal mirror
+                                // Bit[0]: Horizontal binning enable
+
+#define PCLK_RATIO       0x3824 // Bit[4:0]: PCLK ratio manual
+
+/* frame control registers */
+#define FRAME_CTRL01    0x4201  // Control Passed Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode
+                                // Bit[7:4]: Not used
+                                // Bit[3:0]: Frame ON number
+#define FRAME_CTRL02    0x4202  // Control Masked Frame Number When both ON and OFF number set to 0x00,frame control is in bypass mode
+                                // Bit[7:4]: Not used
+                                // BIT[3:0]: Frame OFF number
+
+/* format control registers */
+#define FORMAT_CTRL00   0x4300
+
+#define CLOCK_POL_CONTROL 0x4740// Bit[5]: PCLK polarity 0: active low
+                                //          1: active high
+                                // Bit[3]: Gate PCLK under VSYNC
+                                // Bit[2]: Gate PCLK under HREF
+                                // Bit[1]: HREF polarity
+                                //          0: active low
+                                //          1: active high
+                                // Bit[0] VSYNC polarity
+                                //          0: active low
+                                //          1: active high
+
+#define ISP_CONTROL_01   0x5001 // Bit[5]: Scale enable
+                                //          0: Disable
+                                //          1: Enable
+
+/* output format control registers */
+#define FORMAT_CTRL     0x501F // Format select
+                                // Bit[2:0]:
+                                //  000: YUV422
+                                //  001: RGB
+                                //  010: Dither
+                                //  011: RAW after DPC
+                                //  101: RAW after CIP
+
+/* ISP top control registers */
+#define PRE_ISP_TEST_SETTING_1  0x503D  // Bit[7]: Test enable
+                                        //         0: Test disable
+                                        //         1: Color bar enable
+                                        // Bit[6]: Rolling
+                                        // Bit[5]: Transparent
+                                        // Bit[4]: Square black and white
+                                        // Bit[3:2]: Color bar style
+                                        //         00: Standard 8 color bar
+                                        //         01: Gradual change at vertical mode 1
+                                        //         10: Gradual change at horizontal
+                                        //         11: Gradual change at vertical mode 2
+                                        // Bit[1:0]: Test select
+                                        //         00: Color bar
+                                        //         01: Random data
+                                        //         10: Square data
+                                        //         11: Black image
+
+//exposure = {0x3500[3:0], 0x3501[7:0], 0x3502[7:0]} / 16 × tROW
+
+#define SCALE_CTRL_1     0x5601 // Bit[6:4]: HDIV RW
+                                //          DCW scale times
+                                //          000: DCW 1 time
+                                //          001: DCW 2 times
+                                //          010: DCW 4 times
+                                //          100: DCW 8 times
+                                //          101: DCW 16 times
+                                //          Others: DCW 16 times
+                                // Bit[2:0]: VDIV RW
+                                //          DCW scale times
+                                //          000: DCW 1 time
+                                //          001: DCW 2 times
+                                //          010: DCW 4 times
+                                //          100: DCW 8 times
+                                //          101: DCW 16 times
+                                //          Others: DCW 16 times
+
+#define SCALE_CTRL_2     0x5602 // X_SCALE High Bits
+#define SCALE_CTRL_3     0x5603 // X_SCALE Low Bits
+#define SCALE_CTRL_4     0x5604 // Y_SCALE High Bits
+#define SCALE_CTRL_5     0x5605 // Y_SCALE Low Bits
+#define SCALE_CTRL_6     0x5606 // Bit[3:0]: V Offset
+
+#define VFIFO_CTRL0C     0x460C // Bit[1]: PCLK manual enable
+                                //          0: Auto
+                                //          1: Manual by PCLK_RATIO
+
+#define VFIFO_X_SIZE_H   0x4602
+#define VFIFO_X_SIZE_L   0x4603
+#define VFIFO_Y_SIZE_H   0x4604
+#define VFIFO_Y_SIZE_L   0x4605
+
+#define COMPRESSION_CTRL00 0x4400 //
+#define COMPRESSION_CTRL01 0x4401 //
+#define COMPRESSION_CTRL02 0x4402 //
+#define COMPRESSION_CTRL03 0x4403 //
+#define COMPRESSION_CTRL04 0x4404 //
+#define COMPRESSION_CTRL05 0x4405 //
+#define COMPRESSION_CTRL06 0x4406 //
+#define COMPRESSION_CTRL07 0x4407 // Bit[5:0]: QS
+#define COMPRESSION_ISI_CTRL 0x4408 //
+#define COMPRESSION_CTRL09 0x4409 //
+#define COMPRESSION_CTRL0a 0x440a //
+#define COMPRESSION_CTRL0b 0x440b //
+#define COMPRESSION_CTRL0c 0x440c //
+#define COMPRESSION_CTRL0d 0x440d //
+#define COMPRESSION_CTRL0E 0x440e //
+
+/**
+ * @brief register value
+ */
+#define TEST_COLOR_BAR  0xC0    /* Enable Color Bar roling Test */
+
+#define AEC_PK_MANUAL_AGC_MANUALEN  0x02    /* Enable AGC Manual enable */
+#define AEC_PK_MANUAL_AEC_MANUALEN  0x01    /* Enable AEC Manual enable */
+
+#define TIMING_TC_REG20_VFLIP   0x06 /* Vertical flip enable */
+#define TIMING_TC_REG21_HMIRROR 0x06 /* Horizontal mirror enable */
+
+#endif // __OV3660_REG_REGS_H__

+ 334 - 0
code/lib/sensors/ov5640_settings.h

@@ -0,0 +1,334 @@
+#ifndef _OV5640_SETTINGS_H_
+#define _OV5640_SETTINGS_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "esp_attr.h"
+#include "ov5640_regs.h"
+
+static const ratio_settings_t ratio_table[] = {
+    //  mw,   mh,  sx,  sy,   ex,   ey, ox, oy,   tx,   ty
+    { 2560, 1920,   0,   0, 2623, 1951, 32, 16, 2844, 1968 }, //4x3
+    { 2560, 1704,   0, 110, 2623, 1843, 32, 16, 2844, 1752 }, //3x2
+    { 2560, 1600,   0, 160, 2623, 1791, 32, 16, 2844, 1648 }, //16x10
+    { 2560, 1536,   0, 192, 2623, 1759, 32, 16, 2844, 1584 }, //5x3
+    { 2560, 1440,   0, 240, 2623, 1711, 32, 16, 2844, 1488 }, //16x9
+    { 2560, 1080,   0, 420, 2623, 1531, 32, 16, 2844, 1128 }, //21x9
+    { 2400, 1920,  80,   0, 2543, 1951, 32, 16, 2684, 1968 }, //5x4
+    { 1920, 1920, 320,   0, 2543, 1951, 32, 16, 2684, 1968 }, //1x1
+    { 1088, 1920, 736,   0, 1887, 1951, 32, 16, 1884, 1968 }  //9x16
+};
+
+#define REG_DLY 0xffff
+#define REGLIST_TAIL 0x0000
+
+static const DRAM_ATTR uint16_t sensor_default_regs[][2] = {
+    {SYSTEM_CTROL0, 0x82},  // software reset
+    {REG_DLY, 10}, // delay 10ms
+    {SYSTEM_CTROL0, 0x42},  // power down
+
+    //enable pll
+    {0x3103, 0x13},
+
+    //io direction
+    {0x3017, 0xff},
+    {0x3018, 0xff},
+
+    {DRIVE_CAPABILITY, 0xc3},
+    {CLOCK_POL_CONTROL, 0x21},
+
+    {0x4713, 0x02},//jpg mode select
+
+    {ISP_CONTROL_01, 0x83}, // turn color matrix, awb and SDE
+
+    //sys reset
+    {0x3000, 0x00},
+    {0x3002, 0x1c},
+
+    //clock enable
+    {0x3004, 0xff},
+    {0x3006, 0xc3},
+
+    //isp control
+    {0x5000, 0xa7},
+    {ISP_CONTROL_01, 0xa3},//+scaling?
+    {0x5003, 0x08},//special_effect
+
+    //unknown
+    {0x370c, 0x02},//!!IMPORTANT
+    {0x3634, 0x40},//!!IMPORTANT
+
+    //AEC/AGC
+    {0x3a02, 0x03},
+    {0x3a03, 0xd8},
+    {0x3a08, 0x01},
+    {0x3a09, 0x27},
+    {0x3a0a, 0x00},
+    {0x3a0b, 0xf6},
+    {0x3a0d, 0x04},
+    {0x3a0e, 0x03},
+    {0x3a0f, 0x30},//ae_level
+    {0x3a10, 0x28},//ae_level
+    {0x3a11, 0x60},//ae_level
+    {0x3a13, 0x43},
+    {0x3a14, 0x03},
+    {0x3a15, 0xd8},
+    {0x3a18, 0x00},//gainceiling
+    {0x3a19, 0xf8},//gainceiling
+    {0x3a1b, 0x30},//ae_level
+    {0x3a1e, 0x26},//ae_level
+    {0x3a1f, 0x14},//ae_level
+
+    //vcm debug
+    {0x3600, 0x08},
+    {0x3601, 0x33},
+
+    //50/60Hz
+    {0x3c01, 0xa4},
+    {0x3c04, 0x28},
+    {0x3c05, 0x98},
+    {0x3c06, 0x00},
+    {0x3c07, 0x08},
+    {0x3c08, 0x00},
+    {0x3c09, 0x1c},
+    {0x3c0a, 0x9c},
+    {0x3c0b, 0x40},
+
+    {0x460c, 0x22},//disable jpeg footer
+
+    //BLC
+    {0x4001, 0x02},
+    {0x4004, 0x02},
+
+    //AWB
+    {0x5180, 0xff},
+    {0x5181, 0xf2},
+    {0x5182, 0x00},
+    {0x5183, 0x14},
+    {0x5184, 0x25},
+    {0x5185, 0x24},
+    {0x5186, 0x09},
+    {0x5187, 0x09},
+    {0x5188, 0x09},
+    {0x5189, 0x75},
+    {0x518a, 0x54},
+    {0x518b, 0xe0},
+    {0x518c, 0xb2},
+    {0x518d, 0x42},
+    {0x518e, 0x3d},
+    {0x518f, 0x56},
+    {0x5190, 0x46},
+    {0x5191, 0xf8},
+    {0x5192, 0x04},
+    {0x5193, 0x70},
+    {0x5194, 0xf0},
+    {0x5195, 0xf0},
+    {0x5196, 0x03},
+    {0x5197, 0x01},
+    {0x5198, 0x04},
+    {0x5199, 0x12},
+    {0x519a, 0x04},
+    {0x519b, 0x00},
+    {0x519c, 0x06},
+    {0x519d, 0x82},
+    {0x519e, 0x38},
+
+    //color matrix (Saturation)
+    {0x5381, 0x1e},
+    {0x5382, 0x5b},
+    {0x5383, 0x08},
+    {0x5384, 0x0a},
+    {0x5385, 0x7e},
+    {0x5386, 0x88},
+    {0x5387, 0x7c},
+    {0x5388, 0x6c},
+    {0x5389, 0x10},
+    {0x538a, 0x01},
+    {0x538b, 0x98},
+
+    //CIP control (Sharpness)
+    {0x5300, 0x10},//sharpness
+    {0x5301, 0x10},//sharpness
+    {0x5302, 0x18},//sharpness
+    {0x5303, 0x19},//sharpness
+    {0x5304, 0x10},
+    {0x5305, 0x10},
+    {0x5306, 0x08},//denoise
+    {0x5307, 0x16},
+    {0x5308, 0x40},
+    {0x5309, 0x10},//sharpness
+    {0x530a, 0x10},//sharpness
+    {0x530b, 0x04},//sharpness
+    {0x530c, 0x06},//sharpness
+
+    //GAMMA
+    {0x5480, 0x01},
+    {0x5481, 0x00},
+    {0x5482, 0x1e},
+    {0x5483, 0x3b},
+    {0x5484, 0x58},
+    {0x5485, 0x66},
+    {0x5486, 0x71},
+    {0x5487, 0x7d},
+    {0x5488, 0x83},
+    {0x5489, 0x8f},
+    {0x548a, 0x98},
+    {0x548b, 0xa6},
+    {0x548c, 0xb8},
+    {0x548d, 0xca},
+    {0x548e, 0xd7},
+    {0x548f, 0xe3},
+    {0x5490, 0x1d},
+
+    //Special Digital Effects (SDE) (UV adjust)
+    {0x5580, 0x06},//enable brightness and contrast
+    {0x5583, 0x40},//special_effect
+    {0x5584, 0x10},//special_effect
+    {0x5586, 0x20},//contrast
+    {0x5587, 0x00},//brightness
+    {0x5588, 0x00},//brightness
+    {0x5589, 0x10},
+    {0x558a, 0x00},
+    {0x558b, 0xf8},
+    {0x501d, 0x40},// enable manual offset of contrast
+
+    //power on
+    {0x3008, 0x02},
+
+    //50Hz
+    {0x3c00, 0x04},
+    
+    {REG_DLY, 300},
+    {REGLIST_TAIL, 0x00}, // tail
+};
+
+static const DRAM_ATTR uint16_t sensor_fmt_jpeg[][2] = {
+    {FORMAT_CTRL, 0x00}, // YUV422
+    {FORMAT_CTRL00, 0x30}, // YUYV
+    {0x3002, 0x00},//0x1c to 0x00 !!!
+    {0x3006, 0xff},//0xc3 to 0xff !!!
+    {0x471c, 0x50},//0xd0 to 0x50 !!!
+    {REGLIST_TAIL, 0x00}, // tail
+};
+
+static const DRAM_ATTR uint16_t sensor_fmt_raw[][2] = {
+    {FORMAT_CTRL, 0x03}, // RAW (DPC)
+    {FORMAT_CTRL00, 0x00}, // RAW
+    {REGLIST_TAIL, 0x00}
+};
+
+static const DRAM_ATTR uint16_t sensor_fmt_grayscale[][2] = {
+    {FORMAT_CTRL, 0x00}, // YUV422
+    {FORMAT_CTRL00, 0x10}, // Y8
+    {REGLIST_TAIL, 0x00}
+};
+
+static const DRAM_ATTR uint16_t sensor_fmt_yuv422[][2] = {
+    {FORMAT_CTRL, 0x00}, // YUV422
+    {FORMAT_CTRL00, 0x30}, // YUYV
+    {REGLIST_TAIL, 0x00}
+};
+
+static const DRAM_ATTR uint16_t sensor_fmt_rgb565[][2] = {
+    {FORMAT_CTRL, 0x01}, // RGB
+    {FORMAT_CTRL00, 0x61}, // RGB565 (BGR)
+    {REGLIST_TAIL, 0x00}
+};
+
+static const DRAM_ATTR uint8_t sensor_saturation_levels[9][11] = {
+    {0x1d, 0x60, 0x03, 0x07, 0x48, 0x4f, 0x4b, 0x40, 0x0b, 0x01, 0x98},//-4
+    {0x1d, 0x60, 0x03, 0x08, 0x54, 0x5c, 0x58, 0x4b, 0x0d, 0x01, 0x98},//-3
+    {0x1d, 0x60, 0x03, 0x0a, 0x60, 0x6a, 0x64, 0x56, 0x0e, 0x01, 0x98},//-2
+    {0x1d, 0x60, 0x03, 0x0b, 0x6c, 0x77, 0x70, 0x60, 0x10, 0x01, 0x98},//-1
+    {0x1d, 0x60, 0x03, 0x0c, 0x78, 0x84, 0x7d, 0x6b, 0x12, 0x01, 0x98},//0
+    {0x1d, 0x60, 0x03, 0x0d, 0x84, 0x91, 0x8a, 0x76, 0x14, 0x01, 0x98},//+1
+    {0x1d, 0x60, 0x03, 0x0e, 0x90, 0x9e, 0x96, 0x80, 0x16, 0x01, 0x98},//+2
+    {0x1d, 0x60, 0x03, 0x10, 0x9c, 0xac, 0xa2, 0x8b, 0x17, 0x01, 0x98},//+3
+    {0x1d, 0x60, 0x03, 0x11, 0xa8, 0xb9, 0xaf, 0x96, 0x19, 0x01, 0x98},//+4
+};
+
+static const DRAM_ATTR uint8_t sensor_special_effects[7][4] = {
+    {0x06, 0x40, 0x2c, 0x08},//Normal
+    {0x46, 0x40, 0x28, 0x08},//Negative
+    {0x1e, 0x80, 0x80, 0x08},//Grayscale
+    {0x1e, 0x80, 0xc0, 0x08},//Red Tint
+    {0x1e, 0x60, 0x60, 0x08},//Green Tint
+    {0x1e, 0xa0, 0x40, 0x08},//Blue Tint
+    {0x1e, 0x40, 0xa0, 0x08},//Sepia
+};
+
+static const DRAM_ATTR uint16_t sensor_regs_gamma0[][2] = {
+    {0x5480, 0x01},
+    {0x5481, 0x08},
+    {0x5482, 0x14},
+    {0x5483, 0x28},
+    {0x5484, 0x51},
+    {0x5485, 0x65},
+    {0x5486, 0x71},
+    {0x5487, 0x7d},
+    {0x5488, 0x87},
+    {0x5489, 0x91},
+    {0x548a, 0x9a},
+    {0x548b, 0xaa},
+    {0x548c, 0xb8},
+    {0x548d, 0xcd},
+    {0x548e, 0xdd},
+    {0x548f, 0xea},
+    {0x5490, 0x1d}
+};
+
+static const DRAM_ATTR uint16_t sensor_regs_gamma1[][2] = {
+    {0x5480, 0x1},
+    {0x5481, 0x0},
+    {0x5482, 0x1e},
+    {0x5483, 0x3b},
+    {0x5484, 0x58},
+    {0x5485, 0x66},
+    {0x5486, 0x71},
+    {0x5487, 0x7d},
+    {0x5488, 0x83},
+    {0x5489, 0x8f},
+    {0x548a, 0x98},
+    {0x548b, 0xa6},
+    {0x548c, 0xb8},
+    {0x548d, 0xca},
+    {0x548e, 0xd7},
+    {0x548f, 0xe3},
+    {0x5490, 0x1d}
+};
+
+static const DRAM_ATTR uint16_t sensor_regs_awb0[][2] = {
+    {0x5180, 0xff},
+    {0x5181, 0xf2},
+    {0x5182, 0x00},
+    {0x5183, 0x14},
+    {0x5184, 0x25},
+    {0x5185, 0x24},
+    {0x5186, 0x09},
+    {0x5187, 0x09},
+    {0x5188, 0x09},
+    {0x5189, 0x75},
+    {0x518a, 0x54},
+    {0x518b, 0xe0},
+    {0x518c, 0xb2},
+    {0x518d, 0x42},
+    {0x518e, 0x3d},
+    {0x518f, 0x56},
+    {0x5190, 0x46},
+    {0x5191, 0xf8},
+    {0x5192, 0x04},
+    {0x5193, 0x70},
+    {0x5194, 0xf0},
+    {0x5195, 0xf0},
+    {0x5196, 0x03},
+    {0x5197, 0x01},
+    {0x5198, 0x04},
+    {0x5199, 0x12},
+    {0x519a, 0x04},
+    {0x519b, 0x00},
+    {0x519c, 0x06},
+    {0x519d, 0x82},
+    {0x519e, 0x38}
+};
+
+#endif

+ 557 - 0
code/lib/sensors/ov7725.c

@@ -0,0 +1,557 @@
+/*
+ * This file is part of the OpenMV project.
+ * Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
+ * This work is licensed under the MIT license, see the file LICENSE for details.
+ *
+ * OV7725 driver.
+ *
+ */
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include "sccb.h"
+#include "ov7725.h"
+#include "ov7725_regs.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#if defined(ARDUINO_ARCH_ESP32) && defined(CONFIG_ARDUHAL_ESP_LOG)
+#include "esp32-hal-log.h"
+#else
+#include "esp_log.h"
+static const char* TAG = "ov7725";
+#endif
+
+
+static const uint8_t default_regs[][2] = {
+    {COM3,          COM3_SWAP_YUV},
+    {COM7,          COM7_RES_QVGA | COM7_FMT_YUV},
+
+    {COM4,          0x01 | 0x00}, /* bypass PLL (0x00:off, 0x40:4x, 0x80:6x, 0xC0:8x) */
+    {CLKRC,         0x80 | 0x03}, /* Res/Bypass pre-scalar (0x40:bypass, 0x00-0x3F:prescaler PCLK=XCLK/(prescaler + 1)/2 ) */
+
+    // QVGA Window Size
+    {HSTART,        0x3F},
+    {HSIZE,         0x50},
+    {VSTART,        0x03},
+    {VSIZE,         0x78},
+    {HREF,          0x00},
+
+    // Scale down to QVGA Resolution
+    {HOUTSIZE,      0x50},
+    {VOUTSIZE,      0x78},
+    {EXHCH,         0x00},
+
+    {COM12,         0x03},
+    {TGT_B,         0x7F},
+    {FIXGAIN,       0x09},
+    {AWB_CTRL0,     0xE0},
+    {DSP_CTRL1,     0xFF},
+
+    {DSP_CTRL2,     DSP_CTRL2_VDCW_EN | DSP_CTRL2_HDCW_EN | DSP_CTRL2_HZOOM_EN | DSP_CTRL2_VZOOM_EN},
+
+    {DSP_CTRL3,     0x00},
+    {DSP_CTRL4,     0x00},
+    {DSPAUTO,       0xFF},
+
+    {COM8,          0xF0},
+    {COM6,          0xC5},
+    {COM9,          0x11},
+    {COM10,         COM10_VSYNC_NEG | COM10_PCLK_MASK}, //Invert VSYNC and MASK PCLK
+    {BDBASE,        0x7F},
+    {DBSTEP,        0x03},
+    {AEW,           0x96},
+    {AEB,           0x64},
+    {VPT,           0xA1},
+    {EXHCL,         0x00},
+    {AWB_CTRL3,     0xAA},
+    {COM8,          0xFF},
+
+    //Gamma
+    {GAM1,          0x0C},
+    {GAM2,          0x16},
+    {GAM3,          0x2A},
+    {GAM4,          0x4E},
+    {GAM5,          0x61},
+    {GAM6,          0x6F},
+    {GAM7,          0x7B},
+    {GAM8,          0x86},
+    {GAM9,          0x8E},
+    {GAM10,         0x97},
+    {GAM11,         0xA4},
+    {GAM12,         0xAF},
+    {GAM13,         0xC5},
+    {GAM14,         0xD7},
+    {GAM15,         0xE8},
+
+    {SLOP,          0x20},
+    {EDGE1,         0x05},
+    {EDGE2,         0x03},
+    {EDGE3,         0x00},
+    {DNSOFF,        0x01},
+
+    {MTX1,          0xB0},
+    {MTX2,          0x9D},
+    {MTX3,          0x13},
+    {MTX4,          0x16},
+    {MTX5,          0x7B},
+    {MTX6,          0x91},
+    {MTX_CTRL,      0x1E},
+
+    {BRIGHTNESS,    0x08},
+    {CONTRAST,      0x30},
+    {UVADJ0,        0x81},
+    {SDE,           (SDE_CONT_BRIGHT_EN | SDE_SATURATION_EN)},
+
+    // For 30 fps/60Hz
+    {DM_LNL,        0x00},
+    {DM_LNH,        0x00},
+    {BDBASE,        0x7F},
+    {DBSTEP,        0x03},
+
+    // Lens Correction, should be tuned with real camera module
+    {LC_RADI,       0x10},
+    {LC_COEF,       0x10},
+    {LC_COEFB,      0x14},
+    {LC_COEFR,      0x17},
+    {LC_CTR,        0x05},
+    {COM5,          0xF5}, //0x65
+
+    {0x00,          0x00},
+};
+
+static int get_reg(sensor_t *sensor, int reg, int mask)
+{
+    int ret = SCCB_Read(sensor->slv_addr, reg & 0xFF);
+    if(ret > 0){
+        ret &= mask;
+    }
+    return ret;
+}
+
+static int set_reg(sensor_t *sensor, int reg, int mask, int value)
+{
+    int ret = 0;
+    ret = SCCB_Read(sensor->slv_addr, reg & 0xFF);
+    if(ret < 0){
+        return ret;
+    }
+    value = (ret & ~mask) | (value & mask);
+    ret = SCCB_Write(sensor->slv_addr, reg & 0xFF, value);
+    return ret;
+}
+
+static int set_reg_bits(sensor_t *sensor, uint8_t reg, uint8_t offset, uint8_t length, uint8_t value)
+{
+    int ret = 0;
+    ret = SCCB_Read(sensor->slv_addr, reg);
+    if(ret < 0){
+        return ret;
+    }
+    uint8_t mask = ((1 << length) - 1) << offset;
+    value = (ret & ~mask) | ((value << offset) & mask);
+    ret = SCCB_Write(sensor->slv_addr, reg & 0xFF, value);
+    return ret;
+}
+
+static int get_reg_bits(sensor_t *sensor, uint8_t reg, uint8_t offset, uint8_t length)
+{
+    int ret = 0;
+    ret = SCCB_Read(sensor->slv_addr, reg);
+    if(ret < 0){
+        return ret;
+    }
+    uint8_t mask = ((1 << length) - 1) << offset;
+    return (ret & mask) >> offset;
+}
+
+
+static int reset(sensor_t *sensor)
+{
+    int i=0;
+    const uint8_t (*regs)[2];
+
+    // Reset all registers
+    SCCB_Write(sensor->slv_addr, COM7, COM7_RESET);
+
+    // Delay 10 ms
+    vTaskDelay(10 / portTICK_PERIOD_MS);
+
+    // Write default regsiters
+    for (i=0, regs = default_regs; regs[i][0]; i++) {
+        SCCB_Write(sensor->slv_addr, regs[i][0], regs[i][1]);
+    }
+
+    // Delay
+    vTaskDelay(30 / portTICK_PERIOD_MS);
+
+    return 0;
+}
+
+
+static int set_pixformat(sensor_t *sensor, pixformat_t pixformat)
+{
+    int ret=0;
+    sensor->pixformat = pixformat;
+    // Read register COM7
+    uint8_t reg = SCCB_Read(sensor->slv_addr, COM7);
+
+    switch (pixformat) {
+    case PIXFORMAT_RGB565:
+        reg =  COM7_SET_RGB(reg, COM7_FMT_RGB565);
+        break;
+    case PIXFORMAT_YUV422:
+    case PIXFORMAT_GRAYSCALE:
+        reg =  COM7_SET_FMT(reg, COM7_FMT_YUV);
+        break;
+    default:
+        return -1;
+    }
+
+    // Write back register COM7
+    ret = SCCB_Write(sensor->slv_addr, COM7, reg);
+
+    // Delay
+    vTaskDelay(30 / portTICK_PERIOD_MS);
+
+    return ret;
+}
+
+static int set_framesize(sensor_t *sensor, framesize_t framesize)
+{
+    int ret=0;
+    if (framesize > FRAMESIZE_VGA) {
+        return -1;
+    }
+    uint16_t w = resolution[framesize].width;
+    uint16_t h = resolution[framesize].height;
+    uint8_t reg = SCCB_Read(sensor->slv_addr, COM7);
+
+    sensor->status.framesize = framesize;
+
+    // Write MSBs
+    ret |= SCCB_Write(sensor->slv_addr, HOUTSIZE, w>>2);
+    ret |= SCCB_Write(sensor->slv_addr, VOUTSIZE, h>>1);
+
+    ret |= SCCB_Write(sensor->slv_addr, HSIZE, w>>2);
+    ret |= SCCB_Write(sensor->slv_addr, VSIZE, h>>1);
+
+    // Write LSBs
+    ret |= SCCB_Write(sensor->slv_addr, HREF, ((w&0x3) | ((h&0x1) << 2)));
+
+    if (framesize < FRAMESIZE_VGA) {
+        // Enable auto-scaling/zooming factors
+        ret |= SCCB_Write(sensor->slv_addr, DSPAUTO, 0xFF);
+
+        ret |= SCCB_Write(sensor->slv_addr, HSTART, 0x3F);
+        ret |= SCCB_Write(sensor->slv_addr, VSTART, 0x03);
+
+        ret |= SCCB_Write(sensor->slv_addr, COM7, reg | COM7_RES_QVGA);
+
+        ret |= SCCB_Write(sensor->slv_addr, CLKRC, 0x80 | 0x01);
+
+    } else {
+        // Disable auto-scaling/zooming factors
+        ret |= SCCB_Write(sensor->slv_addr, DSPAUTO, 0xF3);
+
+        // Clear auto-scaling/zooming factors
+        ret |= SCCB_Write(sensor->slv_addr, SCAL0, 0x00);
+        ret |= SCCB_Write(sensor->slv_addr, SCAL1, 0x00);
+        ret |= SCCB_Write(sensor->slv_addr, SCAL2, 0x00);
+
+        ret |= SCCB_Write(sensor->slv_addr, HSTART, 0x23);
+        ret |= SCCB_Write(sensor->slv_addr, VSTART, 0x07);
+
+        ret |= SCCB_Write(sensor->slv_addr, COM7, reg & ~COM7_RES_QVGA);
+
+        ret |= SCCB_Write(sensor->slv_addr, CLKRC, 0x80 | 0x03);
+    }
+
+    // Delay
+    vTaskDelay(30 / portTICK_PERIOD_MS);
+
+    return ret;
+}
+
+static int set_colorbar(sensor_t *sensor, int enable)
+{
+    int ret=0;
+    uint8_t reg;
+    sensor->status.colorbar = enable;
+
+    // Read reg COM3
+    reg = SCCB_Read(sensor->slv_addr, COM3);
+    // Enable colorbar test pattern output
+    reg = COM3_SET_CBAR(reg, enable);
+    // Write back COM3
+    ret |= SCCB_Write(sensor->slv_addr, COM3, reg);
+
+    // Read reg DSP_CTRL3
+    reg = SCCB_Read(sensor->slv_addr, DSP_CTRL3);
+    // Enable DSP colorbar output
+    reg = DSP_CTRL3_SET_CBAR(reg, enable);
+    // Write back DSP_CTRL3
+    ret |= SCCB_Write(sensor->slv_addr, DSP_CTRL3, reg);
+
+    return ret;
+}
+
+static int set_whitebal(sensor_t *sensor, int enable)
+{
+    if(set_reg_bits(sensor, COM8, 1, 1, enable) >= 0){
+        sensor->status.awb = !!enable;
+    }
+    return sensor->status.awb;
+}
+
+static int set_gain_ctrl(sensor_t *sensor, int enable)
+{
+    if(set_reg_bits(sensor, COM8, 2, 1, enable) >= 0){
+        sensor->status.agc = !!enable;
+    }
+    return sensor->status.agc;
+}
+
+static int set_exposure_ctrl(sensor_t *sensor, int enable)
+{
+    if(set_reg_bits(sensor, COM8, 0, 1, enable) >= 0){
+        sensor->status.aec = !!enable;
+    }
+    return sensor->status.aec;
+}
+
+static int set_hmirror(sensor_t *sensor, int enable)
+{
+    if(set_reg_bits(sensor, COM3, 6, 1, enable) >= 0){
+        sensor->status.hmirror = !!enable;
+    }
+    return sensor->status.hmirror;
+}
+
+static int set_vflip(sensor_t *sensor, int enable)
+{
+    if(set_reg_bits(sensor, COM3, 7, 1, enable) >= 0){
+        sensor->status.vflip = !!enable;
+    }
+    return sensor->status.vflip;
+}
+
+static int set_dcw_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = set_reg_bits(sensor, 0x65, 2, 1, !enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set dcw to: %d", enable);
+        sensor->status.dcw = enable;
+    }
+    return ret;
+}
+
+static int set_aec2(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = set_reg_bits(sensor, COM8, 7, 1, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set aec2 to: %d", enable);
+        sensor->status.aec2 = enable;
+    }
+    return ret;
+}
+
+static int set_bpc_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = set_reg_bits(sensor, 0x64, 1, 1, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set bpc to: %d", enable);
+        sensor->status.bpc = enable;
+    }
+    return ret;
+}
+
+static int set_wpc_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = set_reg_bits(sensor, 0x64, 0, 1, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set wpc to: %d", enable);
+        sensor->status.wpc = enable;
+    }
+    return ret;
+}
+
+static int set_raw_gma_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = set_reg_bits(sensor, 0x64, 2, 1, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set raw_gma to: %d", enable);
+        sensor->status.raw_gma = enable;
+    }
+    return ret;
+}
+
+static int set_lenc_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = set_reg_bits(sensor, LC_CTR, 0, 1, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set lenc to: %d", enable);
+        sensor->status.lenc = enable;
+    }
+    return ret;
+}
+
+//real gain
+static int set_agc_gain(sensor_t *sensor, int gain)
+{
+    int ret = 0;
+    ret = set_reg_bits(sensor, COM9, 4, 3, gain % 5);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set gain to: %d", gain);
+        sensor->status.agc_gain = gain;
+    }
+    return ret;
+}
+
+static int set_aec_value(sensor_t *sensor, int value)
+{
+    int ret = 0;
+    ret =  SCCB_Write(sensor->slv_addr, AEC, value & 0xff) | SCCB_Write(sensor->slv_addr, AECH, value >> 8);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set aec_value to: %d", value);
+        sensor->status.aec_value = value;
+    }
+    return ret;
+}
+
+static int set_awb_gain_dsp(sensor_t *sensor, int enable)
+{
+    int ret = 0;
+    ret = set_reg_bits(sensor, 0x63, 7, 1, enable);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set awb_gain to: %d", enable);
+        sensor->status.awb_gain = enable;
+    }
+    return ret;
+}
+
+static int set_brightness(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    ret = SCCB_Write(sensor->slv_addr, 0x9B, level);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set brightness to: %d", level);
+        sensor->status.brightness = level;
+    }
+    return ret;
+}
+
+static int set_contrast(sensor_t *sensor, int level)
+{
+    int ret = 0;
+    ret = SCCB_Write(sensor->slv_addr, 0x9C, level);
+    if (ret == 0) {
+        ESP_LOGD(TAG, "Set contrast to: %d", level);
+        sensor->status.contrast = level;
+    }
+    return ret;
+}
+
+static int init_status(sensor_t *sensor)
+{
+    sensor->status.brightness = SCCB_Read(sensor->slv_addr, 0x9B);
+    sensor->status.contrast = SCCB_Read(sensor->slv_addr, 0x9C);
+    sensor->status.saturation = 0;
+    sensor->status.ae_level = 0;
+    sensor->status.special_effect = get_reg_bits(sensor, 0x64, 5, 1);
+    sensor->status.wb_mode = get_reg_bits(sensor, 0x6B, 7, 1);
+    sensor->status.agc_gain = get_reg_bits(sensor, COM9, 4, 3);
+    sensor->status.aec_value = SCCB_Read(sensor->slv_addr, AEC) | (SCCB_Read(sensor->slv_addr, AECH) << 8);
+    sensor->status.gainceiling = SCCB_Read(sensor->slv_addr, 0x00);
+    sensor->status.awb = get_reg_bits(sensor, COM8, 1, 1);
+    sensor->status.awb_gain = get_reg_bits(sensor, 0x63, 7, 1);
+    sensor->status.aec = get_reg_bits(sensor, COM8, 0, 1);
+    sensor->status.aec2 = get_reg_bits(sensor, COM8, 7, 1);
+    sensor->status.agc = get_reg_bits(sensor, COM8, 2, 1);
+    sensor->status.bpc = get_reg_bits(sensor, 0x64, 1, 1);
+    sensor->status.wpc = get_reg_bits(sensor, 0x64, 0, 1);
+    sensor->status.raw_gma = get_reg_bits(sensor, 0x64, 2, 1);
+    sensor->status.lenc = get_reg_bits(sensor, LC_CTR, 0, 1);
+    sensor->status.hmirror = get_reg_bits(sensor, COM3, 6, 1);
+    sensor->status.vflip = get_reg_bits(sensor, COM3, 7, 1);
+    sensor->status.dcw = get_reg_bits(sensor, 0x65, 2, 1);
+    sensor->status.colorbar = get_reg_bits(sensor, COM3, 0, 1);
+    sensor->status.sharpness = get_reg_bits(sensor, EDGE0, 0, 5);
+    sensor->status.denoise = SCCB_Read(sensor->slv_addr, 0x8E);
+    return 0;
+}
+
+static int set_dummy(sensor_t *sensor, int val){ return -1; }
+static int set_gainceiling_dummy(sensor_t *sensor, gainceiling_t val){ return -1; }
+static int set_res_raw(sensor_t *sensor, int startX, int startY, int endX, int endY, int offsetX, int offsetY, int totalX, int totalY, int outputX, int outputY, bool scale, bool binning){return -1;}
+static int _set_pll(sensor_t *sensor, int bypass, int multiplier, int sys_div, int root_2x, int pre_div, int seld5, int pclk_manual, int pclk_div){return -1;}
+
+esp_err_t xclk_timer_conf(int ledc_timer, int xclk_freq_hz);
+static int set_xclk(sensor_t *sensor, int timer, int xclk)
+{
+    int ret = 0;
+    sensor->xclk_freq_hz = xclk * 1000000U;
+    ret = xclk_timer_conf(timer, sensor->xclk_freq_hz);
+    return ret;
+}
+
+int ov7725_init(sensor_t *sensor)
+{
+    // Set function pointers
+    sensor->reset = reset;
+    sensor->init_status = init_status;
+    sensor->set_pixformat = set_pixformat;
+    sensor->set_framesize = set_framesize;
+    sensor->set_colorbar = set_colorbar;
+    sensor->set_whitebal = set_whitebal;
+    sensor->set_gain_ctrl = set_gain_ctrl;
+    sensor->set_exposure_ctrl = set_exposure_ctrl;
+    sensor->set_hmirror = set_hmirror;
+    sensor->set_vflip = set_vflip;
+
+    sensor->set_brightness = set_brightness;
+    sensor->set_contrast = set_contrast;
+    sensor->set_aec2 = set_aec2;
+    sensor->set_aec_value = set_aec_value;
+    sensor->set_awb_gain = set_awb_gain_dsp;
+    sensor->set_agc_gain = set_agc_gain;
+    sensor->set_dcw = set_dcw_dsp;
+    sensor->set_bpc = set_bpc_dsp;
+    sensor->set_wpc = set_wpc_dsp;
+    sensor->set_raw_gma = set_raw_gma_dsp;
+    sensor->set_lenc = set_lenc_dsp;
+
+    //not supported
+    sensor->set_saturation= set_dummy;
+    sensor->set_sharpness = set_dummy;
+    sensor->set_denoise = set_dummy;
+    sensor->set_quality = set_dummy;
+    sensor->set_special_effect = set_dummy;
+    sensor->set_wb_mode = set_dummy;
+    sensor->set_ae_level = set_dummy;
+    sensor->set_gainceiling = set_gainceiling_dummy;
+
+
+    sensor->get_reg = get_reg;
+    sensor->set_reg = set_reg;
+    sensor->set_res_raw = set_res_raw;
+    sensor->set_pll = _set_pll;
+    sensor->set_xclk = set_xclk;
+    
+    // Retrieve sensor's signature
+    sensor->id.MIDH = SCCB_Read(sensor->slv_addr, REG_MIDH);
+    sensor->id.MIDL = SCCB_Read(sensor->slv_addr, REG_MIDL);
+    sensor->id.PID = SCCB_Read(sensor->slv_addr, REG_PID);
+    sensor->id.VER = SCCB_Read(sensor->slv_addr, REG_VER);
+    
+    ESP_LOGD(TAG, "OV7725 Attached");
+
+    return 0;
+}

+ 14 - 0
code/lib/sensors/ov7725.h

@@ -0,0 +1,14 @@
+/*
+ * This file is part of the OpenMV project.
+ * Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
+ * This work is licensed under the MIT license, see the file LICENSE for details.
+ *
+ * OV7725 driver.
+ *
+ */
+#ifndef __OV7725_H__
+#define __OV7725_H__
+#include "sensor.h"
+
+int ov7725_init(sensor_t *sensor);
+#endif // __OV7725_H__

+ 335 - 0
code/lib/sensors/ov7725_regs.h

@@ -0,0 +1,335 @@
+/*
+ * This file is part of the OpenMV project.
+ * Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
+ * This work is licensed under the MIT license, see the file LICENSE for details.
+ *
+ * OV2640 register definitions.
+ */
+#ifndef __REG_REGS_H__
+#define __REG_REGS_H__
+#define GAIN                    0x00 /* AGC – Gain control gain setting  */
+#define BLUE                    0x01 /* AWB – Blue channel gain setting  */
+#define RED                     0x02 /* AWB – Red channel gain setting   */
+#define GREEN                   0x03 /* AWB – Green channel gain setting */
+#define BAVG                    0x05 /* U/B Average Level   */
+#define GAVG                    0x06 /* Y/Gb Average Level  */
+#define RAVG                    0x07 /* V/R Average Level   */
+#define AECH                    0x08 /* Exposure Value – AEC MSBs */
+
+#define COM2                    0x09 /* Common Control 2 */
+#define COM2_SOFT_SLEEP         0x10 /* Soft sleep mode  */
+#define COM2_OUT_DRIVE_1x       0x00 /* Output drive capability 1x */
+#define COM2_OUT_DRIVE_2x       0x01 /* Output drive capability 2x */
+#define COM2_OUT_DRIVE_3x       0x02 /* Output drive capability 3x */
+#define COM2_OUT_DRIVE_4x       0x03 /* Output drive capability 4x */
+
+#define REG_PID                     0x0A /* Product ID Number MSB */
+#define REG_VER                     0x0B /* Product ID Number LSB */
+
+#define COM3                    0x0C /* Common Control 3                                        */
+#define COM3_VFLIP              0x80 /* Vertical flip image ON/OFF selection                    */
+#define COM3_MIRROR             0x40 /* Horizontal mirror image ON/OFF selection                */
+#define COM3_SWAP_BR            0x20 /* Swap B/R output sequence in RGB output mode             */
+#define COM3_SWAP_YUV           0x10 /* Swap Y/UV output sequence in YUV output mode            */
+#define COM3_SWAP_MSB           0x08 /* Swap output MSB/LSB                                     */
+#define COM3_TRI_CLOCK          0x04 /* Tri-state option for output clock at power-down period  */
+#define COM3_TRI_DATA           0x02 /* Tri-state option for output data at power-down period   */
+#define COM3_COLOR_BAR          0x01 /* Sensor color bar test pattern output enable             */
+#define COM3_SET_CBAR(r, x)     ((r&0xFE)|((x&1)<<0))
+#define COM3_SET_MIRROR(r, x)   ((r&0xBF)|((x&1)<<6))
+#define COM3_SET_FLIP(r, x)     ((r&0x7F)|((x&1)<<7))
+
+#define COM4                    0x0D /* Common Control 4         */
+#define COM4_PLL_BYPASS         0x00 /* Bypass PLL               */
+#define COM4_PLL_4x             0x40 /* PLL frequency 4x         */
+#define COM4_PLL_6x             0x80 /* PLL frequency 6x         */
+#define COM4_PLL_8x             0xc0 /* PLL frequency 8x         */
+#define COM4_AEC_FULL           0x00 /* AEC evaluate full window */
+#define COM4_AEC_1_2            0x10 /* AEC evaluate 1/2 window  */
+#define COM4_AEC_1_4            0x20 /* AEC evaluate 1/4 window  */
+#define COM4_AEC_2_3            0x30 /* AEC evaluate 2/3 window  */
+
+#define COM5                    0x0E /* Common Control 5 */
+#define COM5_AFR                0x80 /* Auto frame rate control ON/OFF selection (night mode) */
+#define COM5_AFR_SPEED          0x40 /* Auto frame rate control speed selection */
+#define COM5_AFR_0              0x00 /* No reduction of frame rate          */
+#define COM5_AFR_1_2            0x10 /* Max reduction to 1/2 frame rate     */
+#define COM5_AFR_1_4            0x20 /* Max reduction to 1/4 frame rate     */
+#define COM5_AFR_1_8            0x30 /* Max reduction to 1/8 frame rate     */
+#define COM5_AFR_4x             0x04 /* Add frame when AGC reaches 4x gain  */
+#define COM5_AFR_8x             0x08 /* Add frame when AGC reaches 8x gain  */
+#define COM5_AFR_16x            0x0c /* Add frame when AGC reaches 16x gain */
+#define COM5_AEC_NO_LIMIT       0x01 /* No limit to AEC increase step       */
+
+#define COM6                    0x0F /* Common Control 6 */
+#define COM6_AUTO_WINDOW        0x01 /* Auto window setting ON/OFF selection when format changes */
+
+#define AEC                     0x10 /* AEC[7:0] (see register AECH for AEC[15:8]) */
+#define CLKRC                   0x11 /* Internal Clock */
+
+#define COM7                    0x12 /* Common Control 7         */
+#define COM7_RESET              0x80 /* SCCB Register Reset      */
+#define COM7_RES_VGA            0x00 /* Resolution VGA           */
+#define COM7_RES_QVGA           0x40 /* Resolution QVGA          */
+#define COM7_BT656              0x20 /* BT.656 protocol ON/OFF   */
+#define COM7_SENSOR_RAW         0x10 /* Sensor RAW               */
+#define COM7_FMT_GBR422         0x00 /* RGB output format GBR422 */
+#define COM7_FMT_RGB565         0x04 /* RGB output format RGB565 */
+#define COM7_FMT_RGB555         0x08 /* RGB output format RGB555 */
+#define COM7_FMT_RGB444         0x0C /* RGB output format RGB444 */
+#define COM7_FMT_YUV            0x00 /* Output format YUV        */
+#define COM7_FMT_P_BAYER        0x01 /* Output format Processed Bayer RAW */
+#define COM7_FMT_RGB            0x02 /* Output format RGB        */
+#define COM7_FMT_R_BAYER        0x03 /* Output format Bayer RAW  */
+#define COM7_SET_FMT(r, x)      ((r&0xFC)|((x&0x3)<<0))
+#define COM7_SET_RGB(r, x)      ((r&0xF0)|(x&0x0C)|COM7_FMT_RGB)
+
+#define COM8                    0x13 /* Common Control 8                */
+#define COM8_FAST_AUTO          0x80 /* Enable fast AGC/AEC algorithm   */
+#define COM8_STEP_VSYNC         0x00 /* AEC - Step size limited to vertical blank */
+#define COM8_STEP_UNLIMIT       0x40 /* AEC - Step size unlimited step size       */
+#define COM8_BANDF_EN           0x20 /* Banding filter ON/OFF */
+#define COM8_AEC_BANDF          0x10 /* Enable AEC below banding value */
+#define COM8_AEC_FINE_EN        0x08 /* Fine AEC ON/OFF control */
+#define COM8_AGC_EN             0x04 /* AGC Enable */
+#define COM8_AWB_EN             0x02 /* AWB Enable */
+#define COM8_AEC_EN             0x01 /* AEC Enable */
+#define COM8_SET_AGC(r, x)      ((r&0xFB)|((x&0x1)<<2))
+#define COM8_SET_AWB(r, x)      ((r&0xFD)|((x&0x1)<<1))
+#define COM8_SET_AEC(r, x)      ((r&0xFE)|((x&0x1)<<0))
+
+#define COM9                    0x14 /* Common Control 9 */
+#define COM9_HISTO_AVG          0x80 /* Histogram or average based AEC/AGC selection */
+#define COM9_AGC_GAIN_2x        0x00 /* Automatic Gain Ceiling 2x  */
+#define COM9_AGC_GAIN_4x        0x10 /* Automatic Gain Ceiling 4x  */
+#define COM9_AGC_GAIN_8x        0x20 /* Automatic Gain Ceiling 8x  */
+#define COM9_AGC_GAIN_16x       0x30 /* Automatic Gain Ceiling 16x */
+#define COM9_AGC_GAIN_32x       0x40 /* Automatic Gain Ceiling 32x */
+#define COM9_DROP_VSYNC         0x04 /* Drop VSYNC output of corrupt frame */
+#define COM9_DROP_HREF          0x02 /* Drop HREF output of corrupt frame  */
+#define COM9_SET_AGC(r, x)      ((r&0x8F)|((x&0x07)<<4))
+
+#define COM10                   0x15 /* Common Control 10 */
+#define COM10_NEGATIVE          0x80 /* Output negative data */
+#define COM10_HSYNC_EN          0x40 /* HREF changes to HSYNC */
+#define COM10_PCLK_FREE         0x00 /* PCLK output option: free running PCLK */
+#define COM10_PCLK_MASK         0x20 /* PCLK output option: masked during horizontal blank  */
+#define COM10_PCLK_REV          0x10 /* PCLK reverse */
+#define COM10_HREF_REV          0x08 /* HREF reverse */
+#define COM10_VSYNC_FALLING     0x00 /* VSYNC changes on falling edge of PCLK */
+#define COM10_VSYNC_RISING      0x04 /* VSYNC changes on rising edge of PCLK */
+#define COM10_VSYNC_NEG         0x02 /* VSYNC negative */
+#define COM10_OUT_RANGE_8       0x01 /* Output data range: Full range */
+#define COM10_OUT_RANGE_10      0x00 /* Output data range: Data from [10] to [F0] (8 MSBs) */
+
+#define REG16                   0x16 /* Register 16 */
+#define REG16_BIT_SHIFT         0x80 /* Bit shift test pattern options */
+#define HSTART                  0x17 /* Horizontal Frame (HREF column) Start 8 MSBs (2 LSBs are at HREF[5:4]) */
+#define HSIZE                   0x18 /* Horizontal Sensor Size (2 LSBs are at HREF[1:0]) */
+#define VSTART                  0x19 /* Vertical Frame (row) Start 8 MSBs (1 LSB is at HREF[6]) */
+#define VSIZE                   0x1A /* Vertical Sensor Size (1 LSB is at HREF[2]) */
+#define PSHFT                   0x1B /* Data Format - Pixel Delay Select */
+#define REG_MIDH                    0x1C /* Manufacturer ID Byte – High */
+#define REG_MIDL                    0x1D /* Manufacturer ID Byte – Low */
+#define LAEC                    0x1F /* Fine AEC Value - defines exposure value less than one row period */
+
+#define COM11                   0x20 /* Common Control 11 */
+#define COM11_SNGL_FRAME_EN     0x02 /* Single frame ON/OFF selection */
+#define COM11_SNGL_XFR_TRIG     0x01 /* Single frame transfer trigger */
+
+#define BDBASE                  0x22 /* Banding Filter Minimum AEC Value */
+#define DBSTEP                  0x23 /* Banding Filter Maximum Step */
+#define AEW                     0x24 /* AGC/AEC - Stable Operating Region (Upper Limit) */
+#define AEB                     0x25 /* AGC/AEC - Stable Operating Region (Lower Limit) */
+#define VPT                     0x26 /* AGC/AEC Fast Mode Operating Region */
+#define REG28                   0x28 /* Selection on the number of dummy rows, N */
+#define HOUTSIZE                0x29 /* Horizontal Data Output Size MSBs (2 LSBs at register EXHCH[1:0]) */
+#define EXHCH                   0x2A /* Dummy Pixel Insert MSB */
+#define EXHCL                   0x2B /* Dummy Pixel Insert LSB */
+#define VOUTSIZE                0x2C /* Vertical Data Output Size MSBs (LSB at register EXHCH[2])       */
+#define ADVFL                   0x2D /* LSB of Insert Dummy Rows in Vertical Sync (1 bit equals 1 row)  */
+#define ADVFH                   0x2E /* MSB of Insert Dummy Rows in Vertical Sync */
+#define YAVE                    0x2F /* Y/G Channel Average Value */
+#define LUMHTH                  0x30 /* Histogram AEC/AGC Luminance High Level Threshold */
+#define LUMLTH                  0x31 /* Histogram AEC/AGC Luminance Low Level Threshold  */
+#define HREF                    0x32 /* Image Start and Size Control */
+#define DM_LNL                  0x33 /* Dummy Row Low 8 Bits  */
+#define DM_LNH                  0x34 /* Dummy Row High 8 Bits */
+#define ADOFF_B                 0x35 /* AD Offset Compensation Value for B Channel  */
+#define ADOFF_R                 0x36 /* AD Offset Compensation Value for R Channel  */
+#define ADOFF_GB                0x37 /* AD Offset Compensation Value for GB Channel */
+#define ADOFF_GR                0x38 /* AD Offset Compensation Value for GR Channel */
+#define OFF_B                   0x39 /* AD Offset Compensation Value for B Channel  */
+#define OFF_R                   0x3A /* AD Offset Compensation Value for R Channel  */
+#define OFF_GB                  0x3B /* AD Offset Compensation Value for GB Channel */
+#define OFF_GR                  0x3C /* AD Offset Compensation Value for GR Channel */
+#define COM12                   0x3D /* DC offset compensation for analog process */
+
+#define COM13                   0x3E /* Common Control 13 */
+#define COM13_BLC_EN            0x80 /* BLC enable */
+#define COM13_ADC_EN            0x40 /* ADC channel BLC ON/OFF control */
+#define COM13_ANALOG_BLC        0x20 /* Analog processing channel BLC ON/OFF control */
+#define COM13_ABLC_GAIN_EN      0x04 /* ABLC gain trigger enable */
+
+#define COM14                   0x3F /* Common Control 14 */
+#define COM15                   0x40 /* Common Control 15 */
+#define COM16                   0x41 /* Common Control 16 */
+#define TGT_B                   0x42 /* BLC Blue Channel Target Value   */
+#define TGT_R                   0x43 /* BLC Red Channel Target Value    */
+#define TGT_GB                  0x44 /* BLC Gb Channel Target Value     */
+#define TGT_GR                  0x45 /* BLC Gr Channel Target Value     */
+
+#define LC_CTR                  0x46 /* Lens Correction Control */
+#define LC_CTR_RGB_COMP_1       0x00 /* R, G, and B channel compensation coefficient is set by LC_COEF (0x49) */
+#define LC_CTR_RGB_COMP_3       0x04 /* R, G, and B channel compensation coefficient is set by registers
+                                        LC_COEFB (0x4B), LC_COEF (0x49), and LC_COEFR (0x4C), respectively */
+#define LC_CTR_EN               0x01 /* Lens correction enable */
+#define LC_XC                   0x47 /* X Coordinate of Lens Correction Center Relative to Array Center */
+#define LC_YC                   0x48 /* Y Coordinate of Lens Correction Center Relative to Array Center */
+#define LC_COEF                 0x49 /* Lens Correction Coefficient */
+#define LC_RADI                 0x4A /* Lens Correction Radius */
+#define LC_COEFB                0x4B /* Lens Correction B Channel Compensation Coefficient */
+#define LC_COEFR                0x4C /* Lens Correction R Channel Compensation Coefficient */
+
+#define FIXGAIN                 0x4D /* Analog Fix Gain Amplifier */
+#define AREF0                   0x4E /* Sensor Reference Control */
+#define AREF1                   0x4F /* Sensor Reference Current Control */
+#define AREF2                   0x50 /* Analog Reference Control */
+#define AREF3                   0x51 /* ADC Reference Control */
+#define AREF4                   0x52 /* ADC Reference Control */
+#define AREF5                   0x53 /* ADC Reference Control */
+#define AREF6                   0x54 /* Analog Reference Control */
+#define AREF7                   0x55 /* Analog Reference Control */
+#define UFIX                    0x60 /* U Channel Fixed Value Output */
+#define VFIX                    0x61 /* V Channel Fixed Value Output */
+#define AWBB_BLK                0x62 /* AWB Option for Advanced AWB  */
+
+#define AWB_CTRL0               0x63 /* AWB Control Byte 0   */
+#define AWB_CTRL0_GAIN_EN       0x80 /* AWB gain enable      */
+#define AWB_CTRL0_CALC_EN       0x40 /* AWB calculate enable */
+#define AWB_CTRL0_WBC_MASK      0x0F /* WBC threshold 2      */
+
+#define DSP_CTRL1               0x64 /* DSP Control Byte 1                  */
+#define DSP_CTRL1_FIFO_EN       0x80 /* FIFO enable/disable selection       */
+#define DSP_CTRL1_UV_EN         0x40 /* UV adjust function ON/OFF selection */
+#define DSP_CTRL1_SDE_EN        0x20 /* SDE enable                          */
+#define DSP_CTRL1_MTRX_EN       0x10 /* Color matrix ON/OFF selection       */
+#define DSP_CTRL1_INTRP_EN      0x08 /* Interpolation ON/OFF selection      */
+#define DSP_CTRL1_GAMMA_EN      0x04 /* Gamma function ON/OFF selection     */
+#define DSP_CTRL1_BLACK_EN      0x02 /* Black defect auto correction ON/OFF */
+#define DSP_CTRL1_WHITE_EN      0x01 /* White defect auto correction ON/OFF */
+
+#define DSP_CTRL2               0x65 /* DSP Control Byte 2          */
+#define DSP_CTRL2_VDCW_EN       0x08 /* Vertical DCW enable         */
+#define DSP_CTRL2_HDCW_EN       0x04 /* Horizontal DCW enable       */
+#define DSP_CTRL2_VZOOM_EN      0x02 /* Vertical zoom out enable    */
+#define DSP_CTRL2_HZOOM_EN      0x01 /* Horizontal zoom out enable  */
+
+#define DSP_CTRL3               0x66 /* DSP Control Byte 3                      */
+#define DSP_CTRL3_UV_EN         0x80 /* UV output sequence option               */
+#define DSP_CTRL3_CBAR_EN       0x20 /* DSP color bar ON/OFF selection          */
+#define DSP_CTRL3_FIFO_EN       0x08 /* FIFO power down ON/OFF selection        */
+#define DSP_CTRL3_SCAL1_PWDN    0x04 /* Scaling module power down control 1     */
+#define DSP_CTRL3_SCAL2_PWDN    0x02 /* Scaling module power down control 2     */
+#define DSP_CTRL3_INTRP_PWDN    0x01 /* Interpolation module power down control */
+#define DSP_CTRL3_SET_CBAR(r, x)    ((r&0xDF)|((x&1)<<5))
+
+
+#define DSP_CTRL4               0x67 /* DSP Control Byte 4          */
+#define DSP_CTRL4_YUV_RGB       0x00 /* Output selection YUV or RGB */
+#define DSP_CTRL4_RAW8          0x02 /* Output selection RAW8       */
+#define DSP_CTRL4_RAW10         0x03 /* Output selection RAW10      */
+
+
+#define AWB_BIAS                0x68 /* AWB BLC Level Clip */
+#define AWB_CTRL1               0x69 /* AWB Control 1 */
+#define AWB_CTRL2               0x6A /* AWB Control 2 */
+
+#define AWB_CTRL3               0x6B /* AWB Control 3 */
+#define AWB_CTRL3_ADVANCED      0x80 /* AWB mode select - Advanced AWB */
+#define AWB_CTRL3_SIMPLE        0x00 /* AWB mode select - Simple AWB */
+
+#define AWB_CTRL4               0x6C /* AWB Control 4  */
+#define AWB_CTRL5               0x6D /* AWB Control 5  */
+#define AWB_CTRL6               0x6E /* AWB Control 6  */
+#define AWB_CTRL7               0x6F /* AWB Control 7  */
+#define AWB_CTRL8               0x70 /* AWB Control 8  */
+#define AWB_CTRL9               0x71 /* AWB Control 9  */
+#define AWB_CTRL10              0x72 /* AWB Control 10 */
+#define AWB_CTRL11              0x73 /* AWB Control 11 */
+#define AWB_CTRL12              0x74 /* AWB Control 12 */
+#define AWB_CTRL13              0x75 /* AWB Control 13 */
+#define AWB_CTRL14              0x76 /* AWB Control 14 */
+#define AWB_CTRL15              0x77 /* AWB Control 15 */
+#define AWB_CTRL16              0x78 /* AWB Control 16 */
+#define AWB_CTRL17              0x79 /* AWB Control 17 */
+#define AWB_CTRL18              0x7A /* AWB Control 18 */
+#define AWB_CTRL19              0x7B /* AWB Control 19 */
+#define AWB_CTRL20              0x7C /* AWB Control 20 */
+#define AWB_CTRL21              0x7D /* AWB Control 21 */
+#define GAM1                    0x7E /* Gamma Curve 1st Segment Input End Point 0x04 Output Value */
+#define GAM2                    0x7F /* Gamma Curve 2nd Segment Input End Point 0x08 Output Value */
+#define GAM3                    0x80 /* Gamma Curve 3rd Segment Input End Point 0x10 Output Value */
+#define GAM4                    0x81 /* Gamma Curve 4th Segment Input End Point 0x20 Output Value */
+#define GAM5                    0x82 /* Gamma Curve 5th Segment Input End Point 0x28 Output Value */
+#define GAM6                    0x83 /* Gamma Curve 6th Segment Input End Point 0x30 Output Value */
+#define GAM7                    0x84 /* Gamma Curve 7th Segment Input End Point 0x38 Output Value */
+#define GAM8                    0x85 /* Gamma Curve 8th Segment Input End Point 0x40 Output Value */
+#define GAM9                    0x86 /* Gamma Curve 9th Segment Input End Point 0x48 Output Value */
+#define GAM10                   0x87 /* Gamma Curve 10th Segment Input End Point 0x50 Output Value */
+#define GAM11                   0x88 /* Gamma Curve 11th Segment Input End Point 0x60 Output Value */
+#define GAM12                   0x89 /* Gamma Curve 12th Segment Input End Point 0x70 Output Value */
+#define GAM13                   0x8A /* Gamma Curve 13th Segment Input End Point 0x90 Output Value */
+#define GAM14                   0x8B /* Gamma Curve 14th Segment Input End Point 0xB0 Output Value */
+#define GAM15                   0x8C /* Gamma Curve 15th Segment Input End Point 0xD0 Output Value */
+#define SLOP                    0x8D /* Gamma Curve Highest Segment Slope */
+#define DNSTH                   0x8E /* De-noise Threshold */
+#define EDGE0                   0x8F /* Edge Enhancement Strength Control */
+#define EDGE1                   0x90 /* Edge Enhancement Threshold Control */
+#define DNSOFF                  0x91 /* Auto De-noise Threshold Control */
+#define EDGE2                   0x92 /* Edge Enhancement Strength Upper Limit */
+#define EDGE3                   0x93 /* Edge Enhancement Strength Upper Limit */
+#define MTX1                    0x94 /* Matrix Coefficient 1 */
+#define MTX2                    0x95 /* Matrix Coefficient 2 */
+#define MTX3                    0x96 /* Matrix Coefficient 3 */
+#define MTX4                    0x97 /* Matrix Coefficient 4 */
+#define MTX5                    0x98 /* Matrix Coefficient 5 */
+#define MTX6                    0x99 /* Matrix Coefficient 6 */
+
+#define MTX_CTRL                0x9A /* Matrix Control */
+#define MTX_CTRL_DBL_EN         0x80 /* Matrix double ON/OFF selection */
+
+#define BRIGHTNESS              0x9B /* Brightness Control */
+#define CONTRAST                0x9C /* Contrast Gain */
+#define UVADJ0                  0x9E /* Auto UV Adjust Control 0 */
+#define UVADJ1                  0x9F /* Auto UV Adjust Control 1 */
+#define SCAL0                   0xA0 /* DCW Ratio Control */
+#define SCAL1                   0xA1 /* Horizontal Zoom Out Control */
+#define SCAL2                   0xA2 /* Vertical Zoom Out Control */
+#define FIFODLYM                0xA3 /* FIFO Manual Mode Delay Control */
+#define FIFODLYA                0xA4 /* FIFO Auto Mode Delay Control */
+
+#define SDE                     0xA6 /* Special Digital Effect Control  */
+#define SDE_NEGATIVE_EN         0x40 /* Negative image enable           */
+#define SDE_GRAYSCALE_EN        0x20 /* Gray scale image enable         */
+#define SDE_V_FIXED_EN          0x10 /* V fixed value enable            */
+#define SDE_U_FIXED_EN          0x08 /* U fixed value enable            */
+#define SDE_CONT_BRIGHT_EN      0x04 /* Contrast/Brightness enable      */
+#define SDE_SATURATION_EN       0x02 /* Saturation enable               */
+#define SDE_HUE_EN              0x01 /* Hue enable                      */
+
+#define USAT                    0xA7 /* U Component Saturation Gain     */
+#define VSAT                    0xA8 /* V Component Saturation Gain     */
+#define HUECOS                  0xA9 /* Cosine value × 0x80             */
+#define HUESIN                  0xAA /* Sine value × 0x80               */
+#define SIGN_BIT                0xAB /* Sign Bit for Hue and Brightness */
+
+#define DSPAUTO                 0xAC /* DSP Auto Function ON/OFF Control */
+#define DSPAUTO_AWB_EN          0x80 /* AWB auto threshold control */
+#define DSPAUTO_DENOISE_EN      0x40 /* De-noise auto threshold control */
+#define DSPAUTO_EDGE_EN         0x20 /* Sharpness (edge enhancement) auto strength control */
+#define DSPAUTO_UV_EN           0x10 /* UV adjust auto slope control */
+#define DSPAUTO_SCAL0_EN        0x08 /* Auto scaling factor control (register SCAL0 (0xA0)) */
+#define DSPAUTO_SCAL1_EN        0x04 /* Auto scaling factor control (registers SCAL1 (0xA1 and SCAL2 (0xA2))*/
+#define SET_REG(reg, x)         (##reg_DEFAULT|x)
+#endif //__REG_REGS_H__

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 25 - 0
code/lib/tfmicro/CMakeLists.txt


+ 900 - 0
code/lib/tfmicro/fixedpoint/fixedpoint.h

@@ -0,0 +1,900 @@
+// Copyright 2015 The Gemmlowp Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// fixedpoint.h: fixed-point arithmetic, with basic operations and
+// a few math functions such as tanh.
+
+#ifndef GEMMLOWP_INTERNAL_FIXEDPOINT_H_
+#define GEMMLOWP_INTERNAL_FIXEDPOINT_H_
+
+#include <algorithm>
+#include <cassert>
+#include <cmath>
+#include <cstdint>
+#include <limits>
+
+#include "../internal/detect_platform.h"
+
+namespace gemmlowp {
+
+// Part 1: Low-level integer-arithmetic primitives.
+// The implementations here are generic implementations valid for
+// scalar types (e.g. std::int32_t). Architecture-specific SIMD types
+// (e.g. NEON int32x4_t) may be supported by providing
+// specializations for them in separate files.
+//
+// The purpose of these primitives is two-fold:
+//  - They will be used to implement higher-level fixed-point
+//    abstractions, namely the FixedPoint class and its arithmetic
+//    operators.
+//  - They will be directly used to implement some more involved
+//    fixed-point computations, e.g. the fixed-point implementation
+//    of math functions such as tanh.
+
+// Some compile-time traits around raw types to handle SIMD aspects:
+// number of lanes, underlying scalar type.
+template <typename tIntegerType>
+struct FixedPointRawTypeTraits {};
+
+template <>
+struct FixedPointRawTypeTraits<std::int32_t> {
+  typedef std::int32_t ScalarRawType;
+  static constexpr int kLanes = 1;
+};
+
+template <>
+struct FixedPointRawTypeTraits<std::int16_t> {
+  typedef std::int16_t ScalarRawType;
+  static constexpr int kLanes = 1;
+};
+
+// Returns a SIMD value duplicating a scalar value across all lanes.
+template <typename tRawType>
+tRawType Dup(typename FixedPointRawTypeTraits<tRawType>::ScalarRawType x) {
+  return x;
+}
+
+// Plain bit-wise AND
+template <typename tIntegerType>
+tIntegerType BitAnd(tIntegerType a, tIntegerType b) {
+  return a & b;
+}
+
+// Plain bit-wise OR
+template <typename tIntegerType>
+tIntegerType BitOr(tIntegerType a, tIntegerType b) {
+  return a | b;
+}
+
+// Plain bit-wise XOR
+template <typename tIntegerType>
+tIntegerType BitXor(tIntegerType a, tIntegerType b) {
+  return a ^ b;
+}
+
+// Plain bit-wise NOT
+template <typename tIntegerType>
+tIntegerType BitNot(tIntegerType a) {
+  return ~a;
+}
+
+// Integer addition. Not saturating. Overflow is undefined behavior.
+template <typename tIntegerType>
+tIntegerType Add(tIntegerType a, tIntegerType b) {
+  return a + b;
+}
+
+// Integer subtraction. Not saturating. Overflow is undefined behavior.
+template <typename tIntegerType>
+tIntegerType Mul(tIntegerType a, tIntegerType b) {
+  return a * b;
+}
+
+template <typename tIntegerType>
+tIntegerType Sub(tIntegerType a, tIntegerType b) {
+  return a - b;
+}
+
+// Integer unary negative. Not saturating. Overflow is undefined behavior.
+template <typename tIntegerType>
+tIntegerType Neg(tIntegerType a) {
+  return -a;
+}
+
+// Integer arithmetic left-shift, equivalent to multiplying with a power of two.
+// Negative values are OK. In case of overflow, no Undefined
+// Behavior, but the results are implementation-defined (in practice,
+// they currently are saturated, but we make no commitment to that). The idea
+// is that the caller will want to implement the overflowing cases with
+// saturation with compare-and-mask, so we don't care about the results
+// in the overflow case, we just want to avoid undefined behavior.
+//
+// tIntegerType may be int32 or any narrower signed type.
+template <typename tIntegerType>
+tIntegerType ShiftLeft(tIntegerType a, int offset) {
+  const std::int64_t wide_a = static_cast<std::int64_t>(a);
+  const std::int64_t wide_shifted = wide_a * (1 << offset);
+  const auto min = std::numeric_limits<tIntegerType>::min();
+  const auto max = std::numeric_limits<tIntegerType>::max();
+  return wide_shifted < min
+             ? min
+             : wide_shifted > max ? max
+                                  : static_cast<tIntegerType>(wide_shifted);
+}
+
+// Integer arithmetic right-shift. Not rounding.
+// Relying on implementation-defined, but in-practice-consistent,
+// C++ compiler behavior.
+template <typename tIntegerType>
+tIntegerType ShiftRight(tIntegerType a, int offset) {
+  return a >> offset;
+}
+
+// Each bit of the result is set to the corresponding bit of either then_val or
+// else_val depending on whether the corresponding bit of if_mask is set.
+// Equivalent to the VBSL instruction in ARM NEON.
+template <typename tIntegerType>
+tIntegerType SelectUsingMask(tIntegerType if_mask, tIntegerType then_val,
+                             tIntegerType else_val) {
+  return BitXor(BitAnd(if_mask, then_val), BitAnd(BitNot(if_mask), else_val));
+}
+
+// For each input scalar, the corresponding bits of the result are set if the
+// input scalar is non-zero.
+template <typename tIntegerType>
+tIntegerType MaskIfNonZero(tIntegerType a) {
+  static constexpr tIntegerType zero = 0;
+  return a ? BitNot(zero) : zero;
+}
+
+// For each input scalar, the corresponding bits of the result are set if the
+// input scalar is zero.
+template <typename tIntegerType>
+tIntegerType MaskIfZero(tIntegerType a) {
+  return MaskIfNonZero<tIntegerType>(!a);
+}
+
+// For each pair of input scalars, the corresponding bits of the result are
+// set if the input scalars are equal.
+template <typename tIntegerType>
+tIntegerType MaskIfEqual(tIntegerType a, tIntegerType b) {
+  return MaskIfNonZero<tIntegerType>(a == b);
+}
+
+// For each pair of input scalars, the corresponding bits of the result are
+// set if the input scalars are not equal.
+template <typename tIntegerType>
+tIntegerType MaskIfNotEqual(tIntegerType a, tIntegerType b) {
+  return MaskIfNonZero<tIntegerType>(a != b);
+}
+
+// For each pair of input scalars, the corresponding bits of the result are
+// set if the input scalars a, b satisfy a > b.
+template <typename tIntegerType>
+tIntegerType MaskIfGreaterThan(tIntegerType a, tIntegerType b) {
+  return MaskIfNonZero<tIntegerType>(a > b);
+}
+
+// For each pair of input scalars, the corresponding bits of the result are
+// set if the input scalars a, b satisfy a >= b.
+template <typename tIntegerType>
+tIntegerType MaskIfGreaterThanOrEqual(tIntegerType a, tIntegerType b) {
+  return MaskIfNonZero<tIntegerType>(a >= b);
+}
+
+// For each pair of input scalars, the corresponding bits of the result are
+// set if the input scalars a, b satisfy a < b.
+template <typename tIntegerType>
+tIntegerType MaskIfLessThan(tIntegerType a, tIntegerType b) {
+  return MaskIfNonZero<tIntegerType>(a < b);
+}
+
+// For each pair of input scalars, the corresponding bits of the result are
+// set if the input scalars a, b satisfy a <= b.
+template <typename tIntegerType>
+tIntegerType MaskIfLessThanOrEqual(tIntegerType a, tIntegerType b) {
+  return MaskIfNonZero<tIntegerType>(a <= b);
+}
+
+// Returns true if all of the input scalars are nonzero.
+// This function may currently assume that each of the input scalars has either
+// all or none of its bits set. Otherwise, its behavior is currently undefined.
+template <typename tIntegerType>
+bool All(tIntegerType a) {
+  return a;
+}
+
+// Returns true if any of the input scalars are nonzero.
+// This function may currently assume that each of the input scalars has either
+// all or none of its bits set. Otherwise, its behavior is currently undefined.
+template <typename tIntegerType>
+bool Any(tIntegerType a) {
+  return a;
+}
+
+// Returns (a+b)/2, rounded to the nearest integer.
+// Equivalent to VRHADD in the ARM NEON instruction set.
+template <typename IntegerType>
+IntegerType RoundingHalfSum(IntegerType a, IntegerType b) {
+  static_assert(std::is_same<IntegerType, void>::value, "unimplemented");
+  (void)b;
+  return a;
+}
+
+template <>
+inline std::int32_t RoundingHalfSum(std::int32_t a, std::int32_t b) {
+  std::int64_t a64 = a;
+  std::int64_t b64 = b;
+  std::int64_t sum = a64 + b64;
+  std::int64_t sign = sum >= 0 ? 1 : -1;
+  return static_cast<std::int32_t>((sum + sign) / 2);
+}
+
+template <>
+inline std::int16_t RoundingHalfSum(std::int16_t a, std::int16_t b) {
+  std::int32_t a32 = a;
+  std::int32_t b32 = b;
+  std::int32_t sum = a32 + b32;
+  std::int32_t sign = sum >= 0 ? 1 : -1;
+  return static_cast<std::int16_t>((sum + sign) / 2);
+}
+
+template <typename IntegerType>
+IntegerType SaturatingAdd(IntegerType a, IntegerType b) {
+  static_assert(std::is_same<IntegerType, void>::value, "unimplemented");
+  (void)b;
+  return a;
+}
+
+// So far this is only needed for int16.
+template <>
+inline std::int16_t SaturatingAdd(std::int16_t a, std::int16_t b) {
+  std::int32_t a32 = a;
+  std::int32_t b32 = b;
+  std::int32_t sum = a32 + b32;
+  return static_cast<std::int16_t>(
+      std::min(static_cast<std::int32_t>(32767),
+               std::max(static_cast<std::int32_t>(-32768), sum)));
+}
+
+// Returns a+b, saturating if the integers are 16bit or narrower,
+// otherwise just a plain addition.
+template <typename IntegerType, bool Is16Bit>
+struct AddSaturatingIf16BitImpl {
+  static IntegerType Run(IntegerType a, IntegerType b) { return Add(a, b); }
+};
+template <typename IntegerType>
+struct AddSaturatingIf16BitImpl<IntegerType, true> {
+  static IntegerType Run(IntegerType a, IntegerType b) {
+    return SaturatingAdd(a, b);
+  }
+};
+template <typename IntegerType>
+IntegerType AddSaturatingIf16Bit(IntegerType a, IntegerType b) {
+  using ScalarType =
+      typename FixedPointRawTypeTraits<IntegerType>::ScalarRawType;
+  return AddSaturatingIf16BitImpl<IntegerType, sizeof(ScalarType) == 2>::Run(a,
+                                                                             b);
+}
+
+// Returns the integer that represents the product of two fixed-point
+// numbers, interpreting all integers as fixed-point values in the
+// interval [-1, 1), rounding to the nearest value, and saturating
+// -1 * -1 to the maximum value (since 1 is not in the half-open
+// interval [-1, 1)).
+//
+// [The explanation below specializes to std::int32_t for example purpose.]
+//
+// The mapping between IntegerType and the interval [-1, 1) is unique and
+// implied by IntegerType, which is assumed to be signed. For example,
+// for IntegerType==std::int32_t, the mapping is
+//   real_value = integer_value / 2^31.
+// So in this case, and leaving aside rounding and saturating, this
+// function computes ((a / 2^31) * (b / 2^31)) * 2^31, which simplifies to
+//   (a * b) / 2^31.
+//
+// The 'doubling' part in the name of this function comes from the fact that
+// this operation is very close to a "multiply-high" operation, keeping only
+// the top half bits, except that that would be effectively computing
+//   (a * b) / 2^32,
+// so here we are computing 2x that, since
+//   1/2^31 = 2 * 1/2^32.
+// The idea is to use all of the available 32 bits in the destination int32
+// value.
+//
+// [End of the explanation specializing to int32.]
+//
+// This is equivalent to the VQRDMULH instruction in ARM NEON.
+template <typename IntegerType>
+IntegerType SaturatingRoundingDoublingHighMul(IntegerType a, IntegerType b) {
+  static_assert(std::is_same<IntegerType, void>::value, "unimplemented");
+  (void)b;
+  return a;
+}
+
+// This function implements the same computation as the ARMv7 NEON VQRDMULH
+// instruction.
+template <>
+inline std::int32_t SaturatingRoundingDoublingHighMul(std::int32_t a,
+                                                      std::int32_t b) {
+  bool overflow = a == b && a == std::numeric_limits<std::int32_t>::min();
+  std::int64_t a_64(a);
+  std::int64_t b_64(b);
+  std::int64_t ab_64 = a_64 * b_64;
+  std::int32_t nudge = ab_64 >= 0 ? (1 << 30) : (1 - (1 << 30));
+  std::int32_t ab_x2_high32 =
+      static_cast<std::int32_t>((ab_64 + nudge) / (1ll << 31));
+  return overflow ? std::numeric_limits<std::int32_t>::max() : ab_x2_high32;
+}
+
+template <>
+inline std::int16_t SaturatingRoundingDoublingHighMul(std::int16_t a,
+                                                      std::int16_t b) {
+  bool overflow = a == b && a == std::numeric_limits<std::int16_t>::min();
+  std::int32_t a_32(a);
+  std::int32_t b_32(b);
+  std::int32_t ab_32 = a_32 * b_32;
+  std::int16_t nudge = ab_32 >= 0 ? (1 << 14) : (1 - (1 << 14));
+  std::int16_t ab_x2_high16 =
+      static_cast<std::int16_t>((ab_32 + nudge) / (1 << 15));
+  return overflow ? std::numeric_limits<std::int16_t>::max() : ab_x2_high16;
+}
+
+// Correctly-rounded-to-nearest division by a power-of-two.
+// Also known as a rounding arithmetic right shift.
+template <typename IntegerType>
+inline IntegerType RoundingDivideByPOT(IntegerType x, int exponent) {
+  assert(exponent >= 0);
+  assert(exponent <= 31);
+  const IntegerType mask = Dup<IntegerType>((1ll << exponent) - 1);
+  const IntegerType zero = Dup<IntegerType>(0);
+  const IntegerType one = Dup<IntegerType>(1);
+  const IntegerType remainder = BitAnd(x, mask);
+  const IntegerType threshold =
+      Add(ShiftRight(mask, 1), BitAnd(MaskIfLessThan(x, zero), one));
+  return Add(ShiftRight(x, exponent),
+             BitAnd(MaskIfGreaterThan(remainder, threshold), one));
+}
+
+// Returns the product of a run-time integer value by a compile-time power
+// of two, with either a positive exponent (equivalent to an arithmetic
+// left shift, saturating) or a negative exponent (equivalent to an arithmetic
+// right shift, rounding to nearest).
+template <int Exponent, typename IntegerType,
+          int ExponentSign = (Exponent > 0 ? 1 : Exponent < 0 ? -1 : 0)>
+struct ImplSaturatingRoundingMultiplyByPOT {};
+
+template <int Exponent, typename IntegerType>
+struct ImplSaturatingRoundingMultiplyByPOT<Exponent, IntegerType, 0> {
+  static IntegerType eval(IntegerType x) { return x; }
+};
+
+template <int Exponent, typename IntegerType>
+struct ImplSaturatingRoundingMultiplyByPOT<Exponent, IntegerType, 1> {
+  static IntegerType eval(IntegerType x) {
+    using ScalarIntegerType =
+        typename FixedPointRawTypeTraits<IntegerType>::ScalarRawType;
+    const IntegerType min =
+        Dup<IntegerType>(std::numeric_limits<ScalarIntegerType>::min());
+    const IntegerType max =
+        Dup<IntegerType>(std::numeric_limits<ScalarIntegerType>::max());
+    const int ScalarIntegerTypeBits = 8 * sizeof(ScalarIntegerType);
+
+    const std::int32_t threshold =
+        ((1 << (ScalarIntegerTypeBits - 1 - Exponent)) - 1);
+    const IntegerType positive_mask =
+        MaskIfGreaterThan(x, Dup<IntegerType>(threshold));
+    const IntegerType negative_mask =
+        MaskIfLessThan(x, Dup<IntegerType>(-threshold));
+
+    IntegerType result = ShiftLeft(x, Exponent);
+    result = SelectUsingMask(positive_mask, max, result);
+    result = SelectUsingMask(negative_mask, min, result);
+    return result;
+  }
+};
+
+template <int Exponent, typename IntegerType>
+struct ImplSaturatingRoundingMultiplyByPOT<Exponent, IntegerType, -1> {
+  static IntegerType eval(IntegerType x) {
+    return RoundingDivideByPOT<IntegerType>(x, -Exponent);
+  }
+};
+
+template <int Exponent, typename IntegerType>
+IntegerType SaturatingRoundingMultiplyByPOT(IntegerType x) {
+  return ImplSaturatingRoundingMultiplyByPOT<Exponent, IntegerType>::eval(x);
+}
+
+// Part 2: the FixedPoint class.
+
+// A FixedPoint object represents a fixed-point value stored in the underlying
+// integer type tRawType, if tRawType is a plain scalar integer type.
+// Alternatively, tRawType may be a SIMD type (e.g. NEON int32x4_t) in which
+// case a FixedPoint object represents a corresponding SIMD vector of fixed
+// point values.
+//
+// tIntegerBits describes the range of the fixed-point format: if
+// tIntegerBits == m then the range of representable values is the half-open
+// interval [-2^m; 2^m) where the open boundary on the right side means that
+// 2^m is not representable (how close the maximum representable value is to
+// it, depends on bit-depth of tRawType).
+//
+// In "Q format notation",
+//   https://en.wikipedia.org/wiki/Q_(number_format)
+// we are describing the format
+//   Qm.n
+// where
+//   m = tIntegerBits
+// and
+//   n = NumberOfBits(tRawType) - (m + 1)
+// Note that the (m + 1) in the above line is because we adopt the convention
+// that we count the integer bits exclusively of the sign bit; so (m + 1) is
+// the total number of integer bits inclusive of the sign bit.
+//
+// Accordingly, the number of integral representable values in our range
+//   [-2^m ; 2^m)
+// is equal to 2^(m+1).
+template <typename tRawType, int tIntegerBits>
+class FixedPoint {
+ public:
+  typedef tRawType RawType;
+
+  typedef FixedPointRawTypeTraits<RawType> RawTypeTraits;
+  typedef typename RawTypeTraits::ScalarRawType ScalarRawType;
+
+  static constexpr int kTotalBits = 8 * sizeof(ScalarRawType);
+  static constexpr int kIntegerBits = tIntegerBits;
+  static constexpr int kFractionalBits = kTotalBits - 1 - kIntegerBits;
+  static_assert(kIntegerBits >= 0 && kIntegerBits < kTotalBits,
+                "bad IntegerBits");
+
+  typedef FixedPoint<ScalarRawType, kIntegerBits> ScalarFixedPointType;
+
+  static const ScalarRawType ScalarRawMin() {
+    return std::numeric_limits<ScalarRawType>::min();
+  }
+
+  static const ScalarRawType ScalarRawMax() {
+    return std::numeric_limits<ScalarRawType>::max();
+  }
+
+  static const ScalarRawType RawMin() {
+    return VectorFromScalar(ScalarRawMin());
+  }
+
+  static const ScalarRawType RawMax() {
+    return VectorFromScalar(ScalarRawMax());
+  }
+
+  static FixedPoint FromRaw(RawType x) {
+    FixedPoint retval;
+    retval.raw() = x;
+    return retval;
+  }
+
+  static FixedPoint FromScalarRaw(ScalarRawType x) {
+    FixedPoint retval;
+    retval.raw() = Dup<RawType>(x);
+    return retval;
+  }
+
+  static FixedPoint FromScalarFixedPoint(ScalarFixedPointType x) {
+    return FromScalarRaw(x.raw());
+  }
+
+  template <int Exponent>
+  static FixedPoint ConstantPOT() {
+    static constexpr int kOffset = kFractionalBits + Exponent;
+    static_assert(
+        kOffset < 31,
+        "Constant not exactly representable in this fixed-point format");
+    return FromScalarRaw(ScalarRawType(1) << kOffset);
+  }
+
+  static FixedPoint Zero() { return FromScalarRaw(0); }
+
+  static FixedPoint One() {
+    return FromScalarRaw(
+        kIntegerBits == 0
+            ? ScalarRawMax()
+            : (ScalarRawType(1) << (kIntegerBits == 0 ? 0 : kFractionalBits)));
+  }
+
+  static FixedPoint FromDouble(double x) {
+    const double min_bound = static_cast<double>(ScalarRawMin());
+    const double max_bound = static_cast<double>(ScalarRawMax());
+    return FromScalarRaw(static_cast<ScalarRawType>(std::min(
+        std::max(round(x * static_cast<double>(1ll << kFractionalBits)),
+                 min_bound),
+        max_bound)));
+  }
+
+  RawType raw() const { return i_; }
+  RawType& raw() { return i_; }
+
+ private:
+  RawType i_;
+};
+
+// Part 3: implementation of arithmetic operators for the
+// FixedPoint class, and a few related functions.
+
+// A FixedPoint multiplication is just a
+// SaturatingRoundingDoublingHighMul operation on the underlying
+// raw integer values. The IntegerBits simply add up, as is obvious
+// from the fact that the range is [-2^IntegerBits, 2^IntegerBits).
+template <typename tRawType, int tIntegerBits_a, int tIntegerBits_b>
+FixedPoint<tRawType, tIntegerBits_a + tIntegerBits_b> operator*(
+    FixedPoint<tRawType, tIntegerBits_a> a,
+    FixedPoint<tRawType, tIntegerBits_b> b) {
+  FixedPoint<tRawType, tIntegerBits_a + tIntegerBits_b> c;
+  c.raw() = SaturatingRoundingDoublingHighMul(a.raw(), b.raw());
+  return c;
+}
+
+// Tweaking IntegerBits gives exact multiplication by a power of two.
+template <int tExponent, typename tRawType, int tIntegerBits>
+FixedPoint<tRawType, tExponent + tIntegerBits> ExactMulByPot(
+    FixedPoint<tRawType, tIntegerBits> a) {
+  FixedPoint<tRawType, tExponent + tIntegerBits> c;
+  c.raw() = a.raw();
+  return c;
+}
+
+// If we want to leave IntegerBits fixed, then multiplication
+// by a power of two has to be saturating/rounding, not exact anymore.
+template <int tExponent, typename tRawType, int tIntegerBits>
+FixedPoint<tRawType, tIntegerBits> SaturatingRoundingMultiplyByPOT(
+    FixedPoint<tRawType, tIntegerBits> a) {
+  return FixedPoint<tRawType, tIntegerBits>::FromRaw(
+      SaturatingRoundingMultiplyByPOT<tExponent>(a.raw()));
+}
+
+// Generic arithmetic operators.
+
+#define MAKE_FIXEDPOINT_UNARY_FUNC(FuncName, ImplFuncName)                     \
+  template <typename tRawType, int tIntegerBits>                               \
+  FixedPoint<tRawType, tIntegerBits> FuncName(                                 \
+      FixedPoint<tRawType, tIntegerBits> a) {                                  \
+    return FixedPoint<tRawType, tIntegerBits>::FromRaw(ImplFuncName(a.raw())); \
+  }
+
+#define MAKE_FIXEDPOINT_BINARY_FUNC(FuncName, ImplFuncName) \
+  template <typename tRawType, int tIntegerBits>            \
+  FixedPoint<tRawType, tIntegerBits> FuncName(              \
+      FixedPoint<tRawType, tIntegerBits> a,                 \
+      FixedPoint<tRawType, tIntegerBits> b) {               \
+    return FixedPoint<tRawType, tIntegerBits>::FromRaw(     \
+        ImplFuncName(a.raw(), b.raw()));                    \
+  }
+
+MAKE_FIXEDPOINT_UNARY_FUNC(operator-, Neg)
+MAKE_FIXEDPOINT_UNARY_FUNC(operator~, BitNot)
+MAKE_FIXEDPOINT_BINARY_FUNC(operator+, Add)
+MAKE_FIXEDPOINT_BINARY_FUNC(operator-, Sub)
+MAKE_FIXEDPOINT_BINARY_FUNC(operator&, BitAnd)
+MAKE_FIXEDPOINT_BINARY_FUNC(operator^, BitXor)
+MAKE_FIXEDPOINT_BINARY_FUNC(operator|, BitOr)
+MAKE_FIXEDPOINT_BINARY_FUNC(RoundingHalfSum, RoundingHalfSum)
+
+#undef MAKE_FIXEDPOINT_UNARY_FUNC
+#undef MAKE_FIXEDPOINT_BINARY_FUNC
+
+#define MAKE_FIXEDPOINT_UNARY_FUNC_RETURNING_RAW(FuncName)  \
+  template <typename tRawType, int tIntegerBits>            \
+  tRawType FuncName(FixedPoint<tRawType, tIntegerBits> a) { \
+    return FuncName(a.raw());                               \
+  }
+
+#define MAKE_FIXEDPOINT_BINARY_FUNC_RETURNING_RAW(FuncName) \
+  template <typename tRawType, int tIntegerBits>            \
+  tRawType FuncName(FixedPoint<tRawType, tIntegerBits> a,   \
+                    FixedPoint<tRawType, tIntegerBits> b) { \
+    return FuncName(a.raw(), b.raw());                      \
+  }
+
+MAKE_FIXEDPOINT_UNARY_FUNC_RETURNING_RAW(MaskIfZero)
+MAKE_FIXEDPOINT_UNARY_FUNC_RETURNING_RAW(MaskIfNonZero)
+MAKE_FIXEDPOINT_BINARY_FUNC_RETURNING_RAW(MaskIfEqual)
+MAKE_FIXEDPOINT_BINARY_FUNC_RETURNING_RAW(MaskIfNotEqual)
+MAKE_FIXEDPOINT_BINARY_FUNC_RETURNING_RAW(MaskIfGreaterThan)
+MAKE_FIXEDPOINT_BINARY_FUNC_RETURNING_RAW(MaskIfGreaterThanOrEqual)
+MAKE_FIXEDPOINT_BINARY_FUNC_RETURNING_RAW(MaskIfLessThan)
+MAKE_FIXEDPOINT_BINARY_FUNC_RETURNING_RAW(MaskIfLessThanOrEqual)
+
+#undef MAKE_FIXEDPOINT_UNARY_FUNC_RETURNING_RAW
+#undef MAKE_FIXEDPOINT_BINARY_FUNC_RETURNING_RAW
+
+template <typename tRawType, int tIntegerBits>
+FixedPoint<tRawType, tIntegerBits> SelectUsingMask(
+    tRawType if_mask, FixedPoint<tRawType, tIntegerBits> then_val,
+    FixedPoint<tRawType, tIntegerBits> else_val) {
+  return FixedPoint<tRawType, tIntegerBits>::FromRaw(
+      SelectUsingMask(if_mask, then_val.raw(), else_val.raw()));
+}
+
+template <typename tRawType, int tIntegerBits>
+bool operator==(FixedPoint<tRawType, tIntegerBits> a,
+                FixedPoint<tRawType, tIntegerBits> b) {
+  return All(MaskIfEqual(a.raw(), b.raw()));
+}
+
+template <typename tRawType, int tIntegerBits>
+bool operator!=(FixedPoint<tRawType, tIntegerBits> a,
+                FixedPoint<tRawType, tIntegerBits> b) {
+  return !(a == b);
+}
+
+template <typename tRawType, int tIntegerBits>
+FixedPoint<tRawType, tIntegerBits> SaturatingAdd(
+    FixedPoint<tRawType, tIntegerBits> a,
+    FixedPoint<tRawType, tIntegerBits> b) {
+  return FixedPoint<tRawType, tIntegerBits>::FromRaw(
+      SaturatingAdd(a.raw(), b.raw()));
+}
+
+template <typename tRawType, int tIntegerBits>
+FixedPoint<tRawType, tIntegerBits> AddSaturatingIf16Bit(
+    FixedPoint<tRawType, tIntegerBits> a,
+    FixedPoint<tRawType, tIntegerBits> b) {
+  return FixedPoint<tRawType, tIntegerBits>::FromRaw(
+      AddSaturatingIf16Bit(a.raw(), b.raw()));
+}
+
+// Conversion to floating-point.
+template <typename tRawType, int tIntegerBits>
+double ToDouble(FixedPoint<tRawType, tIntegerBits> x) {
+  static_assert(FixedPointRawTypeTraits<tRawType>::kLanes == 1,
+                "not applicable to SIMD types");
+  typedef FixedPoint<tRawType, tIntegerBits> F;
+  return x.raw() / static_cast<double>(1ll << F::kFractionalBits);
+}
+
+// Rescale changes the number of IntegerBits and updates the underlying
+// raw integer value accordingly.
+template <int tIntegerBitsDst, typename tRawType, int tIntegerBitsSrc>
+FixedPoint<tRawType, tIntegerBitsDst> Rescale(
+    FixedPoint<tRawType, tIntegerBitsSrc> x) {
+  static constexpr int kExponent = tIntegerBitsSrc - tIntegerBitsDst;
+  FixedPoint<tRawType, tIntegerBitsDst> result;
+  result.raw() = SaturatingRoundingMultiplyByPOT<kExponent>(x.raw());
+  return result;
+}
+
+// CheckedFixedPointConstant allows to specify fixed-point constants
+// initialized as real numbers, in a way that does not compile floating-point
+// arithmetic in production code, yet still checks agreement with the
+// floating-point expressions when asserts are enabled.
+//
+// The raw integer value provided is always a int32, encoding a 32-bit
+// fixed-point value, regardless of the actual Scalar type. This allows
+// writing generic code that applies just as well to the 32-bit and 16-bit
+// cases. In the 16-bit case, the raw integer value is internally
+// rounding-shifted by 16 bits to the right.
+template <typename FixedPointType>
+inline typename FixedPointType::ScalarRawType RescaleConstantInitializer(
+    std::int32_t int32_value) {
+  typedef typename FixedPointType::ScalarRawType ScalarRawType;
+  static constexpr int ScalarTypeBits = 8 * sizeof(ScalarRawType);
+  return static_cast<ScalarRawType>(
+      RoundingDivideByPOT<std::int32_t>(int32_value, 32 - ScalarTypeBits));
+}
+#ifdef GEMMLOWP_ENABLE_FIXEDPOINT_CONSTANTS_CHECKS
+template <typename FixedPointType>
+FixedPointType CheckedFixedPointConstant(std::int32_t raw_value,
+                                         double double_value) {
+  const FixedPointType result = FixedPointType::FromScalarRaw(raw_value);
+  assert(result == FixedPointType::FromDouble(double_value));
+  return result;
+}
+#define GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(FixedPointType,                   \
+                                             ScalarRawInt32Value, DoubleValue) \
+  (gemmlowp::CheckedFixedPointConstant<FixedPointType>(                        \
+      gemmlowp::RescaleConstantInitializer<FixedPointType>(                    \
+          ScalarRawInt32Value),                                                \
+      DoubleValue))
+
+#else
+#define GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(FixedPointType,                   \
+                                             ScalarRawInt32Value, DoubleValue) \
+  (FixedPointType::FromScalarRaw(                                              \
+      gemmlowp::RescaleConstantInitializer<FixedPointType>(                    \
+          ScalarRawInt32Value)))
+#endif
+
+// Implementation of exponential function.
+
+// Returns exp(x) for x in [-1/4, 0).
+template <typename tRawType>
+FixedPoint<tRawType, 0> exp_on_interval_between_negative_one_quarter_and_0_excl(
+    FixedPoint<tRawType, 0> a) {
+  typedef FixedPoint<tRawType, 0> F;
+  const F constant_term =
+      GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(F, 1895147668, std::exp(-1.0 / 8.0));
+  const F constant_1_over_3 =
+      GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(F, 715827883, 1.0 / 3.0);
+  // We're evaluating a Taylor expansion around -1/8, so we do the change of
+  // variable: x = a + 1/8.
+  // In fixed-point with 0 integer bits, 1/8 is represented by 1 << 28.
+  F x = a + F::template ConstantPOT<-3>();
+  F x2 = x * x;
+  F x3 = x2 * x;
+  F x4 = x2 * x2;
+  F x4_over_4 = SaturatingRoundingMultiplyByPOT<-2>(x4);
+  F x4_over_24_plus_x3_over_6_plus_x2_over_2 =
+      SaturatingRoundingMultiplyByPOT<-1>(
+          ((x4_over_4 + x3) * constant_1_over_3) + x2);
+  return AddSaturatingIf16Bit(
+      constant_term,
+      constant_term * (x + x4_over_24_plus_x3_over_6_plus_x2_over_2));
+}
+
+// Returns exp(x) for x < 0.
+template <typename tRawType, int tIntegerBits>
+FixedPoint<tRawType, 0> exp_on_negative_values(
+    FixedPoint<tRawType, tIntegerBits> a) {
+  typedef FixedPoint<tRawType, tIntegerBits> InputF;
+  typedef FixedPoint<tRawType, 0> ResultF;
+  static constexpr int kFractionalBits = InputF::kFractionalBits;
+  static constexpr int kIntegerBits = InputF::kIntegerBits;
+  const InputF kOneQuarter = InputF::template ConstantPOT<-2>();
+  InputF mask = kOneQuarter - InputF::FromScalarRaw(1);
+  InputF a_mod_quarter_minus_one_quarter = (a & mask) - kOneQuarter;
+  ResultF result = exp_on_interval_between_negative_one_quarter_and_0_excl(
+      Rescale<0>(a_mod_quarter_minus_one_quarter));
+  tRawType remainder = (a_mod_quarter_minus_one_quarter - a).raw();
+
+#define GEMMLOWP_EXP_BARREL_SHIFTER(Exponent, FixedPointMultiplier)         \
+  if (kIntegerBits > Exponent) {                                            \
+    const ResultF kMultiplier = GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(       \
+        ResultF, FixedPointMultiplier, std::exp(-std::pow(2.0, Exponent))); \
+    static constexpr int kShiftAmount =                                     \
+        kIntegerBits > Exponent ? kFractionalBits + Exponent : 0;           \
+    result = SelectUsingMask(                                               \
+        MaskIfNonZero(BitAnd(remainder, Dup<tRawType>(1 << kShiftAmount))), \
+        result * kMultiplier, result);                                      \
+  }
+
+  GEMMLOWP_EXP_BARREL_SHIFTER(-2, 1672461947);
+  GEMMLOWP_EXP_BARREL_SHIFTER(-1, 1302514674);
+  GEMMLOWP_EXP_BARREL_SHIFTER(+0, 790015084);
+  GEMMLOWP_EXP_BARREL_SHIFTER(+1, 290630308);
+  GEMMLOWP_EXP_BARREL_SHIFTER(+2, 39332535);
+  GEMMLOWP_EXP_BARREL_SHIFTER(+3, 720401);
+  GEMMLOWP_EXP_BARREL_SHIFTER(+4, 242);
+
+#undef GEMMLOWP_EXP_BARREL_SHIFTER
+
+  static constexpr int clampB = kIntegerBits > 5 ? 36 - kIntegerBits : 0;
+  if (kIntegerBits > 5) {
+    const InputF clamp =
+        GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(InputF, -(1 << clampB), -32.0);
+    result = SelectUsingMask(MaskIfLessThan(a, clamp), ResultF::Zero(), result);
+  }
+
+  result = SelectUsingMask(MaskIfZero(a), ResultF::One(), result);
+  return result;
+}
+
+// Implementation of tanh: (1 - exp(-2x)) / (1 + exp(-2x)).
+
+// Returns (1 - x) / (1 + x) for x in (0, 1).
+template <typename tRawType>
+FixedPoint<tRawType, 0> one_minus_x_over_one_plus_x_for_x_in_0_1(
+    FixedPoint<tRawType, 0> a) {
+  typedef FixedPoint<tRawType, 0> F0;
+  typedef FixedPoint<tRawType, 2> F2;
+  F0 half_denominator = RoundingHalfSum(a, F0::One());
+  // Newton-Raphson division
+  // https://en.wikipedia.org/wiki/Division_algorithm#Newton.E2.80.93Raphson_division
+  // Refer to that page for the logic behind the 48/17 and 32/17 constants.
+  const F2 constant_48_over_17 =
+      GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(F2, 1515870810, 48.0 / 17.0);
+  const F2 constant_neg_32_over_17 =
+      GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(F2, -1010580540, -32.0 / 17.0);
+  F2 x = constant_48_over_17 + half_denominator * constant_neg_32_over_17;
+  for (int i = 0; i < 3; i++) {
+    F2 half_denominator_times_x = half_denominator * x;
+    F2 one_minus_half_denominator_times_x =
+        F2::One() - half_denominator_times_x;
+    x = x + Rescale<2>(x * one_minus_half_denominator_times_x);
+  }
+  return Rescale<0>(x - F2::One());
+}
+
+// Returns -tanh(x) for x < 0.
+template <typename tRawType, int tIntegerBits>
+FixedPoint<tRawType, 0> neg_tanh_on_negative_values(
+    FixedPoint<tRawType, tIntegerBits> a) {
+  return one_minus_x_over_one_plus_x_for_x_in_0_1(
+      exp_on_negative_values(ExactMulByPot<1>(a)));
+}
+
+// Returns tanh(x) for any x.
+template <typename tRawType, int tIntegerBits>
+FixedPoint<tRawType, 0> tanh(FixedPoint<tRawType, tIntegerBits> a) {
+  typedef FixedPoint<tRawType, tIntegerBits> InputF;
+  typedef FixedPoint<tRawType, 0> ResultF;
+  tRawType mask_if_negative = MaskIfLessThan(a, InputF::Zero());
+  tRawType mask_if_zero = MaskIfZero(a);
+  InputF n = SelectUsingMask(mask_if_negative, a, -a);
+  ResultF t = neg_tanh_on_negative_values(n);
+  return SelectUsingMask(mask_if_zero, ResultF::Zero(),
+                         SelectUsingMask(mask_if_negative, -t, t));
+}
+
+// Implementation of logistic function.
+
+// Returns 1 / (1 + x) for x in (0, 1).
+template <typename tRawType>
+FixedPoint<tRawType, 0> one_over_one_plus_x_for_x_in_0_1(
+    FixedPoint<tRawType, 0> a) {
+  typedef FixedPoint<tRawType, 0> F0;
+  typedef FixedPoint<tRawType, 2> F2;
+  F0 half_denominator = RoundingHalfSum(a, F0::One());
+  // Newton-Raphson division
+  // https://en.wikipedia.org/wiki/Division_algorithm#Newton.E2.80.93Raphson_division
+  // Refer to that page for the logic behind the 48/17 and 32/17 constants.
+  const F2 constant_48_over_17 =
+      GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(F2, 1515870810, 48.0 / 17.0);
+  const F2 constant_neg_32_over_17 =
+      GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(F2, -1010580540, -32.0 / 17.0);
+  F2 x = constant_48_over_17 + half_denominator * constant_neg_32_over_17;
+  for (int i = 0; i < 3; i++) {
+    F2 half_denominator_times_x = half_denominator * x;
+    F2 one_minus_half_denominator_times_x =
+        F2::One() - half_denominator_times_x;
+    x = x + Rescale<2>(x * one_minus_half_denominator_times_x);
+  }
+  return Rescale<0>(ExactMulByPot<-1>(x));
+}
+
+// Returns logistic(x) = 1 / (1 + exp(-x)) for x > 0.
+template <typename tRawType, int tIntegerBits>
+FixedPoint<tRawType, 0> logistic_on_positive_values(
+    FixedPoint<tRawType, tIntegerBits> a) {
+  return one_over_one_plus_x_for_x_in_0_1(exp_on_negative_values(-a));
+}
+
+// Returns logistic(x) = 1 / (1 + exp(-x)) for any x.
+template <typename tRawType, int tIntegerBits>
+FixedPoint<tRawType, 0> logistic(FixedPoint<tRawType, tIntegerBits> a) {
+  typedef FixedPoint<tRawType, tIntegerBits> InputF;
+  typedef FixedPoint<tRawType, 0> ResultF;
+  tRawType mask_if_positive = MaskIfGreaterThan(a, InputF::Zero());
+  tRawType mask_if_zero = MaskIfZero(a);
+  InputF abs_input = SelectUsingMask(mask_if_positive, a, -a);
+  ResultF result_if_positive = logistic_on_positive_values(abs_input);
+  ResultF result_if_negative = ResultF::One() - result_if_positive;
+  const ResultF one_half =
+      GEMMLOWP_CHECKED_FIXEDPOINT_CONSTANT(ResultF, 1 << 30, 0.5);
+  return SelectUsingMask(mask_if_zero, one_half,
+                         SelectUsingMask(mask_if_positive, result_if_positive,
+                                         result_if_negative));
+}
+
+}  // end namespace gemmlowp
+
+#ifdef GEMMLOWP_NEON
+#include "./fixedpoint_neon.h"
+#elif defined(GEMMLOWP_AVX2)
+#include "./fixedpoint_avx.h"
+#elif defined(GEMMLOWP_SSE4)
+#include "./fixedpoint_sse.h"
+#elif defined(GEMMLOWP_MSA)
+#include "./fixedpoint_msa.h"
+#endif
+
+#endif  // GEMMLOWP_INTERNAL_FIXEDPOINT_H_

+ 384 - 0
code/lib/tfmicro/fixedpoint/fixedpoint_sse.h

@@ -0,0 +1,384 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// fixedpoint_SSE.h: optimized SSE specializations of the templates
+// in fixedpoint.h.
+
+#ifndef GEMMLOWP_INTERNAL_FIXEDPOINT_SSE_H_
+#define GEMMLOWP_INTERNAL_FIXEDPOINT_SSE_H_
+
+#include <smmintrin.h>
+#include "fixedpoint.h"
+
+namespace gemmlowp {
+
+// SSE intrinsics are not finely typed: there is a single __m128i vector
+// type that does not distinguish between "int32x4" and "int16x8" use
+// cases, unlike the NEON equivalents. Because we had initially focused
+// on int32x4, we did not pay attention and specialized these fixedpoint
+// templates directly for __m128i hardcoding the int32x4 semantics,
+// not leaving room for int16x8 semantics. Amending that by adding a separate
+// data type, int16x8_m128i, that wraps __m128i while being a separate
+// type.
+struct int16x8_m128i {
+  int16x8_m128i() {}
+  explicit int16x8_m128i(__m128i w) : v(w) {}
+  ~int16x8_m128i() {}
+
+  __m128i v;
+};
+
+template <>
+struct FixedPointRawTypeTraits<__m128i> {
+  typedef std::int32_t ScalarRawType;
+  static constexpr int kLanes = 4;
+};
+
+template <>
+struct FixedPointRawTypeTraits<int16x8_m128i> {
+  typedef std::int16_t ScalarRawType;
+  static constexpr int kLanes = 8;
+};
+
+template <>
+inline __m128i BitAnd(__m128i a, __m128i b) {
+  return _mm_and_si128(a, b);
+}
+
+template <>
+inline int16x8_m128i BitAnd(int16x8_m128i a, int16x8_m128i b) {
+  return int16x8_m128i(_mm_and_si128(a.v, b.v));
+}
+
+template <>
+inline __m128i BitOr(__m128i a, __m128i b) {
+  return _mm_or_si128(a, b);
+}
+
+template <>
+inline int16x8_m128i BitOr(int16x8_m128i a, int16x8_m128i b) {
+  return int16x8_m128i(_mm_or_si128(a.v, b.v));
+}
+
+template <>
+inline __m128i BitXor(__m128i a, __m128i b) {
+  return _mm_xor_si128(a, b);
+}
+
+template <>
+inline int16x8_m128i BitXor(int16x8_m128i a, int16x8_m128i b) {
+  return int16x8_m128i(_mm_xor_si128(a.v, b.v));
+}
+
+template <>
+inline __m128i BitNot(__m128i a) {
+  return _mm_andnot_si128(a, _mm_set1_epi32(-1));
+}
+
+template <>
+inline int16x8_m128i BitNot(int16x8_m128i a) {
+  return int16x8_m128i(_mm_andnot_si128(a.v, _mm_set1_epi16(-1)));
+}
+
+template <>
+inline __m128i Add(__m128i a, __m128i b) {
+  return _mm_add_epi32(a, b);
+}
+
+template <>
+inline int16x8_m128i Add(int16x8_m128i a, int16x8_m128i b) {
+  return int16x8_m128i(_mm_add_epi16(a.v, b.v));
+}
+
+template <>
+inline __m128i Mul(__m128i a, __m128i b) {
+  return _mm_mullo_epi32(a, b);
+}
+
+template <>
+inline int16x8_m128i Mul(int16x8_m128i a, int16x8_m128i b) {
+  return int16x8_m128i(_mm_mullo_epi16(a.v, b.v));
+}
+
+template <>
+inline __m128i Sub(__m128i a, __m128i b) {
+  return _mm_sub_epi32(a, b);
+}
+
+template <>
+inline int16x8_m128i Sub(int16x8_m128i a, int16x8_m128i b) {
+  return int16x8_m128i(_mm_sub_epi16(a.v, b.v));
+}
+
+template <>
+inline __m128i Neg(__m128i a) {
+  return _mm_sign_epi32(a, _mm_set1_epi32(-1));
+}
+
+template <>
+inline int16x8_m128i Neg(int16x8_m128i a) {
+  return int16x8_m128i(_mm_sign_epi16(a.v, _mm_set1_epi16(-1)));
+}
+
+template <>
+inline __m128i ShiftLeft(__m128i a, int offset) {
+  return _mm_slli_epi32(a, offset);
+}
+
+template <>
+inline int16x8_m128i ShiftLeft(int16x8_m128i a, int offset) {
+  return int16x8_m128i(_mm_slli_epi16(a.v, offset));
+}
+
+template <>
+inline __m128i ShiftRight(__m128i a, int offset) {
+  return _mm_srai_epi32(a, offset);
+}
+
+template <>
+inline int16x8_m128i ShiftRight(int16x8_m128i a, int offset) {
+  return int16x8_m128i(_mm_srai_epi16(a.v, offset));
+}
+
+template <>
+inline __m128i SelectUsingMask(__m128i if_mask, __m128i then_val,
+                               __m128i else_val) {
+  // borrowed from Intel's arm_neon_sse.h header.
+  return _mm_or_si128(_mm_and_si128(if_mask, then_val),
+                      _mm_andnot_si128(if_mask, else_val));
+}
+
+template <>
+inline int16x8_m128i SelectUsingMask(int16x8_m128i if_mask,
+                                     int16x8_m128i then_val,
+                                     int16x8_m128i else_val) {
+  // borrowed from Intel's arm_neon_sse.h header.
+  return int16x8_m128i(SelectUsingMask(if_mask.v, then_val.v, else_val.v));
+}
+
+template <>
+inline __m128i MaskIfEqual(__m128i a, __m128i b) {
+  return _mm_cmpeq_epi32(a, b);
+}
+
+template <>
+inline int16x8_m128i MaskIfEqual(int16x8_m128i a, int16x8_m128i b) {
+  return int16x8_m128i(_mm_cmpeq_epi16(a.v, b.v));
+}
+
+template <>
+inline __m128i MaskIfNotEqual(__m128i a, __m128i b) {
+  return BitNot(MaskIfEqual(a, b));
+}
+
+template <>
+inline int16x8_m128i MaskIfNotEqual(int16x8_m128i a, int16x8_m128i b) {
+  return BitNot(MaskIfEqual(a, b));
+}
+
+template <>
+inline __m128i MaskIfZero(__m128i a) {
+  return MaskIfEqual(a, _mm_set1_epi32(0));
+}
+
+template <>
+inline int16x8_m128i MaskIfZero(int16x8_m128i a) {
+  return MaskIfEqual(a, int16x8_m128i(_mm_set1_epi16(0)));
+}
+
+template <>
+inline __m128i MaskIfNonZero(__m128i a) {
+  return MaskIfNotEqual(a, _mm_set1_epi32(0));
+}
+
+template <>
+inline int16x8_m128i MaskIfNonZero(int16x8_m128i a) {
+  return MaskIfNotEqual(a, int16x8_m128i(_mm_set1_epi16(0)));
+}
+
+template <>
+inline __m128i MaskIfGreaterThan(__m128i a, __m128i b) {
+  return _mm_cmpgt_epi32(a, b);
+}
+
+template <>
+inline int16x8_m128i MaskIfGreaterThan(int16x8_m128i a, int16x8_m128i b) {
+  return int16x8_m128i(_mm_cmpgt_epi16(a.v, b.v));
+}
+
+template <>
+inline __m128i MaskIfLessThan(__m128i a, __m128i b) {
+  return _mm_cmplt_epi32(a, b);
+}
+
+template <>
+inline int16x8_m128i MaskIfLessThan(int16x8_m128i a, int16x8_m128i b) {
+  return int16x8_m128i(_mm_cmplt_epi16(a.v, b.v));
+}
+
+template <>
+inline __m128i MaskIfGreaterThanOrEqual(__m128i a, __m128i b) {
+  return BitNot(MaskIfLessThan(a, b));
+}
+
+template <>
+inline int16x8_m128i MaskIfGreaterThanOrEqual(int16x8_m128i a,
+                                              int16x8_m128i b) {
+  return BitNot(MaskIfLessThan(a, b));
+}
+
+template <>
+inline __m128i MaskIfLessThanOrEqual(__m128i a, __m128i b) {
+  return BitNot(MaskIfGreaterThan(a, b));
+}
+
+template <>
+inline int16x8_m128i MaskIfLessThanOrEqual(int16x8_m128i a, int16x8_m128i b) {
+  return BitNot(MaskIfGreaterThan(a, b));
+}
+
+/* Assumptions:
+   - All and Any are used on masks.
+   - masks are all_ones for true lanes, all_zeroes otherwise.
+Hence, All means all 128bits set, and Any means any bit set.
+*/
+
+template <>
+inline bool All(__m128i a) {
+  return _mm_testc_si128(a, a);
+}
+
+template <>
+inline bool All(int16x8_m128i a) {
+  return _mm_testc_si128(a.v, a.v);
+}
+
+template <>
+inline bool Any(__m128i a) {
+  return !_mm_testz_si128(a, a);
+}
+
+template <>
+inline bool Any(int16x8_m128i a) {
+  return !_mm_testz_si128(a.v, a.v);
+}
+
+template <>
+inline __m128i RoundingHalfSum(__m128i a, __m128i b) {
+  /* __m128i round_bit_mask, a_over_2, b_over_2, round_bit, sum; */
+  /* We divide the inputs before the add to avoid the overflow and costly test
+   */
+  /* of checking if an overflow occured on signed add */
+  /* round_bit_mask = _mm_set1_epi32(1); */
+  /* a_over_2 = _mm_srai_epi32(a, 1); */
+  /* b_over_2 = _mm_srai_epi32(b, 1); */
+  /* sum = Add(a_over_2, b_over_2); */
+  /* round_bit = _mm_sign_epi32(BitAnd(BitOr(a,b), round_bit_mask), sum); */
+  /* return Add(sum, round_bit); */
+
+  /* Other possibility detecting overflow and xor the sign if an overflow
+   * happened*/
+  __m128i one, sign_bit_mask, sum, rounded_half_sum, overflow, result;
+  one = _mm_set1_epi32(1);
+  sign_bit_mask = _mm_set1_epi32(0x80000000);
+  sum = Add(a, b);
+  rounded_half_sum = _mm_srai_epi32(Add(sum, one), 1);
+  overflow =
+      BitAnd(BitAnd(BitXor(a, rounded_half_sum), BitXor(b, rounded_half_sum)),
+             sign_bit_mask);
+  result = BitXor(rounded_half_sum, overflow);
+  return result;
+}
+
+template <>
+inline int16x8_m128i RoundingHalfSum(int16x8_m128i a, int16x8_m128i b) {
+  // Idea: go to unsigned to use _mm_avg_epu16,
+  // borrowed from Intel's arm_neon_sse.h header.
+  __m128i constant_neg_32768 = _mm_set1_epi16(-32768);
+  __m128i a_unsigned = _mm_sub_epi16(a.v, constant_neg_32768);
+  __m128i b_unsigned = _mm_sub_epi16(b.v, constant_neg_32768);
+  __m128i avg_unsigned = _mm_avg_epu16(a_unsigned, b_unsigned);
+  __m128i avg = _mm_add_epi16(avg_unsigned, constant_neg_32768);
+  return int16x8_m128i(avg);
+}
+
+template <>
+inline __m128i SaturatingRoundingDoublingHighMul(__m128i a, __m128i b) {
+  __m128i min, saturation_mask, a0_a2, a1_a3, b0_b2, b1_b3;
+  __m128i a0b0_a2b2, a1b1_a3b3, a0b0_a2b2_rounded, a1b1_a3b3_rounded;
+  __m128i a0b0_a2b2_rounded_2x, a1b1_a3b3_rounded_2x, result;
+  __m128i nudge;
+
+  // saturation only happen if a == b == INT_MIN
+  min = _mm_set1_epi32(std::numeric_limits<std::int32_t>::min());
+  saturation_mask = BitAnd(MaskIfEqual(a, b), MaskIfEqual(a, min));
+
+  // a = a0 | a1 | a2 | a3
+  // b = b0 | b1 | b2 | b3
+  a0_a2 = a;
+  a1_a3 = _mm_srli_si128(a, 4);
+  b0_b2 = b;
+  b1_b3 = _mm_srli_si128(b, 4);
+
+  a0b0_a2b2 = _mm_mul_epi32(a0_a2, b0_b2);
+  a1b1_a3b3 = _mm_mul_epi32(a1_a3, b1_b3);
+
+  // do the rounding and take into account that it will be doubled
+  nudge = _mm_set1_epi64x(1 << 30);
+  a0b0_a2b2_rounded = _mm_add_epi64(a0b0_a2b2, nudge);
+  a1b1_a3b3_rounded = _mm_add_epi64(a1b1_a3b3, nudge);
+
+  // do the doubling
+  a0b0_a2b2_rounded_2x = _mm_slli_epi64(a0b0_a2b2_rounded, 1);
+  a1b1_a3b3_rounded_2x = _mm_slli_epi64(a1b1_a3b3_rounded, 1);
+
+  // get the high part of the products
+  result = _mm_blend_epi16(_mm_srli_si128(a0b0_a2b2_rounded_2x, 4),
+                           a1b1_a3b3_rounded_2x, 0xcc);
+
+  // saturate those which overflowed
+  return SelectUsingMask(saturation_mask, min, result);
+}
+
+template <>
+inline int16x8_m128i SaturatingRoundingDoublingHighMul(int16x8_m128i a,
+                                                       int16x8_m128i b) {
+  // Idea: use _mm_mulhrs_epi16 then saturate with a bit-operation,
+  // borrowed from Intel's arm_neon_sse.h header.
+  __m128i result_unsaturated = _mm_mulhrs_epi16(a.v, b.v);
+  __m128i saturation_mask =
+      _mm_cmpeq_epi16(result_unsaturated, _mm_set1_epi16(0x8000));
+  __m128i result = _mm_xor_si128(result_unsaturated, saturation_mask);
+  return int16x8_m128i(result);
+}
+
+template <>
+inline __m128i Dup<__m128i>(std::int32_t x) {
+  return _mm_set1_epi32(x);
+}
+
+template <>
+inline int16x8_m128i Dup<int16x8_m128i>(std::int16_t x) {
+  return int16x8_m128i(_mm_set1_epi16(x));
+}
+
+// So far this is only needed for int16.
+template <>
+inline int16x8_m128i SaturatingAdd(int16x8_m128i a, int16x8_m128i b) {
+  return int16x8_m128i(_mm_adds_epi16(a.v, b.v));
+}
+
+}  // end namespace gemmlowp
+
+#endif  // GEMMLOWP_INTERNAL_FIXEDPOINT_SSE_H_

+ 398 - 0
code/lib/tfmicro/flatbuffers/base.h

@@ -0,0 +1,398 @@
+#ifndef FLATBUFFERS_BASE_H_
+#define FLATBUFFERS_BASE_H_
+
+// clang-format off
+
+// If activate should be declared and included first.
+#if defined(FLATBUFFERS_MEMORY_LEAK_TRACKING) && \
+    defined(_MSC_VER) && defined(_DEBUG)
+  // The _CRTDBG_MAP_ALLOC inside <crtdbg.h> will replace
+  // calloc/free (etc) to its debug version using #define directives.
+  #define _CRTDBG_MAP_ALLOC
+  #include <stdlib.h>
+  #include <crtdbg.h>
+  // Replace operator new by trace-enabled version.
+  #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__)
+  #define new DEBUG_NEW
+#endif
+
+#if !defined(FLATBUFFERS_ASSERT)
+#include <assert.h>
+#define FLATBUFFERS_ASSERT assert
+#elif defined(FLATBUFFERS_ASSERT_INCLUDE)
+// Include file with forward declaration
+#include FLATBUFFERS_ASSERT_INCLUDE
+#endif
+
+#ifndef ARDUINO
+#include <cstdint>
+#endif
+
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
+
+#if defined(ARDUINO) && !defined(ARDUINOSTL_M_H)
+  #include <utility.h>
+#else
+  #include <utility>
+#endif
+
+#include <string>
+#include <type_traits>
+#include <vector>
+#include <set>
+#include <algorithm>
+#include <iterator>
+#include <memory>
+
+#ifdef _STLPORT_VERSION
+  #define FLATBUFFERS_CPP98_STL
+#endif
+#ifndef FLATBUFFERS_CPP98_STL
+  #include <functional>
+#endif
+
+#include "flatbuffers/stl_emulation.h"
+
+#if defined(__ICCARM__)
+#include <intrinsics.h>
+#endif
+
+// Note the __clang__ check is needed, because clang presents itself
+// as an older GNUC compiler (4.2).
+// Clang 3.3 and later implement all of the ISO C++ 2011 standard.
+// Clang 3.4 and later implement all of the ISO C++ 2014 standard.
+// http://clang.llvm.org/cxx_status.html
+
+// Note the MSVC value '__cplusplus' may be incorrect:
+// The '__cplusplus' predefined macro in the MSVC stuck at the value 199711L,
+// indicating (erroneously!) that the compiler conformed to the C++98 Standard.
+// This value should be correct starting from MSVC2017-15.7-Preview-3.
+// The '__cplusplus' will be valid only if MSVC2017-15.7-P3 and the `/Zc:__cplusplus` switch is set.
+// Workaround (for details see MSDN):
+// Use the _MSC_VER and _MSVC_LANG definition instead of the __cplusplus  for compatibility.
+// The _MSVC_LANG macro reports the Standard version regardless of the '/Zc:__cplusplus' switch.
+
+#if defined(__GNUC__) && !defined(__clang__)
+  #define FLATBUFFERS_GCC (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
+#else
+  #define FLATBUFFERS_GCC 0
+#endif
+
+#if defined(__clang__)
+  #define FLATBUFFERS_CLANG (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__)
+#else
+  #define FLATBUFFERS_CLANG 0
+#endif
+
+/// @cond FLATBUFFERS_INTERNAL
+#if __cplusplus <= 199711L && \
+    (!defined(_MSC_VER) || _MSC_VER < 1600) && \
+    (!defined(__GNUC__) || \
+      (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ < 40400))
+  #error A C++11 compatible compiler with support for the auto typing is \
+         required for FlatBuffers.
+  #error __cplusplus _MSC_VER __GNUC__  __GNUC_MINOR__  __GNUC_PATCHLEVEL__
+#endif
+
+#if !defined(__clang__) && \
+    defined(__GNUC__) && \
+    (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__ < 40600)
+  // Backwards compatibility for g++ 4.4, and 4.5 which don't have the nullptr
+  // and constexpr keywords. Note the __clang__ check is needed, because clang
+  // presents itself as an older GNUC compiler.
+  #ifndef nullptr_t
+    const class nullptr_t {
+    public:
+      template<class T> inline operator T*() const { return 0; }
+    private:
+      void operator&() const;
+    } nullptr = {};
+  #endif
+  #ifndef constexpr
+    #define constexpr const
+  #endif
+#endif
+
+// The wire format uses a little endian encoding (since that's efficient for
+// the common platforms).
+#if defined(__s390x__)
+  #define FLATBUFFERS_LITTLEENDIAN 0
+#endif // __s390x__
+#if !defined(FLATBUFFERS_LITTLEENDIAN)
+  #if defined(__GNUC__) || defined(__clang__) || defined(__ICCARM__)
+    #if (defined(__BIG_ENDIAN__) || \
+         (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
+      #define FLATBUFFERS_LITTLEENDIAN 0
+    #else
+      #define FLATBUFFERS_LITTLEENDIAN 1
+    #endif // __BIG_ENDIAN__
+  #elif defined(_MSC_VER)
+    #if defined(_M_PPC)
+      #define FLATBUFFERS_LITTLEENDIAN 0
+    #else
+      #define FLATBUFFERS_LITTLEENDIAN 1
+    #endif
+  #else
+    #error Unable to determine endianness, define FLATBUFFERS_LITTLEENDIAN.
+  #endif
+#endif // !defined(FLATBUFFERS_LITTLEENDIAN)
+
+#define FLATBUFFERS_VERSION_MAJOR 1
+#define FLATBUFFERS_VERSION_MINOR 12
+#define FLATBUFFERS_VERSION_REVISION 0
+#define FLATBUFFERS_STRING_EXPAND(X) #X
+#define FLATBUFFERS_STRING(X) FLATBUFFERS_STRING_EXPAND(X)
+namespace flatbuffers {
+  // Returns version as string  "MAJOR.MINOR.REVISION".
+  const char* FLATBUFFERS_VERSION();
+}
+
+#if (!defined(_MSC_VER) || _MSC_VER > 1600) && \
+    (!defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 407)) || \
+    defined(__clang__)
+  #define FLATBUFFERS_FINAL_CLASS final
+  #define FLATBUFFERS_OVERRIDE override
+  #define FLATBUFFERS_VTABLE_UNDERLYING_TYPE : flatbuffers::voffset_t
+#else
+  #define FLATBUFFERS_FINAL_CLASS
+  #define FLATBUFFERS_OVERRIDE
+  #define FLATBUFFERS_VTABLE_UNDERLYING_TYPE
+#endif
+
+#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && \
+    (!defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 406)) || \
+    (defined(__cpp_constexpr) && __cpp_constexpr >= 200704)
+  #define FLATBUFFERS_CONSTEXPR constexpr
+#else
+  #define FLATBUFFERS_CONSTEXPR const
+#endif
+
+#if (defined(__cplusplus) && __cplusplus >= 201402L) || \
+    (defined(__cpp_constexpr) && __cpp_constexpr >= 201304)
+  #define FLATBUFFERS_CONSTEXPR_CPP14 FLATBUFFERS_CONSTEXPR
+#else
+  #define FLATBUFFERS_CONSTEXPR_CPP14
+#endif
+
+#if (defined(__GXX_EXPERIMENTAL_CXX0X__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 406)) || \
+    (defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 190023026)) || \
+    defined(__clang__)
+  #define FLATBUFFERS_NOEXCEPT noexcept
+#else
+  #define FLATBUFFERS_NOEXCEPT
+#endif
+
+// NOTE: the FLATBUFFERS_DELETE_FUNC macro may change the access mode to
+// private, so be sure to put it at the end or reset access mode explicitly.
+#if (!defined(_MSC_VER) || _MSC_FULL_VER >= 180020827) && \
+    (!defined(__GNUC__) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 404)) || \
+    defined(__clang__)
+  #define FLATBUFFERS_DELETE_FUNC(func) func = delete;
+#else
+  #define FLATBUFFERS_DELETE_FUNC(func) private: func;
+#endif
+
+#ifndef FLATBUFFERS_HAS_STRING_VIEW
+  // Only provide flatbuffers::string_view if __has_include can be used
+  // to detect a header that provides an implementation
+  #if defined(__has_include)
+    // Check for std::string_view (in c++17)
+    #if __has_include(<string_view>) && (__cplusplus >= 201606 || (defined(_HAS_CXX17) && _HAS_CXX17))
+      #include <string_view>
+      namespace flatbuffers {
+        typedef std::string_view string_view;
+      }
+      #define FLATBUFFERS_HAS_STRING_VIEW 1
+    // Check for std::experimental::string_view (in c++14, compiler-dependent)
+    #elif __has_include(<experimental/string_view>) && (__cplusplus >= 201411)
+      #include <experimental/string_view>
+      namespace flatbuffers {
+        typedef std::experimental::string_view string_view;
+      }
+      #define FLATBUFFERS_HAS_STRING_VIEW 1
+    // Check for absl::string_view
+    #elif __has_include("absl/strings/string_view.h")
+      #include "absl/strings/string_view.h"
+      namespace flatbuffers {
+        typedef absl::string_view string_view;
+      }
+      #define FLATBUFFERS_HAS_STRING_VIEW 1
+    #endif
+  #endif // __has_include
+#endif // !FLATBUFFERS_HAS_STRING_VIEW
+
+#ifndef FLATBUFFERS_HAS_NEW_STRTOD
+  // Modern (C++11) strtod and strtof functions are available for use.
+  // 1) nan/inf strings as argument of strtod;
+  // 2) hex-float  as argument of  strtod/strtof.
+  #if (defined(_MSC_VER) && _MSC_VER >= 1900) || \
+      (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 409)) || \
+      (defined(__clang__))
+    #define FLATBUFFERS_HAS_NEW_STRTOD 1
+  #endif
+#endif // !FLATBUFFERS_HAS_NEW_STRTOD
+
+#ifndef FLATBUFFERS_LOCALE_INDEPENDENT
+  // Enable locale independent functions {strtof_l, strtod_l,strtoll_l, strtoull_l}.
+  // They are part of the POSIX-2008 but not part of the C/C++ standard.
+  // GCC/Clang have definition (_XOPEN_SOURCE>=700) if POSIX-2008.
+  #if ((defined(_MSC_VER) && _MSC_VER >= 1800)            || \
+       (defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE>=700)))
+    #define FLATBUFFERS_LOCALE_INDEPENDENT 1
+  #else
+    #define FLATBUFFERS_LOCALE_INDEPENDENT 0
+  #endif
+#endif  // !FLATBUFFERS_LOCALE_INDEPENDENT
+
+// Suppress Undefined Behavior Sanitizer (recoverable only). Usage:
+// - __supress_ubsan__("undefined")
+// - __supress_ubsan__("signed-integer-overflow")
+#if defined(__clang__) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >=7))
+  #define __supress_ubsan__(type) __attribute__((no_sanitize(type)))
+#elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__ >= 409)
+  #define __supress_ubsan__(type) __attribute__((no_sanitize_undefined))
+#else
+  #define __supress_ubsan__(type)
+#endif
+
+// This is constexpr function used for checking compile-time constants.
+// Avoid `#pragma warning(disable: 4127) // C4127: expression is constant`.
+template<typename T> FLATBUFFERS_CONSTEXPR inline bool IsConstTrue(T t) {
+  return !!t;
+}
+
+// Enable C++ attribute [[]] if std:c++17 or higher.
+#if ((__cplusplus >= 201703L) \
+    || (defined(_MSVC_LANG) &&  (_MSVC_LANG >= 201703L)))
+  // All attributes unknown to an implementation are ignored without causing an error.
+  #define FLATBUFFERS_ATTRIBUTE(attr) [[attr]]
+
+  #define FLATBUFFERS_FALLTHROUGH() [[fallthrough]]
+#else
+  #define FLATBUFFERS_ATTRIBUTE(attr)
+
+  #if FLATBUFFERS_CLANG >= 30800
+    #define FLATBUFFERS_FALLTHROUGH() [[clang::fallthrough]]
+  #elif FLATBUFFERS_GCC >= 70300
+    #define FLATBUFFERS_FALLTHROUGH() [[gnu::fallthrough]]
+  #else
+    #define FLATBUFFERS_FALLTHROUGH()
+  #endif
+#endif
+
+/// @endcond
+
+/// @file
+namespace flatbuffers {
+
+/// @cond FLATBUFFERS_INTERNAL
+// Our default offset / size type, 32bit on purpose on 64bit systems.
+// Also, using a consistent offset type maintains compatibility of serialized
+// offset values between 32bit and 64bit systems.
+typedef uint32_t uoffset_t;
+
+// Signed offsets for references that can go in both directions.
+typedef int32_t soffset_t;
+
+// Offset/index used in v-tables, can be changed to uint8_t in
+// format forks to save a bit of space if desired.
+typedef uint16_t voffset_t;
+
+typedef uintmax_t largest_scalar_t;
+
+// In 32bits, this evaluates to 2GB - 1
+#define FLATBUFFERS_MAX_BUFFER_SIZE ((1ULL << (sizeof(::flatbuffers::soffset_t) * 8 - 1)) - 1)
+
+// We support aligning the contents of buffers up to this size.
+#define FLATBUFFERS_MAX_ALIGNMENT 16
+
+#if defined(_MSC_VER)
+  #pragma warning(push)
+  #pragma warning(disable: 4127) // C4127: conditional expression is constant
+#endif
+
+template<typename T> T EndianSwap(T t) {
+  #if defined(_MSC_VER)
+    #define FLATBUFFERS_BYTESWAP16 _byteswap_ushort
+    #define FLATBUFFERS_BYTESWAP32 _byteswap_ulong
+    #define FLATBUFFERS_BYTESWAP64 _byteswap_uint64
+  #elif defined(__ICCARM__)
+    #define FLATBUFFERS_BYTESWAP16 __REV16
+    #define FLATBUFFERS_BYTESWAP32 __REV
+    #define FLATBUFFERS_BYTESWAP64(x) \
+       ((__REV(static_cast<uint32_t>(x >> 32U))) | (static_cast<uint64_t>(__REV(static_cast<uint32_t>(x)))) << 32U)
+  #else
+    #if defined(__GNUC__) && __GNUC__ * 100 + __GNUC_MINOR__ < 408 && !defined(__clang__)
+      // __builtin_bswap16 was missing prior to GCC 4.8.
+      #define FLATBUFFERS_BYTESWAP16(x) \
+        static_cast<uint16_t>(__builtin_bswap32(static_cast<uint32_t>(x) << 16))
+    #else
+      #define FLATBUFFERS_BYTESWAP16 __builtin_bswap16
+    #endif
+    #define FLATBUFFERS_BYTESWAP32 __builtin_bswap32
+    #define FLATBUFFERS_BYTESWAP64 __builtin_bswap64
+  #endif
+  if (sizeof(T) == 1) {   // Compile-time if-then's.
+    return t;
+  } else if (sizeof(T) == 2) {
+    union { T t; uint16_t i; } u = { t };
+    u.i = FLATBUFFERS_BYTESWAP16(u.i);
+    return u.t;
+  } else if (sizeof(T) == 4) {
+    union { T t; uint32_t i; } u = { t };
+    u.i = FLATBUFFERS_BYTESWAP32(u.i);
+    return u.t;
+  } else if (sizeof(T) == 8) {
+    union { T t; uint64_t i; } u = { t };
+    u.i = FLATBUFFERS_BYTESWAP64(u.i);
+    return u.t;
+  } else {
+    FLATBUFFERS_ASSERT(0);
+    return t;
+  }
+}
+
+#if defined(_MSC_VER)
+  #pragma warning(pop)
+#endif
+
+
+template<typename T> T EndianScalar(T t) {
+  #if FLATBUFFERS_LITTLEENDIAN
+    return t;
+  #else
+    return EndianSwap(t);
+  #endif
+}
+
+template<typename T>
+// UBSAN: C++ aliasing type rules, see std::bit_cast<> for details.
+__supress_ubsan__("alignment")
+T ReadScalar(const void *p) {
+  return EndianScalar(*reinterpret_cast<const T *>(p));
+}
+
+template<typename T>
+// UBSAN: C++ aliasing type rules, see std::bit_cast<> for details.
+__supress_ubsan__("alignment")
+void WriteScalar(void *p, T t) {
+  *reinterpret_cast<T *>(p) = EndianScalar(t);
+}
+
+template<typename T> struct Offset;
+template<typename T> __supress_ubsan__("alignment") void WriteScalar(void *p, Offset<T> t) {
+  *reinterpret_cast<uoffset_t *>(p) = EndianScalar(t.o);
+}
+
+// Computes how many bytes you'd have to pad to be able to write an
+// "scalar_size" scalar if the buffer had grown to "buf_size" (downwards in
+// memory).
+__supress_ubsan__("unsigned-integer-overflow")
+inline size_t PaddingBytes(size_t buf_size, size_t scalar_size) {
+  return ((~buf_size) + 1) & (scalar_size - 1);
+}
+
+}  // namespace flatbuffers
+#endif  // FLATBUFFERS_BASE_H_

+ 2783 - 0
code/lib/tfmicro/flatbuffers/flatbuffers.h

@@ -0,0 +1,2783 @@
+/*
+ * Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FLATBUFFERS_H_
+#define FLATBUFFERS_H_
+
+#include "flatbuffers/base.h"
+
+#if defined(FLATBUFFERS_NAN_DEFAULTS)
+#  include <cmath>
+#endif
+
+namespace flatbuffers {
+// Generic 'operator==' with conditional specialisations.
+// T e - new value of a scalar field.
+// T def - default of scalar (is known at compile-time).
+template<typename T> inline bool IsTheSameAs(T e, T def) { return e == def; }
+
+#if defined(FLATBUFFERS_NAN_DEFAULTS) && \
+    defined(FLATBUFFERS_HAS_NEW_STRTOD) && (FLATBUFFERS_HAS_NEW_STRTOD > 0)
+// Like `operator==(e, def)` with weak NaN if T=(float|double).
+template<typename T> inline bool IsFloatTheSameAs(T e, T def) {
+  return (e == def) || ((def != def) && (e != e));
+}
+template<> inline bool IsTheSameAs<float>(float e, float def) {
+  return IsFloatTheSameAs(e, def);
+}
+template<> inline bool IsTheSameAs<double>(double e, double def) {
+  return IsFloatTheSameAs(e, def);
+}
+#endif
+
+// Check 'v' is out of closed range [low; high].
+// Workaround for GCC warning [-Werror=type-limits]:
+// comparison is always true due to limited range of data type.
+template<typename T>
+inline bool IsOutRange(const T &v, const T &low, const T &high) {
+  return (v < low) || (high < v);
+}
+
+// Check 'v' is in closed range [low; high].
+template<typename T>
+inline bool IsInRange(const T &v, const T &low, const T &high) {
+  return !IsOutRange(v, low, high);
+}
+
+// Wrapper for uoffset_t to allow safe template specialization.
+// Value is allowed to be 0 to indicate a null object (see e.g. AddOffset).
+template<typename T> struct Offset {
+  uoffset_t o;
+  Offset() : o(0) {}
+  Offset(uoffset_t _o) : o(_o) {}
+  Offset<void> Union() const { return Offset<void>(o); }
+  bool IsNull() const { return !o; }
+};
+
+inline void EndianCheck() {
+  int endiantest = 1;
+  // If this fails, see FLATBUFFERS_LITTLEENDIAN above.
+  FLATBUFFERS_ASSERT(*reinterpret_cast<char *>(&endiantest) ==
+                     FLATBUFFERS_LITTLEENDIAN);
+  (void)endiantest;
+}
+
+template<typename T> FLATBUFFERS_CONSTEXPR size_t AlignOf() {
+  // clang-format off
+  #ifdef _MSC_VER
+    return __alignof(T);
+  #else
+    #ifndef alignof
+      return __alignof__(T);
+    #else
+      return alignof(T);
+    #endif
+  #endif
+  // clang-format on
+}
+
+// When we read serialized data from memory, in the case of most scalars,
+// we want to just read T, but in the case of Offset, we want to actually
+// perform the indirection and return a pointer.
+// The template specialization below does just that.
+// It is wrapped in a struct since function templates can't overload on the
+// return type like this.
+// The typedef is for the convenience of callers of this function
+// (avoiding the need for a trailing return decltype)
+template<typename T> struct IndirectHelper {
+  typedef T return_type;
+  typedef T mutable_return_type;
+  static const size_t element_stride = sizeof(T);
+  static return_type Read(const uint8_t *p, uoffset_t i) {
+    return EndianScalar((reinterpret_cast<const T *>(p))[i]);
+  }
+};
+template<typename T> struct IndirectHelper<Offset<T>> {
+  typedef const T *return_type;
+  typedef T *mutable_return_type;
+  static const size_t element_stride = sizeof(uoffset_t);
+  static return_type Read(const uint8_t *p, uoffset_t i) {
+    p += i * sizeof(uoffset_t);
+    return reinterpret_cast<return_type>(p + ReadScalar<uoffset_t>(p));
+  }
+};
+template<typename T> struct IndirectHelper<const T *> {
+  typedef const T *return_type;
+  typedef T *mutable_return_type;
+  static const size_t element_stride = sizeof(T);
+  static return_type Read(const uint8_t *p, uoffset_t i) {
+    return reinterpret_cast<const T *>(p + i * sizeof(T));
+  }
+};
+
+// An STL compatible iterator implementation for Vector below, effectively
+// calling Get() for every element.
+template<typename T, typename IT> struct VectorIterator {
+  typedef std::random_access_iterator_tag iterator_category;
+  typedef IT value_type;
+  typedef ptrdiff_t difference_type;
+  typedef IT *pointer;
+  typedef IT &reference;
+
+  VectorIterator(const uint8_t *data, uoffset_t i)
+      : data_(data + IndirectHelper<T>::element_stride * i) {}
+  VectorIterator(const VectorIterator &other) : data_(other.data_) {}
+  VectorIterator() : data_(nullptr) {}
+
+  VectorIterator &operator=(const VectorIterator &other) {
+    data_ = other.data_;
+    return *this;
+  }
+
+  // clang-format off
+  #if !defined(FLATBUFFERS_CPP98_STL)
+  VectorIterator &operator=(VectorIterator &&other) {
+    data_ = other.data_;
+    return *this;
+  }
+  #endif  // !defined(FLATBUFFERS_CPP98_STL)
+  // clang-format on
+
+  bool operator==(const VectorIterator &other) const {
+    return data_ == other.data_;
+  }
+
+  bool operator<(const VectorIterator &other) const {
+    return data_ < other.data_;
+  }
+
+  bool operator!=(const VectorIterator &other) const {
+    return data_ != other.data_;
+  }
+
+  difference_type operator-(const VectorIterator &other) const {
+    return (data_ - other.data_) / IndirectHelper<T>::element_stride;
+  }
+
+  IT operator*() const { return IndirectHelper<T>::Read(data_, 0); }
+
+  IT operator->() const { return IndirectHelper<T>::Read(data_, 0); }
+
+  VectorIterator &operator++() {
+    data_ += IndirectHelper<T>::element_stride;
+    return *this;
+  }
+
+  VectorIterator operator++(int) {
+    VectorIterator temp(data_, 0);
+    data_ += IndirectHelper<T>::element_stride;
+    return temp;
+  }
+
+  VectorIterator operator+(const uoffset_t &offset) const {
+    return VectorIterator(data_ + offset * IndirectHelper<T>::element_stride,
+                          0);
+  }
+
+  VectorIterator &operator+=(const uoffset_t &offset) {
+    data_ += offset * IndirectHelper<T>::element_stride;
+    return *this;
+  }
+
+  VectorIterator &operator--() {
+    data_ -= IndirectHelper<T>::element_stride;
+    return *this;
+  }
+
+  VectorIterator operator--(int) {
+    VectorIterator temp(data_, 0);
+    data_ -= IndirectHelper<T>::element_stride;
+    return temp;
+  }
+
+  VectorIterator operator-(const uoffset_t &offset) const {
+    return VectorIterator(data_ - offset * IndirectHelper<T>::element_stride,
+                          0);
+  }
+
+  VectorIterator &operator-=(const uoffset_t &offset) {
+    data_ -= offset * IndirectHelper<T>::element_stride;
+    return *this;
+  }
+
+ private:
+  const uint8_t *data_;
+};
+
+template<typename Iterator>
+struct VectorReverseIterator : public std::reverse_iterator<Iterator> {
+  explicit VectorReverseIterator(Iterator iter)
+      : std::reverse_iterator<Iterator>(iter) {}
+
+  typename Iterator::value_type operator*() const {
+    return *(std::reverse_iterator<Iterator>::current);
+  }
+
+  typename Iterator::value_type operator->() const {
+    return *(std::reverse_iterator<Iterator>::current);
+  }
+};
+
+struct String;
+
+// This is used as a helper type for accessing vectors.
+// Vector::data() assumes the vector elements start after the length field.
+template<typename T> class Vector {
+ public:
+  typedef VectorIterator<T, typename IndirectHelper<T>::mutable_return_type>
+      iterator;
+  typedef VectorIterator<T, typename IndirectHelper<T>::return_type>
+      const_iterator;
+  typedef VectorReverseIterator<iterator> reverse_iterator;
+  typedef VectorReverseIterator<const_iterator> const_reverse_iterator;
+
+  uoffset_t size() const { return EndianScalar(length_); }
+
+  // Deprecated: use size(). Here for backwards compatibility.
+  FLATBUFFERS_ATTRIBUTE(deprecated("use size() instead"))
+  uoffset_t Length() const { return size(); }
+
+  typedef typename IndirectHelper<T>::return_type return_type;
+  typedef typename IndirectHelper<T>::mutable_return_type mutable_return_type;
+
+  return_type Get(uoffset_t i) const {
+    FLATBUFFERS_ASSERT(i < size());
+    return IndirectHelper<T>::Read(Data(), i);
+  }
+
+  return_type operator[](uoffset_t i) const { return Get(i); }
+
+  // If this is a Vector of enums, T will be its storage type, not the enum
+  // type. This function makes it convenient to retrieve value with enum
+  // type E.
+  template<typename E> E GetEnum(uoffset_t i) const {
+    return static_cast<E>(Get(i));
+  }
+
+  // If this a vector of unions, this does the cast for you. There's no check
+  // to make sure this is the right type!
+  template<typename U> const U *GetAs(uoffset_t i) const {
+    return reinterpret_cast<const U *>(Get(i));
+  }
+
+  // If this a vector of unions, this does the cast for you. There's no check
+  // to make sure this is actually a string!
+  const String *GetAsString(uoffset_t i) const {
+    return reinterpret_cast<const String *>(Get(i));
+  }
+
+  const void *GetStructFromOffset(size_t o) const {
+    return reinterpret_cast<const void *>(Data() + o);
+  }
+
+  iterator begin() { return iterator(Data(), 0); }
+  const_iterator begin() const { return const_iterator(Data(), 0); }
+
+  iterator end() { return iterator(Data(), size()); }
+  const_iterator end() const { return const_iterator(Data(), size()); }
+
+  reverse_iterator rbegin() { return reverse_iterator(end() - 1); }
+  const_reverse_iterator rbegin() const {
+    return const_reverse_iterator(end() - 1);
+  }
+
+  reverse_iterator rend() { return reverse_iterator(begin() - 1); }
+  const_reverse_iterator rend() const {
+    return const_reverse_iterator(begin() - 1);
+  }
+
+  const_iterator cbegin() const { return begin(); }
+
+  const_iterator cend() const { return end(); }
+
+  const_reverse_iterator crbegin() const { return rbegin(); }
+
+  const_reverse_iterator crend() const { return rend(); }
+
+  // Change elements if you have a non-const pointer to this object.
+  // Scalars only. See reflection.h, and the documentation.
+  void Mutate(uoffset_t i, const T &val) {
+    FLATBUFFERS_ASSERT(i < size());
+    WriteScalar(data() + i, val);
+  }
+
+  // Change an element of a vector of tables (or strings).
+  // "val" points to the new table/string, as you can obtain from
+  // e.g. reflection::AddFlatBuffer().
+  void MutateOffset(uoffset_t i, const uint8_t *val) {
+    FLATBUFFERS_ASSERT(i < size());
+    static_assert(sizeof(T) == sizeof(uoffset_t), "Unrelated types");
+    WriteScalar(data() + i,
+                static_cast<uoffset_t>(val - (Data() + i * sizeof(uoffset_t))));
+  }
+
+  // Get a mutable pointer to tables/strings inside this vector.
+  mutable_return_type GetMutableObject(uoffset_t i) const {
+    FLATBUFFERS_ASSERT(i < size());
+    return const_cast<mutable_return_type>(IndirectHelper<T>::Read(Data(), i));
+  }
+
+  // The raw data in little endian format. Use with care.
+  const uint8_t *Data() const {
+    return reinterpret_cast<const uint8_t *>(&length_ + 1);
+  }
+
+  uint8_t *Data() { return reinterpret_cast<uint8_t *>(&length_ + 1); }
+
+  // Similarly, but typed, much like std::vector::data
+  const T *data() const { return reinterpret_cast<const T *>(Data()); }
+  T *data() { return reinterpret_cast<T *>(Data()); }
+
+  template<typename K> return_type LookupByKey(K key) const {
+    void *search_result = std::bsearch(
+        &key, Data(), size(), IndirectHelper<T>::element_stride, KeyCompare<K>);
+
+    if (!search_result) {
+      return nullptr;  // Key not found.
+    }
+
+    const uint8_t *element = reinterpret_cast<const uint8_t *>(search_result);
+
+    return IndirectHelper<T>::Read(element, 0);
+  }
+
+ protected:
+  // This class is only used to access pre-existing data. Don't ever
+  // try to construct these manually.
+  Vector();
+
+  uoffset_t length_;
+
+ private:
+  // This class is a pointer. Copying will therefore create an invalid object.
+  // Private and unimplemented copy constructor.
+  Vector(const Vector &);
+  Vector &operator=(const Vector &);
+
+  template<typename K> static int KeyCompare(const void *ap, const void *bp) {
+    const K *key = reinterpret_cast<const K *>(ap);
+    const uint8_t *data = reinterpret_cast<const uint8_t *>(bp);
+    auto table = IndirectHelper<T>::Read(data, 0);
+
+    // std::bsearch compares with the operands transposed, so we negate the
+    // result here.
+    return -table->KeyCompareWithValue(*key);
+  }
+};
+
+// Represent a vector much like the template above, but in this case we
+// don't know what the element types are (used with reflection.h).
+class VectorOfAny {
+ public:
+  uoffset_t size() const { return EndianScalar(length_); }
+
+  const uint8_t *Data() const {
+    return reinterpret_cast<const uint8_t *>(&length_ + 1);
+  }
+  uint8_t *Data() { return reinterpret_cast<uint8_t *>(&length_ + 1); }
+
+ protected:
+  VectorOfAny();
+
+  uoffset_t length_;
+
+ private:
+  VectorOfAny(const VectorOfAny &);
+  VectorOfAny &operator=(const VectorOfAny &);
+};
+
+#ifndef FLATBUFFERS_CPP98_STL
+template<typename T, typename U>
+Vector<Offset<T>> *VectorCast(Vector<Offset<U>> *ptr) {
+  static_assert(std::is_base_of<T, U>::value, "Unrelated types");
+  return reinterpret_cast<Vector<Offset<T>> *>(ptr);
+}
+
+template<typename T, typename U>
+const Vector<Offset<T>> *VectorCast(const Vector<Offset<U>> *ptr) {
+  static_assert(std::is_base_of<T, U>::value, "Unrelated types");
+  return reinterpret_cast<const Vector<Offset<T>> *>(ptr);
+}
+#endif
+
+// Convenient helper function to get the length of any vector, regardless
+// of whether it is null or not (the field is not set).
+template<typename T> static inline size_t VectorLength(const Vector<T> *v) {
+  return v ? v->size() : 0;
+}
+
+// This is used as a helper type for accessing arrays.
+template<typename T, uint16_t length> class Array {
+  typedef
+      typename flatbuffers::integral_constant<bool,
+                                              flatbuffers::is_scalar<T>::value>
+          scalar_tag;
+  typedef
+      typename flatbuffers::conditional<scalar_tag::value, T, const T *>::type
+          IndirectHelperType;
+
+ public:
+  typedef typename IndirectHelper<IndirectHelperType>::return_type return_type;
+  typedef VectorIterator<T, return_type> const_iterator;
+  typedef VectorReverseIterator<const_iterator> const_reverse_iterator;
+
+  FLATBUFFERS_CONSTEXPR uint16_t size() const { return length; }
+
+  return_type Get(uoffset_t i) const {
+    FLATBUFFERS_ASSERT(i < size());
+    return IndirectHelper<IndirectHelperType>::Read(Data(), i);
+  }
+
+  return_type operator[](uoffset_t i) const { return Get(i); }
+
+  // If this is a Vector of enums, T will be its storage type, not the enum
+  // type. This function makes it convenient to retrieve value with enum
+  // type E.
+  template<typename E> E GetEnum(uoffset_t i) const {
+    return static_cast<E>(Get(i));
+  }
+
+  const_iterator begin() const { return const_iterator(Data(), 0); }
+  const_iterator end() const { return const_iterator(Data(), size()); }
+
+  const_reverse_iterator rbegin() const {
+    return const_reverse_iterator(end());
+  }
+  const_reverse_iterator rend() const { return const_reverse_iterator(end()); }
+
+  const_iterator cbegin() const { return begin(); }
+  const_iterator cend() const { return end(); }
+
+  const_reverse_iterator crbegin() const { return rbegin(); }
+  const_reverse_iterator crend() const { return rend(); }
+
+  // Get a mutable pointer to elements inside this array.
+  // This method used to mutate arrays of structs followed by a @p Mutate
+  // operation. For primitive types use @p Mutate directly.
+  // @warning Assignments and reads to/from the dereferenced pointer are not
+  //  automatically converted to the correct endianness.
+  typename flatbuffers::conditional<scalar_tag::value, void, T *>::type
+  GetMutablePointer(uoffset_t i) const {
+    FLATBUFFERS_ASSERT(i < size());
+    return const_cast<T *>(&data()[i]);
+  }
+
+  // Change elements if you have a non-const pointer to this object.
+  void Mutate(uoffset_t i, const T &val) { MutateImpl(scalar_tag(), i, val); }
+
+  // The raw data in little endian format. Use with care.
+  const uint8_t *Data() const { return data_; }
+
+  uint8_t *Data() { return data_; }
+
+  // Similarly, but typed, much like std::vector::data
+  const T *data() const { return reinterpret_cast<const T *>(Data()); }
+  T *data() { return reinterpret_cast<T *>(Data()); }
+
+ protected:
+  void MutateImpl(flatbuffers::integral_constant<bool, true>, uoffset_t i,
+                  const T &val) {
+    FLATBUFFERS_ASSERT(i < size());
+    WriteScalar(data() + i, val);
+  }
+
+  void MutateImpl(flatbuffers::integral_constant<bool, false>, uoffset_t i,
+                  const T &val) {
+    *(GetMutablePointer(i)) = val;
+  }
+
+  // This class is only used to access pre-existing data. Don't ever
+  // try to construct these manually.
+  // 'constexpr' allows us to use 'size()' at compile time.
+  // @note Must not use 'FLATBUFFERS_CONSTEXPR' here, as const is not allowed on
+  //  a constructor.
+#if defined(__cpp_constexpr)
+  constexpr Array();
+#else
+  Array();
+#endif
+
+  uint8_t data_[length * sizeof(T)];
+
+ private:
+  // This class is a pointer. Copying will therefore create an invalid object.
+  // Private and unimplemented copy constructor.
+  Array(const Array &);
+  Array &operator=(const Array &);
+};
+
+// Specialization for Array[struct] with access using Offset<void> pointer.
+// This specialization used by idl_gen_text.cpp.
+template<typename T, uint16_t length> class Array<Offset<T>, length> {
+  static_assert(flatbuffers::is_same<T, void>::value, "unexpected type T");
+
+ public:
+  typedef const void *return_type;
+
+  const uint8_t *Data() const { return data_; }
+
+  // Make idl_gen_text.cpp::PrintContainer happy.
+  return_type operator[](uoffset_t) const {
+    FLATBUFFERS_ASSERT(false);
+    return nullptr;
+  }
+
+ private:
+  // This class is only used to access pre-existing data.
+  Array();
+  Array(const Array &);
+  Array &operator=(const Array &);
+
+  uint8_t data_[1];
+};
+
+// Lexicographically compare two strings (possibly containing nulls), and
+// return true if the first is less than the second.
+static inline bool StringLessThan(const char *a_data, uoffset_t a_size,
+                                  const char *b_data, uoffset_t b_size) {
+  const auto cmp = memcmp(a_data, b_data, (std::min)(a_size, b_size));
+  return cmp == 0 ? a_size < b_size : cmp < 0;
+}
+
+struct String : public Vector<char> {
+  const char *c_str() const { return reinterpret_cast<const char *>(Data()); }
+  std::string str() const { return std::string(c_str(), size()); }
+
+  // clang-format off
+  #ifdef FLATBUFFERS_HAS_STRING_VIEW
+  flatbuffers::string_view string_view() const {
+    return flatbuffers::string_view(c_str(), size());
+  }
+  #endif // FLATBUFFERS_HAS_STRING_VIEW
+  // clang-format on
+
+  bool operator<(const String &o) const {
+    return StringLessThan(this->data(), this->size(), o.data(), o.size());
+  }
+};
+
+// Convenience function to get std::string from a String returning an empty
+// string on null pointer.
+static inline std::string GetString(const String *str) {
+  return str ? str->str() : "";
+}
+
+// Convenience function to get char* from a String returning an empty string on
+// null pointer.
+static inline const char *GetCstring(const String *str) {
+  return str ? str->c_str() : "";
+}
+
+// Allocator interface. This is flatbuffers-specific and meant only for
+// `vector_downward` usage.
+class Allocator {
+ public:
+  virtual ~Allocator() {}
+
+  // Allocate `size` bytes of memory.
+  virtual uint8_t *allocate(size_t size) = 0;
+
+  // Deallocate `size` bytes of memory at `p` allocated by this allocator.
+  virtual void deallocate(uint8_t *p, size_t size) = 0;
+
+  // Reallocate `new_size` bytes of memory, replacing the old region of size
+  // `old_size` at `p`. In contrast to a normal realloc, this grows downwards,
+  // and is intended specifcally for `vector_downward` use.
+  // `in_use_back` and `in_use_front` indicate how much of `old_size` is
+  // actually in use at each end, and needs to be copied.
+  virtual uint8_t *reallocate_downward(uint8_t *old_p, size_t old_size,
+                                       size_t new_size, size_t in_use_back,
+                                       size_t in_use_front) {
+    FLATBUFFERS_ASSERT(new_size > old_size);  // vector_downward only grows
+    uint8_t *new_p = allocate(new_size);
+    memcpy_downward(old_p, old_size, new_p, new_size, in_use_back,
+                    in_use_front);
+    deallocate(old_p, old_size);
+    return new_p;
+  }
+
+ protected:
+  // Called by `reallocate_downward` to copy memory from `old_p` of `old_size`
+  // to `new_p` of `new_size`. Only memory of size `in_use_front` and
+  // `in_use_back` will be copied from the front and back of the old memory
+  // allocation.
+  void memcpy_downward(uint8_t *old_p, size_t old_size, uint8_t *new_p,
+                       size_t new_size, size_t in_use_back,
+                       size_t in_use_front) {
+    memcpy(new_p + new_size - in_use_back, old_p + old_size - in_use_back,
+           in_use_back);
+    memcpy(new_p, old_p, in_use_front);
+  }
+};
+
+// DefaultAllocator uses new/delete to allocate memory regions
+class DefaultAllocator : public Allocator {
+ public:
+  uint8_t *allocate(size_t size) FLATBUFFERS_OVERRIDE {
+    return new uint8_t[size];
+  }
+
+  void deallocate(uint8_t *p, size_t) FLATBUFFERS_OVERRIDE { delete[] p; }
+
+  static void dealloc(void *p, size_t) { delete[] static_cast<uint8_t *>(p); }
+};
+
+// These functions allow for a null allocator to mean use the default allocator,
+// as used by DetachedBuffer and vector_downward below.
+// This is to avoid having a statically or dynamically allocated default
+// allocator, or having to move it between the classes that may own it.
+inline uint8_t *Allocate(Allocator *allocator, size_t size) {
+  return allocator ? allocator->allocate(size)
+                   : DefaultAllocator().allocate(size);
+}
+
+inline void Deallocate(Allocator *allocator, uint8_t *p, size_t size) {
+  if (allocator)
+    allocator->deallocate(p, size);
+  else
+    DefaultAllocator().deallocate(p, size);
+}
+
+inline uint8_t *ReallocateDownward(Allocator *allocator, uint8_t *old_p,
+                                   size_t old_size, size_t new_size,
+                                   size_t in_use_back, size_t in_use_front) {
+  return allocator ? allocator->reallocate_downward(old_p, old_size, new_size,
+                                                    in_use_back, in_use_front)
+                   : DefaultAllocator().reallocate_downward(
+                         old_p, old_size, new_size, in_use_back, in_use_front);
+}
+
+// DetachedBuffer is a finished flatbuffer memory region, detached from its
+// builder. The original memory region and allocator are also stored so that
+// the DetachedBuffer can manage the memory lifetime.
+class DetachedBuffer {
+ public:
+  DetachedBuffer()
+      : allocator_(nullptr),
+        own_allocator_(false),
+        buf_(nullptr),
+        reserved_(0),
+        cur_(nullptr),
+        size_(0) {}
+
+  DetachedBuffer(Allocator *allocator, bool own_allocator, uint8_t *buf,
+                 size_t reserved, uint8_t *cur, size_t sz)
+      : allocator_(allocator),
+        own_allocator_(own_allocator),
+        buf_(buf),
+        reserved_(reserved),
+        cur_(cur),
+        size_(sz) {}
+
+  // clang-format off
+  #if !defined(FLATBUFFERS_CPP98_STL)
+  // clang-format on
+  DetachedBuffer(DetachedBuffer &&other)
+      : allocator_(other.allocator_),
+        own_allocator_(other.own_allocator_),
+        buf_(other.buf_),
+        reserved_(other.reserved_),
+        cur_(other.cur_),
+        size_(other.size_) {
+    other.reset();
+  }
+  // clang-format off
+  #endif  // !defined(FLATBUFFERS_CPP98_STL)
+  // clang-format on
+
+  // clang-format off
+  #if !defined(FLATBUFFERS_CPP98_STL)
+  // clang-format on
+  DetachedBuffer &operator=(DetachedBuffer &&other) {
+    if (this == &other) return *this;
+
+    destroy();
+
+    allocator_ = other.allocator_;
+    own_allocator_ = other.own_allocator_;
+    buf_ = other.buf_;
+    reserved_ = other.reserved_;
+    cur_ = other.cur_;
+    size_ = other.size_;
+
+    other.reset();
+
+    return *this;
+  }
+  // clang-format off
+  #endif  // !defined(FLATBUFFERS_CPP98_STL)
+  // clang-format on
+
+  ~DetachedBuffer() { destroy(); }
+
+  const uint8_t *data() const { return cur_; }
+
+  uint8_t *data() { return cur_; }
+
+  size_t size() const { return size_; }
+
+  // clang-format off
+  #if 0  // disabled for now due to the ordering of classes in this header
+  template <class T>
+  bool Verify() const {
+    Verifier verifier(data(), size());
+    return verifier.Verify<T>(nullptr);
+  }
+
+  template <class T>
+  const T* GetRoot() const {
+    return flatbuffers::GetRoot<T>(data());
+  }
+
+  template <class T>
+  T* GetRoot() {
+    return flatbuffers::GetRoot<T>(data());
+  }
+  #endif
+  // clang-format on
+
+  // clang-format off
+  #if !defined(FLATBUFFERS_CPP98_STL)
+  // clang-format on
+  // These may change access mode, leave these at end of public section
+  FLATBUFFERS_DELETE_FUNC(DetachedBuffer(const DetachedBuffer &other))
+  FLATBUFFERS_DELETE_FUNC(
+      DetachedBuffer &operator=(const DetachedBuffer &other))
+  // clang-format off
+  #endif  // !defined(FLATBUFFERS_CPP98_STL)
+  // clang-format on
+
+ protected:
+  Allocator *allocator_;
+  bool own_allocator_;
+  uint8_t *buf_;
+  size_t reserved_;
+  uint8_t *cur_;
+  size_t size_;
+
+  inline void destroy() {
+    if (buf_) Deallocate(allocator_, buf_, reserved_);
+    if (own_allocator_ && allocator_) { delete allocator_; }
+    reset();
+  }
+
+  inline void reset() {
+    allocator_ = nullptr;
+    own_allocator_ = false;
+    buf_ = nullptr;
+    reserved_ = 0;
+    cur_ = nullptr;
+    size_ = 0;
+  }
+};
+
+// This is a minimal replication of std::vector<uint8_t> functionality,
+// except growing from higher to lower addresses. i.e push_back() inserts data
+// in the lowest address in the vector.
+// Since this vector leaves the lower part unused, we support a "scratch-pad"
+// that can be stored there for temporary data, to share the allocated space.
+// Essentially, this supports 2 std::vectors in a single buffer.
+class vector_downward {
+ public:
+  explicit vector_downward(size_t initial_size, Allocator *allocator,
+                           bool own_allocator, size_t buffer_minalign)
+      : allocator_(allocator),
+        own_allocator_(own_allocator),
+        initial_size_(initial_size),
+        buffer_minalign_(buffer_minalign),
+        reserved_(0),
+        buf_(nullptr),
+        cur_(nullptr),
+        scratch_(nullptr) {}
+
+  // clang-format off
+  #if !defined(FLATBUFFERS_CPP98_STL)
+  vector_downward(vector_downward &&other)
+  #else
+  vector_downward(vector_downward &other)
+  #endif  // defined(FLATBUFFERS_CPP98_STL)
+      // clang-format on
+      : allocator_(other.allocator_),
+        own_allocator_(other.own_allocator_),
+        initial_size_(other.initial_size_),
+        buffer_minalign_(other.buffer_minalign_),
+        reserved_(other.reserved_),
+        buf_(other.buf_),
+        cur_(other.cur_),
+        scratch_(other.scratch_) {
+    // No change in other.allocator_
+    // No change in other.initial_size_
+    // No change in other.buffer_minalign_
+    other.own_allocator_ = false;
+    other.reserved_ = 0;
+    other.buf_ = nullptr;
+    other.cur_ = nullptr;
+    other.scratch_ = nullptr;
+  }
+
+  // clang-format off
+  #if !defined(FLATBUFFERS_CPP98_STL)
+  // clang-format on
+  vector_downward &operator=(vector_downward &&other) {
+    // Move construct a temporary and swap idiom
+    vector_downward temp(std::move(other));
+    swap(temp);
+    return *this;
+  }
+  // clang-format off
+  #endif  // defined(FLATBUFFERS_CPP98_STL)
+  // clang-format on
+
+  ~vector_downward() {
+    clear_buffer();
+    clear_allocator();
+  }
+
+  void reset() {
+    clear_buffer();
+    clear();
+  }
+
+  void clear() {
+    if (buf_) {
+      cur_ = buf_ + reserved_;
+    } else {
+      reserved_ = 0;
+      cur_ = nullptr;
+    }
+    clear_scratch();
+  }
+
+  void clear_scratch() { scratch_ = buf_; }
+
+  void clear_allocator() {
+    if (own_allocator_ && allocator_) { delete allocator_; }
+    allocator_ = nullptr;
+    own_allocator_ = false;
+  }
+
+  void clear_buffer() {
+    if (buf_) Deallocate(allocator_, buf_, reserved_);
+    buf_ = nullptr;
+  }
+
+  // Relinquish the pointer to the caller.
+  uint8_t *release_raw(size_t &allocated_bytes, size_t &offset) {
+    auto *buf = buf_;
+    allocated_bytes = reserved_;
+    offset = static_cast<size_t>(cur_ - buf_);
+
+    // release_raw only relinquishes the buffer ownership.
+    // Does not deallocate or reset the allocator. Destructor will do that.
+    buf_ = nullptr;
+    clear();
+    return buf;
+  }
+
+  // Relinquish the pointer to the caller.
+  DetachedBuffer release() {
+    // allocator ownership (if any) is transferred to DetachedBuffer.
+    DetachedBuffer fb(allocator_, own_allocator_, buf_, reserved_, cur_,
+                      size());
+    if (own_allocator_) {
+      allocator_ = nullptr;
+      own_allocator_ = false;
+    }
+    buf_ = nullptr;
+    clear();
+    return fb;
+  }
+
+  size_t ensure_space(size_t len) {
+    FLATBUFFERS_ASSERT(cur_ >= scratch_ && scratch_ >= buf_);
+    if (len > static_cast<size_t>(cur_ - scratch_)) { reallocate(len); }
+    // Beyond this, signed offsets may not have enough range:
+    // (FlatBuffers > 2GB not supported).
+    FLATBUFFERS_ASSERT(size() < FLATBUFFERS_MAX_BUFFER_SIZE);
+    return len;
+  }
+
+  inline uint8_t *make_space(size_t len) {
+    size_t space = ensure_space(len);
+    cur_ -= space;
+    return cur_;
+  }
+
+  // Returns nullptr if using the DefaultAllocator.
+  Allocator *get_custom_allocator() { return allocator_; }
+
+  uoffset_t size() const {
+    return static_cast<uoffset_t>(reserved_ - (cur_ - buf_));
+  }
+
+  uoffset_t scratch_size() const {
+    return static_cast<uoffset_t>(scratch_ - buf_);
+  }
+
+  size_t capacity() const { return reserved_; }
+
+  uint8_t *data() const {
+    FLATBUFFERS_ASSERT(cur_);
+    return cur_;
+  }
+
+  uint8_t *scratch_data() const {
+    FLATBUFFERS_ASSERT(buf_);
+    return buf_;
+  }
+
+  uint8_t *scratch_end() const {
+    FLATBUFFERS_ASSERT(scratch_);
+    return scratch_;
+  }
+
+  uint8_t *data_at(size_t offset) const { return buf_ + reserved_ - offset; }
+
+  void push(const uint8_t *bytes, size_t num) {
+    if (num > 0) { memcpy(make_space(num), bytes, num); }
+  }
+
+  // Specialized version of push() that avoids memcpy call for small data.
+  template<typename T> void push_small(const T &little_endian_t) {
+    make_space(sizeof(T));
+    *reinterpret_cast<T *>(cur_) = little_endian_t;
+  }
+
+  template<typename T> void scratch_push_small(const T &t) {
+    ensure_space(sizeof(T));
+    *reinterpret_cast<T *>(scratch_) = t;
+    scratch_ += sizeof(T);
+  }
+
+  // fill() is most frequently called with small byte counts (<= 4),
+  // which is why we're using loops rather than calling memset.
+  void fill(size_t zero_pad_bytes) {
+    make_space(zero_pad_bytes);
+    for (size_t i = 0; i < zero_pad_bytes; i++) cur_[i] = 0;
+  }
+
+  // Version for when we know the size is larger.
+  // Precondition: zero_pad_bytes > 0
+  void fill_big(size_t zero_pad_bytes) {
+    memset(make_space(zero_pad_bytes), 0, zero_pad_bytes);
+  }
+
+  void pop(size_t bytes_to_remove) { cur_ += bytes_to_remove; }
+  void scratch_pop(size_t bytes_to_remove) { scratch_ -= bytes_to_remove; }
+
+  void swap(vector_downward &other) {
+    using std::swap;
+    swap(allocator_, other.allocator_);
+    swap(own_allocator_, other.own_allocator_);
+    swap(initial_size_, other.initial_size_);
+    swap(buffer_minalign_, other.buffer_minalign_);
+    swap(reserved_, other.reserved_);
+    swap(buf_, other.buf_);
+    swap(cur_, other.cur_);
+    swap(scratch_, other.scratch_);
+  }
+
+  void swap_allocator(vector_downward &other) {
+    using std::swap;
+    swap(allocator_, other.allocator_);
+    swap(own_allocator_, other.own_allocator_);
+  }
+
+ private:
+  // You shouldn't really be copying instances of this class.
+  FLATBUFFERS_DELETE_FUNC(vector_downward(const vector_downward &))
+  FLATBUFFERS_DELETE_FUNC(vector_downward &operator=(const vector_downward &))
+
+  Allocator *allocator_;
+  bool own_allocator_;
+  size_t initial_size_;
+  size_t buffer_minalign_;
+  size_t reserved_;
+  uint8_t *buf_;
+  uint8_t *cur_;  // Points at location between empty (below) and used (above).
+  uint8_t *scratch_;  // Points to the end of the scratchpad in use.
+
+  void reallocate(size_t len) {
+    auto old_reserved = reserved_;
+    auto old_size = size();
+    auto old_scratch_size = scratch_size();
+    reserved_ +=
+        (std::max)(len, old_reserved ? old_reserved / 2 : initial_size_);
+    reserved_ = (reserved_ + buffer_minalign_ - 1) & ~(buffer_minalign_ - 1);
+    if (buf_) {
+      buf_ = ReallocateDownward(allocator_, buf_, old_reserved, reserved_,
+                                old_size, old_scratch_size);
+    } else {
+      buf_ = Allocate(allocator_, reserved_);
+    }
+    cur_ = buf_ + reserved_ - old_size;
+    scratch_ = buf_ + old_scratch_size;
+  }
+};
+
+// Converts a Field ID to a virtual table offset.
+inline voffset_t FieldIndexToOffset(voffset_t field_id) {
+  // Should correspond to what EndTable() below builds up.
+  const int fixed_fields = 2;  // Vtable size and Object Size.
+  return static_cast<voffset_t>((field_id + fixed_fields) * sizeof(voffset_t));
+}
+
+template<typename T, typename Alloc>
+const T *data(const std::vector<T, Alloc> &v) {
+  // Eventually the returned pointer gets passed down to memcpy, so
+  // we need it to be non-null to avoid undefined behavior.
+  static uint8_t t;
+  return v.empty() ? reinterpret_cast<const T *>(&t) : &v.front();
+}
+template<typename T, typename Alloc> T *data(std::vector<T, Alloc> &v) {
+  // Eventually the returned pointer gets passed down to memcpy, so
+  // we need it to be non-null to avoid undefined behavior.
+  static uint8_t t;
+  return v.empty() ? reinterpret_cast<T *>(&t) : &v.front();
+}
+
+/// @endcond
+
+/// @addtogroup flatbuffers_cpp_api
+/// @{
+/// @class FlatBufferBuilder
+/// @brief Helper class to hold data needed in creation of a FlatBuffer.
+/// To serialize data, you typically call one of the `Create*()` functions in
+/// the generated code, which in turn call a sequence of `StartTable`/
+/// `PushElement`/`AddElement`/`EndTable`, or the builtin `CreateString`/
+/// `CreateVector` functions. Do this is depth-first order to build up a tree to
+/// the root. `Finish()` wraps up the buffer ready for transport.
+class FlatBufferBuilder {
+ public:
+  /// @brief Default constructor for FlatBufferBuilder.
+  /// @param[in] initial_size The initial size of the buffer, in bytes. Defaults
+  /// to `1024`.
+  /// @param[in] allocator An `Allocator` to use. If null will use
+  /// `DefaultAllocator`.
+  /// @param[in] own_allocator Whether the builder/vector should own the
+  /// allocator. Defaults to / `false`.
+  /// @param[in] buffer_minalign Force the buffer to be aligned to the given
+  /// minimum alignment upon reallocation. Only needed if you intend to store
+  /// types with custom alignment AND you wish to read the buffer in-place
+  /// directly after creation.
+  explicit FlatBufferBuilder(
+      size_t initial_size = 1024, Allocator *allocator = nullptr,
+      bool own_allocator = false,
+      size_t buffer_minalign = AlignOf<largest_scalar_t>())
+      : buf_(initial_size, allocator, own_allocator, buffer_minalign),
+        num_field_loc(0),
+        max_voffset_(0),
+        nested(false),
+        finished(false),
+        minalign_(1),
+        force_defaults_(false),
+        dedup_vtables_(true),
+        string_pool(nullptr) {
+    EndianCheck();
+  }
+
+  // clang-format off
+  /// @brief Move constructor for FlatBufferBuilder.
+  #if !defined(FLATBUFFERS_CPP98_STL)
+  FlatBufferBuilder(FlatBufferBuilder &&other)
+  #else
+  FlatBufferBuilder(FlatBufferBuilder &other)
+  #endif  // #if !defined(FLATBUFFERS_CPP98_STL)
+    : buf_(1024, nullptr, false, AlignOf<largest_scalar_t>()),
+      num_field_loc(0),
+      max_voffset_(0),
+      nested(false),
+      finished(false),
+      minalign_(1),
+      force_defaults_(false),
+      dedup_vtables_(true),
+      string_pool(nullptr) {
+    EndianCheck();
+    // Default construct and swap idiom.
+    // Lack of delegating constructors in vs2010 makes it more verbose than needed.
+    Swap(other);
+  }
+  // clang-format on
+
+  // clang-format off
+  #if !defined(FLATBUFFERS_CPP98_STL)
+  // clang-format on
+  /// @brief Move assignment operator for FlatBufferBuilder.
+  FlatBufferBuilder &operator=(FlatBufferBuilder &&other) {
+    // Move construct a temporary and swap idiom
+    FlatBufferBuilder temp(std::move(other));
+    Swap(temp);
+    return *this;
+  }
+  // clang-format off
+  #endif  // defined(FLATBUFFERS_CPP98_STL)
+  // clang-format on
+
+  void Swap(FlatBufferBuilder &other) {
+    using std::swap;
+    buf_.swap(other.buf_);
+    swap(num_field_loc, other.num_field_loc);
+    swap(max_voffset_, other.max_voffset_);
+    swap(nested, other.nested);
+    swap(finished, other.finished);
+    swap(minalign_, other.minalign_);
+    swap(force_defaults_, other.force_defaults_);
+    swap(dedup_vtables_, other.dedup_vtables_);
+    swap(string_pool, other.string_pool);
+  }
+
+  ~FlatBufferBuilder() {
+    if (string_pool) delete string_pool;
+  }
+
+  void Reset() {
+    Clear();       // clear builder state
+    buf_.reset();  // deallocate buffer
+  }
+
+  /// @brief Reset all the state in this FlatBufferBuilder so it can be reused
+  /// to construct another buffer.
+  void Clear() {
+    ClearOffsets();
+    buf_.clear();
+    nested = false;
+    finished = false;
+    minalign_ = 1;
+    if (string_pool) string_pool->clear();
+  }
+
+  /// @brief The current size of the serialized buffer, counting from the end.
+  /// @return Returns an `uoffset_t` with the current size of the buffer.
+  uoffset_t GetSize() const { return buf_.size(); }
+
+  /// @brief Get the serialized buffer (after you call `Finish()`).
+  /// @return Returns an `uint8_t` pointer to the FlatBuffer data inside the
+  /// buffer.
+  uint8_t *GetBufferPointer() const {
+    Finished();
+    return buf_.data();
+  }
+
+  /// @brief Get a pointer to an unfinished buffer.
+  /// @return Returns a `uint8_t` pointer to the unfinished buffer.
+  uint8_t *GetCurrentBufferPointer() const { return buf_.data(); }
+
+  /// @brief Get the released pointer to the serialized buffer.
+  /// @warning Do NOT attempt to use this FlatBufferBuilder afterwards!
+  /// @return A `FlatBuffer` that owns the buffer and its allocator and
+  /// behaves similar to a `unique_ptr` with a deleter.
+  FLATBUFFERS_ATTRIBUTE(deprecated("use Release() instead"))
+  DetachedBuffer ReleaseBufferPointer() {
+    Finished();
+    return buf_.release();
+  }
+
+  /// @brief Get the released DetachedBuffer.
+  /// @return A `DetachedBuffer` that owns the buffer and its allocator.
+  DetachedBuffer Release() {
+    Finished();
+    return buf_.release();
+  }
+
+  /// @brief Get the released pointer to the serialized buffer.
+  /// @param size The size of the memory block containing
+  /// the serialized `FlatBuffer`.
+  /// @param offset The offset from the released pointer where the finished
+  /// `FlatBuffer` starts.
+  /// @return A raw pointer to the start of the memory block containing
+  /// the serialized `FlatBuffer`.
+  /// @remark If the allocator is owned, it gets deleted when the destructor is
+  /// called..
+  uint8_t *ReleaseRaw(size_t &size, size_t &offset) {
+    Finished();
+    return buf_.release_raw(size, offset);
+  }
+
+  /// @brief get the minimum alignment this buffer needs to be accessed
+  /// properly. This is only known once all elements have been written (after
+  /// you call Finish()). You can use this information if you need to embed
+  /// a FlatBuffer in some other buffer, such that you can later read it
+  /// without first having to copy it into its own buffer.
+  size_t GetBufferMinAlignment() {
+    Finished();
+    return minalign_;
+  }
+
+  /// @cond FLATBUFFERS_INTERNAL
+  void Finished() const {
+    // If you get this assert, you're attempting to get access a buffer
+    // which hasn't been finished yet. Be sure to call
+    // FlatBufferBuilder::Finish with your root table.
+    // If you really need to access an unfinished buffer, call
+    // GetCurrentBufferPointer instead.
+    FLATBUFFERS_ASSERT(finished);
+  }
+  /// @endcond
+
+  /// @brief In order to save space, fields that are set to their default value
+  /// don't get serialized into the buffer.
+  /// @param[in] fd When set to `true`, always serializes default values that
+  /// are set. Optional fields which are not set explicitly, will still not be
+  /// serialized.
+  void ForceDefaults(bool fd) { force_defaults_ = fd; }
+
+  /// @brief By default vtables are deduped in order to save space.
+  /// @param[in] dedup When set to `true`, dedup vtables.
+  void DedupVtables(bool dedup) { dedup_vtables_ = dedup; }
+
+  /// @cond FLATBUFFERS_INTERNAL
+  void Pad(size_t num_bytes) { buf_.fill(num_bytes); }
+
+  void TrackMinAlign(size_t elem_size) {
+    if (elem_size > minalign_) minalign_ = elem_size;
+  }
+
+  void Align(size_t elem_size) {
+    TrackMinAlign(elem_size);
+    buf_.fill(PaddingBytes(buf_.size(), elem_size));
+  }
+
+  void PushFlatBuffer(const uint8_t *bytes, size_t size) {
+    PushBytes(bytes, size);
+    finished = true;
+  }
+
+  void PushBytes(const uint8_t *bytes, size_t size) { buf_.push(bytes, size); }
+
+  void PopBytes(size_t amount) { buf_.pop(amount); }
+
+  template<typename T> void AssertScalarT() {
+    // The code assumes power of 2 sizes and endian-swap-ability.
+    static_assert(flatbuffers::is_scalar<T>::value, "T must be a scalar type");
+  }
+
+  // Write a single aligned scalar to the buffer
+  template<typename T> uoffset_t PushElement(T element) {
+    AssertScalarT<T>();
+    T litle_endian_element = EndianScalar(element);
+    Align(sizeof(T));
+    buf_.push_small(litle_endian_element);
+    return GetSize();
+  }
+
+  template<typename T> uoffset_t PushElement(Offset<T> off) {
+    // Special case for offsets: see ReferTo below.
+    return PushElement(ReferTo(off.o));
+  }
+
+  // When writing fields, we track where they are, so we can create correct
+  // vtables later.
+  void TrackField(voffset_t field, uoffset_t off) {
+    FieldLoc fl = { off, field };
+    buf_.scratch_push_small(fl);
+    num_field_loc++;
+    max_voffset_ = (std::max)(max_voffset_, field);
+  }
+
+  // Like PushElement, but additionally tracks the field this represents.
+  template<typename T> void AddElement(voffset_t field, T e, T def) {
+    // We don't serialize values equal to the default.
+    if (IsTheSameAs(e, def) && !force_defaults_) return;
+    auto off = PushElement(e);
+    TrackField(field, off);
+  }
+
+  template<typename T> void AddOffset(voffset_t field, Offset<T> off) {
+    if (off.IsNull()) return;  // Don't store.
+    AddElement(field, ReferTo(off.o), static_cast<uoffset_t>(0));
+  }
+
+  template<typename T> void AddStruct(voffset_t field, const T *structptr) {
+    if (!structptr) return;  // Default, don't store.
+    Align(AlignOf<T>());
+    buf_.push_small(*structptr);
+    TrackField(field, GetSize());
+  }
+
+  void AddStructOffset(voffset_t field, uoffset_t off) {
+    TrackField(field, off);
+  }
+
+  // Offsets initially are relative to the end of the buffer (downwards).
+  // This function converts them to be relative to the current location
+  // in the buffer (when stored here), pointing upwards.
+  uoffset_t ReferTo(uoffset_t off) {
+    // Align to ensure GetSize() below is correct.
+    Align(sizeof(uoffset_t));
+    // Offset must refer to something already in buffer.
+    FLATBUFFERS_ASSERT(off && off <= GetSize());
+    return GetSize() - off + static_cast<uoffset_t>(sizeof(uoffset_t));
+  }
+
+  void NotNested() {
+    // If you hit this, you're trying to construct a Table/Vector/String
+    // during the construction of its parent table (between the MyTableBuilder
+    // and table.Finish().
+    // Move the creation of these sub-objects to above the MyTableBuilder to
+    // not get this assert.
+    // Ignoring this assert may appear to work in simple cases, but the reason
+    // it is here is that storing objects in-line may cause vtable offsets
+    // to not fit anymore. It also leads to vtable duplication.
+    FLATBUFFERS_ASSERT(!nested);
+    // If you hit this, fields were added outside the scope of a table.
+    FLATBUFFERS_ASSERT(!num_field_loc);
+  }
+
+  // From generated code (or from the parser), we call StartTable/EndTable
+  // with a sequence of AddElement calls in between.
+  uoffset_t StartTable() {
+    NotNested();
+    nested = true;
+    return GetSize();
+  }
+
+  // This finishes one serialized object by generating the vtable if it's a
+  // table, comparing it against existing vtables, and writing the
+  // resulting vtable offset.
+  uoffset_t EndTable(uoffset_t start) {
+    // If you get this assert, a corresponding StartTable wasn't called.
+    FLATBUFFERS_ASSERT(nested);
+    // Write the vtable offset, which is the start of any Table.
+    // We fill it's value later.
+    auto vtableoffsetloc = PushElement<soffset_t>(0);
+    // Write a vtable, which consists entirely of voffset_t elements.
+    // It starts with the number of offsets, followed by a type id, followed
+    // by the offsets themselves. In reverse:
+    // Include space for the last offset and ensure empty tables have a
+    // minimum size.
+    max_voffset_ =
+        (std::max)(static_cast<voffset_t>(max_voffset_ + sizeof(voffset_t)),
+                   FieldIndexToOffset(0));
+    buf_.fill_big(max_voffset_);
+    auto table_object_size = vtableoffsetloc - start;
+    // Vtable use 16bit offsets.
+    FLATBUFFERS_ASSERT(table_object_size < 0x10000);
+    WriteScalar<voffset_t>(buf_.data() + sizeof(voffset_t),
+                           static_cast<voffset_t>(table_object_size));
+    WriteScalar<voffset_t>(buf_.data(), max_voffset_);
+    // Write the offsets into the table
+    for (auto it = buf_.scratch_end() - num_field_loc * sizeof(FieldLoc);
+         it < buf_.scratch_end(); it += sizeof(FieldLoc)) {
+      auto field_location = reinterpret_cast<FieldLoc *>(it);
+      auto pos = static_cast<voffset_t>(vtableoffsetloc - field_location->off);
+      // If this asserts, it means you've set a field twice.
+      FLATBUFFERS_ASSERT(
+          !ReadScalar<voffset_t>(buf_.data() + field_location->id));
+      WriteScalar<voffset_t>(buf_.data() + field_location->id, pos);
+    }
+    ClearOffsets();
+    auto vt1 = reinterpret_cast<voffset_t *>(buf_.data());
+    auto vt1_size = ReadScalar<voffset_t>(vt1);
+    auto vt_use = GetSize();
+    // See if we already have generated a vtable with this exact same
+    // layout before. If so, make it point to the old one, remove this one.
+    if (dedup_vtables_) {
+      for (auto it = buf_.scratch_data(); it < buf_.scratch_end();
+           it += sizeof(uoffset_t)) {
+        auto vt_offset_ptr = reinterpret_cast<uoffset_t *>(it);
+        auto vt2 = reinterpret_cast<voffset_t *>(buf_.data_at(*vt_offset_ptr));
+        auto vt2_size = ReadScalar<voffset_t>(vt2);
+        if (vt1_size != vt2_size || 0 != memcmp(vt2, vt1, vt1_size)) continue;
+        vt_use = *vt_offset_ptr;
+        buf_.pop(GetSize() - vtableoffsetloc);
+        break;
+      }
+    }
+    // If this is a new vtable, remember it.
+    if (vt_use == GetSize()) { buf_.scratch_push_small(vt_use); }
+    // Fill the vtable offset we created above.
+    // The offset points from the beginning of the object to where the
+    // vtable is stored.
+    // Offsets default direction is downward in memory for future format
+    // flexibility (storing all vtables at the start of the file).
+    WriteScalar(buf_.data_at(vtableoffsetloc),
+                static_cast<soffset_t>(vt_use) -
+                    static_cast<soffset_t>(vtableoffsetloc));
+
+    nested = false;
+    return vtableoffsetloc;
+  }
+
+  FLATBUFFERS_ATTRIBUTE(deprecated("call the version above instead"))
+  uoffset_t EndTable(uoffset_t start, voffset_t /*numfields*/) {
+    return EndTable(start);
+  }
+
+  // This checks a required field has been set in a given table that has
+  // just been constructed.
+  template<typename T> void Required(Offset<T> table, voffset_t field);
+
+  uoffset_t StartStruct(size_t alignment) {
+    Align(alignment);
+    return GetSize();
+  }
+
+  uoffset_t EndStruct() { return GetSize(); }
+
+  void ClearOffsets() {
+    buf_.scratch_pop(num_field_loc * sizeof(FieldLoc));
+    num_field_loc = 0;
+    max_voffset_ = 0;
+  }
+
+  // Aligns such that when "len" bytes are written, an object can be written
+  // after it with "alignment" without padding.
+  void PreAlign(size_t len, size_t alignment) {
+    TrackMinAlign(alignment);
+    buf_.fill(PaddingBytes(GetSize() + len, alignment));
+  }
+  template<typename T> void PreAlign(size_t len) {
+    AssertScalarT<T>();
+    PreAlign(len, sizeof(T));
+  }
+  /// @endcond
+
+  /// @brief Store a string in the buffer, which can contain any binary data.
+  /// @param[in] str A const char pointer to the data to be stored as a string.
+  /// @param[in] len The number of bytes that should be stored from `str`.
+  /// @return Returns the offset in the buffer where the string starts.
+  Offset<String> CreateString(const char *str, size_t len) {
+    NotNested();
+    PreAlign<uoffset_t>(len + 1);  // Always 0-terminated.
+    buf_.fill(1);
+    PushBytes(reinterpret_cast<const uint8_t *>(str), len);
+    PushElement(static_cast<uoffset_t>(len));
+    return Offset<String>(GetSize());
+  }
+
+  /// @brief Store a string in the buffer, which is null-terminated.
+  /// @param[in] str A const char pointer to a C-string to add to the buffer.
+  /// @return Returns the offset in the buffer where the string starts.
+  Offset<String> CreateString(const char *str) {
+    return CreateString(str, strlen(str));
+  }
+
+  /// @brief Store a string in the buffer, which is null-terminated.
+  /// @param[in] str A char pointer to a C-string to add to the buffer.
+  /// @return Returns the offset in the buffer where the string starts.
+  Offset<String> CreateString(char *str) {
+    return CreateString(str, strlen(str));
+  }
+
+  /// @brief Store a string in the buffer, which can contain any binary data.
+  /// @param[in] str A const reference to a std::string to store in the buffer.
+  /// @return Returns the offset in the buffer where the string starts.
+  Offset<String> CreateString(const std::string &str) {
+    return CreateString(str.c_str(), str.length());
+  }
+
+  // clang-format off
+  #ifdef FLATBUFFERS_HAS_STRING_VIEW
+  /// @brief Store a string in the buffer, which can contain any binary data.
+  /// @param[in] str A const string_view to copy in to the buffer.
+  /// @return Returns the offset in the buffer where the string starts.
+  Offset<String> CreateString(flatbuffers::string_view str) {
+    return CreateString(str.data(), str.size());
+  }
+  #endif // FLATBUFFERS_HAS_STRING_VIEW
+  // clang-format on
+
+  /// @brief Store a string in the buffer, which can contain any binary data.
+  /// @param[in] str A const pointer to a `String` struct to add to the buffer.
+  /// @return Returns the offset in the buffer where the string starts
+  Offset<String> CreateString(const String *str) {
+    return str ? CreateString(str->c_str(), str->size()) : 0;
+  }
+
+  /// @brief Store a string in the buffer, which can contain any binary data.
+  /// @param[in] str A const reference to a std::string like type with support
+  /// of T::c_str() and T::length() to store in the buffer.
+  /// @return Returns the offset in the buffer where the string starts.
+  template<typename T> Offset<String> CreateString(const T &str) {
+    return CreateString(str.c_str(), str.length());
+  }
+
+  /// @brief Store a string in the buffer, which can contain any binary data.
+  /// If a string with this exact contents has already been serialized before,
+  /// instead simply returns the offset of the existing string.
+  /// @param[in] str A const char pointer to the data to be stored as a string.
+  /// @param[in] len The number of bytes that should be stored from `str`.
+  /// @return Returns the offset in the buffer where the string starts.
+  Offset<String> CreateSharedString(const char *str, size_t len) {
+    if (!string_pool)
+      string_pool = new StringOffsetMap(StringOffsetCompare(buf_));
+    auto size_before_string = buf_.size();
+    // Must first serialize the string, since the set is all offsets into
+    // buffer.
+    auto off = CreateString(str, len);
+    auto it = string_pool->find(off);
+    // If it exists we reuse existing serialized data!
+    if (it != string_pool->end()) {
+      // We can remove the string we serialized.
+      buf_.pop(buf_.size() - size_before_string);
+      return *it;
+    }
+    // Record this string for future use.
+    string_pool->insert(off);
+    return off;
+  }
+
+  /// @brief Store a string in the buffer, which null-terminated.
+  /// If a string with this exact contents has already been serialized before,
+  /// instead simply returns the offset of the existing string.
+  /// @param[in] str A const char pointer to a C-string to add to the buffer.
+  /// @return Returns the offset in the buffer where the string starts.
+  Offset<String> CreateSharedString(const char *str) {
+    return CreateSharedString(str, strlen(str));
+  }
+
+  /// @brief Store a string in the buffer, which can contain any binary data.
+  /// If a string with this exact contents has already been serialized before,
+  /// instead simply returns the offset of the existing string.
+  /// @param[in] str A const reference to a std::string to store in the buffer.
+  /// @return Returns the offset in the buffer where the string starts.
+  Offset<String> CreateSharedString(const std::string &str) {
+    return CreateSharedString(str.c_str(), str.length());
+  }
+
+  /// @brief Store a string in the buffer, which can contain any binary data.
+  /// If a string with this exact contents has already been serialized before,
+  /// instead simply returns the offset of the existing string.
+  /// @param[in] str A const pointer to a `String` struct to add to the buffer.
+  /// @return Returns the offset in the buffer where the string starts
+  Offset<String> CreateSharedString(const String *str) {
+    return CreateSharedString(str->c_str(), str->size());
+  }
+
+  /// @cond FLATBUFFERS_INTERNAL
+  uoffset_t EndVector(size_t len) {
+    FLATBUFFERS_ASSERT(nested);  // Hit if no corresponding StartVector.
+    nested = false;
+    return PushElement(static_cast<uoffset_t>(len));
+  }
+
+  void StartVector(size_t len, size_t elemsize) {
+    NotNested();
+    nested = true;
+    PreAlign<uoffset_t>(len * elemsize);
+    PreAlign(len * elemsize, elemsize);  // Just in case elemsize > uoffset_t.
+  }
+
+  // Call this right before StartVector/CreateVector if you want to force the
+  // alignment to be something different than what the element size would
+  // normally dictate.
+  // This is useful when storing a nested_flatbuffer in a vector of bytes,
+  // or when storing SIMD floats, etc.
+  void ForceVectorAlignment(size_t len, size_t elemsize, size_t alignment) {
+    PreAlign(len * elemsize, alignment);
+  }
+
+  // Similar to ForceVectorAlignment but for String fields.
+  void ForceStringAlignment(size_t len, size_t alignment) {
+    PreAlign((len + 1) * sizeof(char), alignment);
+  }
+
+  /// @endcond
+
+  /// @brief Serialize an array into a FlatBuffer `vector`.
+  /// @tparam T The data type of the array elements.
+  /// @param[in] v A pointer to the array of type `T` to serialize into the
+  /// buffer as a `vector`.
+  /// @param[in] len The number of elements to serialize.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T> Offset<Vector<T>> CreateVector(const T *v, size_t len) {
+    // If this assert hits, you're specifying a template argument that is
+    // causing the wrong overload to be selected, remove it.
+    AssertScalarT<T>();
+    StartVector(len, sizeof(T));
+    // clang-format off
+    #if FLATBUFFERS_LITTLEENDIAN
+      PushBytes(reinterpret_cast<const uint8_t *>(v), len * sizeof(T));
+    #else
+      if (sizeof(T) == 1) {
+        PushBytes(reinterpret_cast<const uint8_t *>(v), len);
+      } else {
+        for (auto i = len; i > 0; ) {
+          PushElement(v[--i]);
+        }
+      }
+    #endif
+    // clang-format on
+    return Offset<Vector<T>>(EndVector(len));
+  }
+
+  template<typename T>
+  Offset<Vector<Offset<T>>> CreateVector(const Offset<T> *v, size_t len) {
+    StartVector(len, sizeof(Offset<T>));
+    for (auto i = len; i > 0;) { PushElement(v[--i]); }
+    return Offset<Vector<Offset<T>>>(EndVector(len));
+  }
+
+  /// @brief Serialize a `std::vector` into a FlatBuffer `vector`.
+  /// @tparam T The data type of the `std::vector` elements.
+  /// @param v A const reference to the `std::vector` to serialize into the
+  /// buffer as a `vector`.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T> Offset<Vector<T>> CreateVector(const std::vector<T> &v) {
+    return CreateVector(data(v), v.size());
+  }
+
+  // vector<bool> may be implemented using a bit-set, so we can't access it as
+  // an array. Instead, read elements manually.
+  // Background: https://isocpp.org/blog/2012/11/on-vectorbool
+  Offset<Vector<uint8_t>> CreateVector(const std::vector<bool> &v) {
+    StartVector(v.size(), sizeof(uint8_t));
+    for (auto i = v.size(); i > 0;) {
+      PushElement(static_cast<uint8_t>(v[--i]));
+    }
+    return Offset<Vector<uint8_t>>(EndVector(v.size()));
+  }
+
+  // clang-format off
+  #ifndef FLATBUFFERS_CPP98_STL
+  /// @brief Serialize values returned by a function into a FlatBuffer `vector`.
+  /// This is a convenience function that takes care of iteration for you.
+  /// @tparam T The data type of the `std::vector` elements.
+  /// @param f A function that takes the current iteration 0..vector_size-1 and
+  /// returns any type that you can construct a FlatBuffers vector out of.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T> Offset<Vector<T>> CreateVector(size_t vector_size,
+      const std::function<T (size_t i)> &f) {
+    std::vector<T> elems(vector_size);
+    for (size_t i = 0; i < vector_size; i++) elems[i] = f(i);
+    return CreateVector(elems);
+  }
+  #endif
+  // clang-format on
+
+  /// @brief Serialize values returned by a function into a FlatBuffer `vector`.
+  /// This is a convenience function that takes care of iteration for you.
+  /// @tparam T The data type of the `std::vector` elements.
+  /// @param f A function that takes the current iteration 0..vector_size-1,
+  /// and the state parameter returning any type that you can construct a
+  /// FlatBuffers vector out of.
+  /// @param state State passed to f.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T, typename F, typename S>
+  Offset<Vector<T>> CreateVector(size_t vector_size, F f, S *state) {
+    std::vector<T> elems(vector_size);
+    for (size_t i = 0; i < vector_size; i++) elems[i] = f(i, state);
+    return CreateVector(elems);
+  }
+
+  /// @brief Serialize a `std::vector<std::string>` into a FlatBuffer `vector`.
+  /// This is a convenience function for a common case.
+  /// @param v A const reference to the `std::vector` to serialize into the
+  /// buffer as a `vector`.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  Offset<Vector<Offset<String>>> CreateVectorOfStrings(
+      const std::vector<std::string> &v) {
+    std::vector<Offset<String>> offsets(v.size());
+    for (size_t i = 0; i < v.size(); i++) offsets[i] = CreateString(v[i]);
+    return CreateVector(offsets);
+  }
+
+  /// @brief Serialize an array of structs into a FlatBuffer `vector`.
+  /// @tparam T The data type of the struct array elements.
+  /// @param[in] v A pointer to the array of type `T` to serialize into the
+  /// buffer as a `vector`.
+  /// @param[in] len The number of elements to serialize.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T>
+  Offset<Vector<const T *>> CreateVectorOfStructs(const T *v, size_t len) {
+    StartVector(len * sizeof(T) / AlignOf<T>(), AlignOf<T>());
+    PushBytes(reinterpret_cast<const uint8_t *>(v), sizeof(T) * len);
+    return Offset<Vector<const T *>>(EndVector(len));
+  }
+
+  /// @brief Serialize an array of native structs into a FlatBuffer `vector`.
+  /// @tparam T The data type of the struct array elements.
+  /// @tparam S The data type of the native struct array elements.
+  /// @param[in] v A pointer to the array of type `S` to serialize into the
+  /// buffer as a `vector`.
+  /// @param[in] len The number of elements to serialize.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T, typename S>
+  Offset<Vector<const T *>> CreateVectorOfNativeStructs(const S *v,
+                                                        size_t len) {
+    extern T Pack(const S &);
+    std::vector<T> vv(len);
+    std::transform(v, v + len, vv.begin(), Pack);
+    return CreateVectorOfStructs<T>(data(vv), vv.size());
+  }
+
+  // clang-format off
+  #ifndef FLATBUFFERS_CPP98_STL
+  /// @brief Serialize an array of structs into a FlatBuffer `vector`.
+  /// @tparam T The data type of the struct array elements.
+  /// @param[in] filler A function that takes the current iteration 0..vector_size-1
+  /// and a pointer to the struct that must be filled.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  /// This is mostly useful when flatbuffers are generated with mutation
+  /// accessors.
+  template<typename T> Offset<Vector<const T *>> CreateVectorOfStructs(
+      size_t vector_size, const std::function<void(size_t i, T *)> &filler) {
+    T* structs = StartVectorOfStructs<T>(vector_size);
+    for (size_t i = 0; i < vector_size; i++) {
+      filler(i, structs);
+      structs++;
+    }
+    return EndVectorOfStructs<T>(vector_size);
+  }
+  #endif
+  // clang-format on
+
+  /// @brief Serialize an array of structs into a FlatBuffer `vector`.
+  /// @tparam T The data type of the struct array elements.
+  /// @param[in] f A function that takes the current iteration 0..vector_size-1,
+  /// a pointer to the struct that must be filled and the state argument.
+  /// @param[in] state Arbitrary state to pass to f.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  /// This is mostly useful when flatbuffers are generated with mutation
+  /// accessors.
+  template<typename T, typename F, typename S>
+  Offset<Vector<const T *>> CreateVectorOfStructs(size_t vector_size, F f,
+                                                  S *state) {
+    T *structs = StartVectorOfStructs<T>(vector_size);
+    for (size_t i = 0; i < vector_size; i++) {
+      f(i, structs, state);
+      structs++;
+    }
+    return EndVectorOfStructs<T>(vector_size);
+  }
+
+  /// @brief Serialize a `std::vector` of structs into a FlatBuffer `vector`.
+  /// @tparam T The data type of the `std::vector` struct elements.
+  /// @param[in] v A const reference to the `std::vector` of structs to
+  /// serialize into the buffer as a `vector`.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T, typename Alloc>
+  Offset<Vector<const T *>> CreateVectorOfStructs(
+      const std::vector<T, Alloc> &v) {
+    return CreateVectorOfStructs(data(v), v.size());
+  }
+
+  /// @brief Serialize a `std::vector` of native structs into a FlatBuffer
+  /// `vector`.
+  /// @tparam T The data type of the `std::vector` struct elements.
+  /// @tparam S The data type of the `std::vector` native struct elements.
+  /// @param[in] v A const reference to the `std::vector` of structs to
+  /// serialize into the buffer as a `vector`.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T, typename S>
+  Offset<Vector<const T *>> CreateVectorOfNativeStructs(
+      const std::vector<S> &v) {
+    return CreateVectorOfNativeStructs<T, S>(data(v), v.size());
+  }
+
+  /// @cond FLATBUFFERS_INTERNAL
+  template<typename T> struct StructKeyComparator {
+    bool operator()(const T &a, const T &b) const {
+      return a.KeyCompareLessThan(&b);
+    }
+
+   private:
+    StructKeyComparator &operator=(const StructKeyComparator &);
+  };
+  /// @endcond
+
+  /// @brief Serialize a `std::vector` of structs into a FlatBuffer `vector`
+  /// in sorted order.
+  /// @tparam T The data type of the `std::vector` struct elements.
+  /// @param[in] v A const reference to the `std::vector` of structs to
+  /// serialize into the buffer as a `vector`.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T>
+  Offset<Vector<const T *>> CreateVectorOfSortedStructs(std::vector<T> *v) {
+    return CreateVectorOfSortedStructs(data(*v), v->size());
+  }
+
+  /// @brief Serialize a `std::vector` of native structs into a FlatBuffer
+  /// `vector` in sorted order.
+  /// @tparam T The data type of the `std::vector` struct elements.
+  /// @tparam S The data type of the `std::vector` native struct elements.
+  /// @param[in] v A const reference to the `std::vector` of structs to
+  /// serialize into the buffer as a `vector`.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T, typename S>
+  Offset<Vector<const T *>> CreateVectorOfSortedNativeStructs(
+      std::vector<S> *v) {
+    return CreateVectorOfSortedNativeStructs<T, S>(data(*v), v->size());
+  }
+
+  /// @brief Serialize an array of structs into a FlatBuffer `vector` in sorted
+  /// order.
+  /// @tparam T The data type of the struct array elements.
+  /// @param[in] v A pointer to the array of type `T` to serialize into the
+  /// buffer as a `vector`.
+  /// @param[in] len The number of elements to serialize.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T>
+  Offset<Vector<const T *>> CreateVectorOfSortedStructs(T *v, size_t len) {
+    std::sort(v, v + len, StructKeyComparator<T>());
+    return CreateVectorOfStructs(v, len);
+  }
+
+  /// @brief Serialize an array of native structs into a FlatBuffer `vector` in
+  /// sorted order.
+  /// @tparam T The data type of the struct array elements.
+  /// @tparam S The data type of the native struct array elements.
+  /// @param[in] v A pointer to the array of type `S` to serialize into the
+  /// buffer as a `vector`.
+  /// @param[in] len The number of elements to serialize.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T, typename S>
+  Offset<Vector<const T *>> CreateVectorOfSortedNativeStructs(S *v,
+                                                              size_t len) {
+    extern T Pack(const S &);
+    typedef T (*Pack_t)(const S &);
+    std::vector<T> vv(len);
+    std::transform(v, v + len, vv.begin(), static_cast<Pack_t &>(Pack));
+    return CreateVectorOfSortedStructs<T>(vv, len);
+  }
+
+  /// @cond FLATBUFFERS_INTERNAL
+  template<typename T> struct TableKeyComparator {
+    TableKeyComparator(vector_downward &buf) : buf_(buf) {}
+    TableKeyComparator(const TableKeyComparator &other) : buf_(other.buf_) {}
+    bool operator()(const Offset<T> &a, const Offset<T> &b) const {
+      auto table_a = reinterpret_cast<T *>(buf_.data_at(a.o));
+      auto table_b = reinterpret_cast<T *>(buf_.data_at(b.o));
+      return table_a->KeyCompareLessThan(table_b);
+    }
+    vector_downward &buf_;
+
+   private:
+    TableKeyComparator &operator=(const TableKeyComparator &other) {
+      buf_ = other.buf_;
+      return *this;
+    }
+  };
+  /// @endcond
+
+  /// @brief Serialize an array of `table` offsets as a `vector` in the buffer
+  /// in sorted order.
+  /// @tparam T The data type that the offset refers to.
+  /// @param[in] v An array of type `Offset<T>` that contains the `table`
+  /// offsets to store in the buffer in sorted order.
+  /// @param[in] len The number of elements to store in the `vector`.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T>
+  Offset<Vector<Offset<T>>> CreateVectorOfSortedTables(Offset<T> *v,
+                                                       size_t len) {
+    std::sort(v, v + len, TableKeyComparator<T>(buf_));
+    return CreateVector(v, len);
+  }
+
+  /// @brief Serialize an array of `table` offsets as a `vector` in the buffer
+  /// in sorted order.
+  /// @tparam T The data type that the offset refers to.
+  /// @param[in] v An array of type `Offset<T>` that contains the `table`
+  /// offsets to store in the buffer in sorted order.
+  /// @return Returns a typed `Offset` into the serialized data indicating
+  /// where the vector is stored.
+  template<typename T>
+  Offset<Vector<Offset<T>>> CreateVectorOfSortedTables(
+      std::vector<Offset<T>> *v) {
+    return CreateVectorOfSortedTables(data(*v), v->size());
+  }
+
+  /// @brief Specialized version of `CreateVector` for non-copying use cases.
+  /// Write the data any time later to the returned buffer pointer `buf`.
+  /// @param[in] len The number of elements to store in the `vector`.
+  /// @param[in] elemsize The size of each element in the `vector`.
+  /// @param[out] buf A pointer to a `uint8_t` pointer that can be
+  /// written to at a later time to serialize the data into a `vector`
+  /// in the buffer.
+  uoffset_t CreateUninitializedVector(size_t len, size_t elemsize,
+                                      uint8_t **buf) {
+    NotNested();
+    StartVector(len, elemsize);
+    buf_.make_space(len * elemsize);
+    auto vec_start = GetSize();
+    auto vec_end = EndVector(len);
+    *buf = buf_.data_at(vec_start);
+    return vec_end;
+  }
+
+  /// @brief Specialized version of `CreateVector` for non-copying use cases.
+  /// Write the data any time later to the returned buffer pointer `buf`.
+  /// @tparam T The data type of the data that will be stored in the buffer
+  /// as a `vector`.
+  /// @param[in] len The number of elements to store in the `vector`.
+  /// @param[out] buf A pointer to a pointer of type `T` that can be
+  /// written to at a later time to serialize the data into a `vector`
+  /// in the buffer.
+  template<typename T>
+  Offset<Vector<T>> CreateUninitializedVector(size_t len, T **buf) {
+    AssertScalarT<T>();
+    return CreateUninitializedVector(len, sizeof(T),
+                                     reinterpret_cast<uint8_t **>(buf));
+  }
+
+  template<typename T>
+  Offset<Vector<const T *>> CreateUninitializedVectorOfStructs(size_t len,
+                                                               T **buf) {
+    return CreateUninitializedVector(len, sizeof(T),
+                                     reinterpret_cast<uint8_t **>(buf));
+  }
+
+  // @brief Create a vector of scalar type T given as input a vector of scalar
+  // type U, useful with e.g. pre "enum class" enums, or any existing scalar
+  // data of the wrong type.
+  template<typename T, typename U>
+  Offset<Vector<T>> CreateVectorScalarCast(const U *v, size_t len) {
+    AssertScalarT<T>();
+    AssertScalarT<U>();
+    StartVector(len, sizeof(T));
+    for (auto i = len; i > 0;) { PushElement(static_cast<T>(v[--i])); }
+    return Offset<Vector<T>>(EndVector(len));
+  }
+
+  /// @brief Write a struct by itself, typically to be part of a union.
+  template<typename T> Offset<const T *> CreateStruct(const T &structobj) {
+    NotNested();
+    Align(AlignOf<T>());
+    buf_.push_small(structobj);
+    return Offset<const T *>(GetSize());
+  }
+
+  /// @brief The length of a FlatBuffer file header.
+  static const size_t kFileIdentifierLength = 4;
+
+  /// @brief Finish serializing a buffer by writing the root offset.
+  /// @param[in] file_identifier If a `file_identifier` is given, the buffer
+  /// will be prefixed with a standard FlatBuffers file header.
+  template<typename T>
+  void Finish(Offset<T> root, const char *file_identifier = nullptr) {
+    Finish(root.o, file_identifier, false);
+  }
+
+  /// @brief Finish a buffer with a 32 bit size field pre-fixed (size of the
+  /// buffer following the size field). These buffers are NOT compatible
+  /// with standard buffers created by Finish, i.e. you can't call GetRoot
+  /// on them, you have to use GetSizePrefixedRoot instead.
+  /// All >32 bit quantities in this buffer will be aligned when the whole
+  /// size pre-fixed buffer is aligned.
+  /// These kinds of buffers are useful for creating a stream of FlatBuffers.
+  template<typename T>
+  void FinishSizePrefixed(Offset<T> root,
+                          const char *file_identifier = nullptr) {
+    Finish(root.o, file_identifier, true);
+  }
+
+  void SwapBufAllocator(FlatBufferBuilder &other) {
+    buf_.swap_allocator(other.buf_);
+  }
+
+ protected:
+  // You shouldn't really be copying instances of this class.
+  FlatBufferBuilder(const FlatBufferBuilder &);
+  FlatBufferBuilder &operator=(const FlatBufferBuilder &);
+
+  void Finish(uoffset_t root, const char *file_identifier, bool size_prefix) {
+    NotNested();
+    buf_.clear_scratch();
+    // This will cause the whole buffer to be aligned.
+    PreAlign((size_prefix ? sizeof(uoffset_t) : 0) + sizeof(uoffset_t) +
+                 (file_identifier ? kFileIdentifierLength : 0),
+             minalign_);
+    if (file_identifier) {
+      FLATBUFFERS_ASSERT(strlen(file_identifier) == kFileIdentifierLength);
+      PushBytes(reinterpret_cast<const uint8_t *>(file_identifier),
+                kFileIdentifierLength);
+    }
+    PushElement(ReferTo(root));  // Location of root.
+    if (size_prefix) { PushElement(GetSize()); }
+    finished = true;
+  }
+
+  struct FieldLoc {
+    uoffset_t off;
+    voffset_t id;
+  };
+
+  vector_downward buf_;
+
+  // Accumulating offsets of table members while it is being built.
+  // We store these in the scratch pad of buf_, after the vtable offsets.
+  uoffset_t num_field_loc;
+  // Track how much of the vtable is in use, so we can output the most compact
+  // possible vtable.
+  voffset_t max_voffset_;
+
+  // Ensure objects are not nested.
+  bool nested;
+
+  // Ensure the buffer is finished before it is being accessed.
+  bool finished;
+
+  size_t minalign_;
+
+  bool force_defaults_;  // Serialize values equal to their defaults anyway.
+
+  bool dedup_vtables_;
+
+  struct StringOffsetCompare {
+    StringOffsetCompare(const vector_downward &buf) : buf_(&buf) {}
+    bool operator()(const Offset<String> &a, const Offset<String> &b) const {
+      auto stra = reinterpret_cast<const String *>(buf_->data_at(a.o));
+      auto strb = reinterpret_cast<const String *>(buf_->data_at(b.o));
+      return StringLessThan(stra->data(), stra->size(), strb->data(),
+                            strb->size());
+    }
+    const vector_downward *buf_;
+  };
+
+  // For use with CreateSharedString. Instantiated on first use only.
+  typedef std::set<Offset<String>, StringOffsetCompare> StringOffsetMap;
+  StringOffsetMap *string_pool;
+
+ private:
+  // Allocates space for a vector of structures.
+  // Must be completed with EndVectorOfStructs().
+  template<typename T> T *StartVectorOfStructs(size_t vector_size) {
+    StartVector(vector_size * sizeof(T) / AlignOf<T>(), AlignOf<T>());
+    return reinterpret_cast<T *>(buf_.make_space(vector_size * sizeof(T)));
+  }
+
+  // End the vector of structues in the flatbuffers.
+  // Vector should have previously be started with StartVectorOfStructs().
+  template<typename T>
+  Offset<Vector<const T *>> EndVectorOfStructs(size_t vector_size) {
+    return Offset<Vector<const T *>>(EndVector(vector_size));
+  }
+};
+/// @}
+
+/// @cond FLATBUFFERS_INTERNAL
+// Helpers to get a typed pointer to the root object contained in the buffer.
+template<typename T> T *GetMutableRoot(void *buf) {
+  EndianCheck();
+  return reinterpret_cast<T *>(
+      reinterpret_cast<uint8_t *>(buf) +
+      EndianScalar(*reinterpret_cast<uoffset_t *>(buf)));
+}
+
+template<typename T> const T *GetRoot(const void *buf) {
+  return GetMutableRoot<T>(const_cast<void *>(buf));
+}
+
+template<typename T> const T *GetSizePrefixedRoot(const void *buf) {
+  return GetRoot<T>(reinterpret_cast<const uint8_t *>(buf) + sizeof(uoffset_t));
+}
+
+/// Helpers to get a typed pointer to objects that are currently being built.
+/// @warning Creating new objects will lead to reallocations and invalidates
+/// the pointer!
+template<typename T>
+T *GetMutableTemporaryPointer(FlatBufferBuilder &fbb, Offset<T> offset) {
+  return reinterpret_cast<T *>(fbb.GetCurrentBufferPointer() + fbb.GetSize() -
+                               offset.o);
+}
+
+template<typename T>
+const T *GetTemporaryPointer(FlatBufferBuilder &fbb, Offset<T> offset) {
+  return GetMutableTemporaryPointer<T>(fbb, offset);
+}
+
+/// @brief Get a pointer to the the file_identifier section of the buffer.
+/// @return Returns a const char pointer to the start of the file_identifier
+/// characters in the buffer.  The returned char * has length
+/// 'flatbuffers::FlatBufferBuilder::kFileIdentifierLength'.
+/// This function is UNDEFINED for FlatBuffers whose schema does not include
+/// a file_identifier (likely points at padding or the start of a the root
+/// vtable).
+inline const char *GetBufferIdentifier(const void *buf,
+                                       bool size_prefixed = false) {
+  return reinterpret_cast<const char *>(buf) +
+         ((size_prefixed) ? 2 * sizeof(uoffset_t) : sizeof(uoffset_t));
+}
+
+// Helper to see if the identifier in a buffer has the expected value.
+inline bool BufferHasIdentifier(const void *buf, const char *identifier,
+                                bool size_prefixed = false) {
+  return strncmp(GetBufferIdentifier(buf, size_prefixed), identifier,
+                 FlatBufferBuilder::kFileIdentifierLength) == 0;
+}
+
+// Helper class to verify the integrity of a FlatBuffer
+class Verifier FLATBUFFERS_FINAL_CLASS {
+ public:
+  Verifier(const uint8_t *buf, size_t buf_len, uoffset_t _max_depth = 64,
+           uoffset_t _max_tables = 1000000, bool _check_alignment = true)
+      : buf_(buf),
+        size_(buf_len),
+        depth_(0),
+        max_depth_(_max_depth),
+        num_tables_(0),
+        max_tables_(_max_tables),
+        upper_bound_(0),
+        check_alignment_(_check_alignment) {
+    FLATBUFFERS_ASSERT(size_ < FLATBUFFERS_MAX_BUFFER_SIZE);
+  }
+
+  // Central location where any verification failures register.
+  bool Check(bool ok) const {
+    // clang-format off
+    #ifdef FLATBUFFERS_DEBUG_VERIFICATION_FAILURE
+      FLATBUFFERS_ASSERT(ok);
+    #endif
+    #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE
+      if (!ok)
+        upper_bound_ = 0;
+    #endif
+    // clang-format on
+    return ok;
+  }
+
+  // Verify any range within the buffer.
+  bool Verify(size_t elem, size_t elem_len) const {
+    // clang-format off
+    #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE
+      auto upper_bound = elem + elem_len;
+      if (upper_bound_ < upper_bound)
+        upper_bound_ =  upper_bound;
+    #endif
+    // clang-format on
+    return Check(elem_len < size_ && elem <= size_ - elem_len);
+  }
+
+  template<typename T> bool VerifyAlignment(size_t elem) const {
+    return Check((elem & (sizeof(T) - 1)) == 0 || !check_alignment_);
+  }
+
+  // Verify a range indicated by sizeof(T).
+  template<typename T> bool Verify(size_t elem) const {
+    return VerifyAlignment<T>(elem) && Verify(elem, sizeof(T));
+  }
+
+  bool VerifyFromPointer(const uint8_t *p, size_t len) {
+    auto o = static_cast<size_t>(p - buf_);
+    return Verify(o, len);
+  }
+
+  // Verify relative to a known-good base pointer.
+  bool Verify(const uint8_t *base, voffset_t elem_off, size_t elem_len) const {
+    return Verify(static_cast<size_t>(base - buf_) + elem_off, elem_len);
+  }
+
+  template<typename T>
+  bool Verify(const uint8_t *base, voffset_t elem_off) const {
+    return Verify(static_cast<size_t>(base - buf_) + elem_off, sizeof(T));
+  }
+
+  // Verify a pointer (may be NULL) of a table type.
+  template<typename T> bool VerifyTable(const T *table) {
+    return !table || table->Verify(*this);
+  }
+
+  // Verify a pointer (may be NULL) of any vector type.
+  template<typename T> bool VerifyVector(const Vector<T> *vec) const {
+    return !vec || VerifyVectorOrString(reinterpret_cast<const uint8_t *>(vec),
+                                        sizeof(T));
+  }
+
+  // Verify a pointer (may be NULL) of a vector to struct.
+  template<typename T> bool VerifyVector(const Vector<const T *> *vec) const {
+    return VerifyVector(reinterpret_cast<const Vector<T> *>(vec));
+  }
+
+  // Verify a pointer (may be NULL) to string.
+  bool VerifyString(const String *str) const {
+    size_t end;
+    return !str || (VerifyVectorOrString(reinterpret_cast<const uint8_t *>(str),
+                                         1, &end) &&
+                    Verify(end, 1) &&           // Must have terminator
+                    Check(buf_[end] == '\0'));  // Terminating byte must be 0.
+  }
+
+  // Common code between vectors and strings.
+  bool VerifyVectorOrString(const uint8_t *vec, size_t elem_size,
+                            size_t *end = nullptr) const {
+    auto veco = static_cast<size_t>(vec - buf_);
+    // Check we can read the size field.
+    if (!Verify<uoffset_t>(veco)) return false;
+    // Check the whole array. If this is a string, the byte past the array
+    // must be 0.
+    auto size = ReadScalar<uoffset_t>(vec);
+    auto max_elems = FLATBUFFERS_MAX_BUFFER_SIZE / elem_size;
+    if (!Check(size < max_elems))
+      return false;  // Protect against byte_size overflowing.
+    auto byte_size = sizeof(size) + elem_size * size;
+    if (end) *end = veco + byte_size;
+    return Verify(veco, byte_size);
+  }
+
+  // Special case for string contents, after the above has been called.
+  bool VerifyVectorOfStrings(const Vector<Offset<String>> *vec) const {
+    if (vec) {
+      for (uoffset_t i = 0; i < vec->size(); i++) {
+        if (!VerifyString(vec->Get(i))) return false;
+      }
+    }
+    return true;
+  }
+
+  // Special case for table contents, after the above has been called.
+  template<typename T> bool VerifyVectorOfTables(const Vector<Offset<T>> *vec) {
+    if (vec) {
+      for (uoffset_t i = 0; i < vec->size(); i++) {
+        if (!vec->Get(i)->Verify(*this)) return false;
+      }
+    }
+    return true;
+  }
+
+  __supress_ubsan__("unsigned-integer-overflow") bool VerifyTableStart(
+      const uint8_t *table) {
+    // Check the vtable offset.
+    auto tableo = static_cast<size_t>(table - buf_);
+    if (!Verify<soffset_t>(tableo)) return false;
+    // This offset may be signed, but doing the subtraction unsigned always
+    // gives the result we want.
+    auto vtableo = tableo - static_cast<size_t>(ReadScalar<soffset_t>(table));
+    // Check the vtable size field, then check vtable fits in its entirety.
+    return VerifyComplexity() && Verify<voffset_t>(vtableo) &&
+           VerifyAlignment<voffset_t>(ReadScalar<voffset_t>(buf_ + vtableo)) &&
+           Verify(vtableo, ReadScalar<voffset_t>(buf_ + vtableo));
+  }
+
+  template<typename T>
+  bool VerifyBufferFromStart(const char *identifier, size_t start) {
+    if (identifier && (size_ < 2 * sizeof(flatbuffers::uoffset_t) ||
+                       !BufferHasIdentifier(buf_ + start, identifier))) {
+      return false;
+    }
+
+    // Call T::Verify, which must be in the generated code for this type.
+    auto o = VerifyOffset(start);
+    return o && reinterpret_cast<const T *>(buf_ + start + o)->Verify(*this)
+    // clang-format off
+    #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE
+           && GetComputedSize()
+    #endif
+        ;
+    // clang-format on
+  }
+
+  // Verify this whole buffer, starting with root type T.
+  template<typename T> bool VerifyBuffer() { return VerifyBuffer<T>(nullptr); }
+
+  template<typename T> bool VerifyBuffer(const char *identifier) {
+    return VerifyBufferFromStart<T>(identifier, 0);
+  }
+
+  template<typename T> bool VerifySizePrefixedBuffer(const char *identifier) {
+    return Verify<uoffset_t>(0U) &&
+           ReadScalar<uoffset_t>(buf_) == size_ - sizeof(uoffset_t) &&
+           VerifyBufferFromStart<T>(identifier, sizeof(uoffset_t));
+  }
+
+  uoffset_t VerifyOffset(size_t start) const {
+    if (!Verify<uoffset_t>(start)) return 0;
+    auto o = ReadScalar<uoffset_t>(buf_ + start);
+    // May not point to itself.
+    if (!Check(o != 0)) return 0;
+    // Can't wrap around / buffers are max 2GB.
+    if (!Check(static_cast<soffset_t>(o) >= 0)) return 0;
+    // Must be inside the buffer to create a pointer from it (pointer outside
+    // buffer is UB).
+    if (!Verify(start + o, 1)) return 0;
+    return o;
+  }
+
+  uoffset_t VerifyOffset(const uint8_t *base, voffset_t start) const {
+    return VerifyOffset(static_cast<size_t>(base - buf_) + start);
+  }
+
+  // Called at the start of a table to increase counters measuring data
+  // structure depth and amount, and possibly bails out with false if
+  // limits set by the constructor have been hit. Needs to be balanced
+  // with EndTable().
+  bool VerifyComplexity() {
+    depth_++;
+    num_tables_++;
+    return Check(depth_ <= max_depth_ && num_tables_ <= max_tables_);
+  }
+
+  // Called at the end of a table to pop the depth count.
+  bool EndTable() {
+    depth_--;
+    return true;
+  }
+
+  // Returns the message size in bytes
+  size_t GetComputedSize() const {
+    // clang-format off
+    #ifdef FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE
+      uintptr_t size = upper_bound_;
+      // Align the size to uoffset_t
+      size = (size - 1 + sizeof(uoffset_t)) & ~(sizeof(uoffset_t) - 1);
+      return (size > size_) ?  0 : size;
+    #else
+      // Must turn on FLATBUFFERS_TRACK_VERIFIER_BUFFER_SIZE for this to work.
+      (void)upper_bound_;
+      FLATBUFFERS_ASSERT(false);
+      return 0;
+    #endif
+    // clang-format on
+  }
+
+ private:
+  const uint8_t *buf_;
+  size_t size_;
+  uoffset_t depth_;
+  uoffset_t max_depth_;
+  uoffset_t num_tables_;
+  uoffset_t max_tables_;
+  mutable size_t upper_bound_;
+  bool check_alignment_;
+};
+
+// Convenient way to bundle a buffer and its length, to pass it around
+// typed by its root.
+// A BufferRef does not own its buffer.
+struct BufferRefBase {};  // for std::is_base_of
+template<typename T> struct BufferRef : BufferRefBase {
+  BufferRef() : buf(nullptr), len(0), must_free(false) {}
+  BufferRef(uint8_t *_buf, uoffset_t _len)
+      : buf(_buf), len(_len), must_free(false) {}
+
+  ~BufferRef() {
+    if (must_free) free(buf);
+  }
+
+  const T *GetRoot() const { return flatbuffers::GetRoot<T>(buf); }
+
+  bool Verify() {
+    Verifier verifier(buf, len);
+    return verifier.VerifyBuffer<T>(nullptr);
+  }
+
+  uint8_t *buf;
+  uoffset_t len;
+  bool must_free;
+};
+
+// "structs" are flat structures that do not have an offset table, thus
+// always have all members present and do not support forwards/backwards
+// compatible extensions.
+
+class Struct FLATBUFFERS_FINAL_CLASS {
+ public:
+  template<typename T> T GetField(uoffset_t o) const {
+    return ReadScalar<T>(&data_[o]);
+  }
+
+  template<typename T> T GetStruct(uoffset_t o) const {
+    return reinterpret_cast<T>(&data_[o]);
+  }
+
+  const uint8_t *GetAddressOf(uoffset_t o) const { return &data_[o]; }
+  uint8_t *GetAddressOf(uoffset_t o) { return &data_[o]; }
+
+ private:
+  // private constructor & copy constructor: you obtain instances of this
+  // class by pointing to existing data only
+  Struct();
+  Struct(const Struct &);
+  Struct &operator=(const Struct &);
+
+  uint8_t data_[1];
+};
+
+// "tables" use an offset table (possibly shared) that allows fields to be
+// omitted and added at will, but uses an extra indirection to read.
+class Table {
+ public:
+  const uint8_t *GetVTable() const {
+    return data_ - ReadScalar<soffset_t>(data_);
+  }
+
+  // This gets the field offset for any of the functions below it, or 0
+  // if the field was not present.
+  voffset_t GetOptionalFieldOffset(voffset_t field) const {
+    // The vtable offset is always at the start.
+    auto vtable = GetVTable();
+    // The first element is the size of the vtable (fields + type id + itself).
+    auto vtsize = ReadScalar<voffset_t>(vtable);
+    // If the field we're accessing is outside the vtable, we're reading older
+    // data, so it's the same as if the offset was 0 (not present).
+    return field < vtsize ? ReadScalar<voffset_t>(vtable + field) : 0;
+  }
+
+  template<typename T> T GetField(voffset_t field, T defaultval) const {
+    auto field_offset = GetOptionalFieldOffset(field);
+    return field_offset ? ReadScalar<T>(data_ + field_offset) : defaultval;
+  }
+
+  template<typename P> P GetPointer(voffset_t field) {
+    auto field_offset = GetOptionalFieldOffset(field);
+    auto p = data_ + field_offset;
+    return field_offset ? reinterpret_cast<P>(p + ReadScalar<uoffset_t>(p))
+                        : nullptr;
+  }
+  template<typename P> P GetPointer(voffset_t field) const {
+    return const_cast<Table *>(this)->GetPointer<P>(field);
+  }
+
+  template<typename P> P GetStruct(voffset_t field) const {
+    auto field_offset = GetOptionalFieldOffset(field);
+    auto p = const_cast<uint8_t *>(data_ + field_offset);
+    return field_offset ? reinterpret_cast<P>(p) : nullptr;
+  }
+
+  template<typename T> bool SetField(voffset_t field, T val, T def) {
+    auto field_offset = GetOptionalFieldOffset(field);
+    if (!field_offset) return IsTheSameAs(val, def);
+    WriteScalar(data_ + field_offset, val);
+    return true;
+  }
+
+  bool SetPointer(voffset_t field, const uint8_t *val) {
+    auto field_offset = GetOptionalFieldOffset(field);
+    if (!field_offset) return false;
+    WriteScalar(data_ + field_offset,
+                static_cast<uoffset_t>(val - (data_ + field_offset)));
+    return true;
+  }
+
+  uint8_t *GetAddressOf(voffset_t field) {
+    auto field_offset = GetOptionalFieldOffset(field);
+    return field_offset ? data_ + field_offset : nullptr;
+  }
+  const uint8_t *GetAddressOf(voffset_t field) const {
+    return const_cast<Table *>(this)->GetAddressOf(field);
+  }
+
+  bool CheckField(voffset_t field) const {
+    return GetOptionalFieldOffset(field) != 0;
+  }
+
+  // Verify the vtable of this table.
+  // Call this once per table, followed by VerifyField once per field.
+  bool VerifyTableStart(Verifier &verifier) const {
+    return verifier.VerifyTableStart(data_);
+  }
+
+  // Verify a particular field.
+  template<typename T>
+  bool VerifyField(const Verifier &verifier, voffset_t field) const {
+    // Calling GetOptionalFieldOffset should be safe now thanks to
+    // VerifyTable().
+    auto field_offset = GetOptionalFieldOffset(field);
+    // Check the actual field.
+    return !field_offset || verifier.Verify<T>(data_, field_offset);
+  }
+
+  // VerifyField for required fields.
+  template<typename T>
+  bool VerifyFieldRequired(const Verifier &verifier, voffset_t field) const {
+    auto field_offset = GetOptionalFieldOffset(field);
+    return verifier.Check(field_offset != 0) &&
+           verifier.Verify<T>(data_, field_offset);
+  }
+
+  // Versions for offsets.
+  bool VerifyOffset(const Verifier &verifier, voffset_t field) const {
+    auto field_offset = GetOptionalFieldOffset(field);
+    return !field_offset || verifier.VerifyOffset(data_, field_offset);
+  }
+
+  bool VerifyOffsetRequired(const Verifier &verifier, voffset_t field) const {
+    auto field_offset = GetOptionalFieldOffset(field);
+    return verifier.Check(field_offset != 0) &&
+           verifier.VerifyOffset(data_, field_offset);
+  }
+
+ private:
+  // private constructor & copy constructor: you obtain instances of this
+  // class by pointing to existing data only
+  Table();
+  Table(const Table &other);
+  Table &operator=(const Table &);
+
+  uint8_t data_[1];
+};
+
+template<typename T>
+void FlatBufferBuilder::Required(Offset<T> table, voffset_t field) {
+  auto table_ptr = reinterpret_cast<const Table *>(buf_.data_at(table.o));
+  bool ok = table_ptr->GetOptionalFieldOffset(field) != 0;
+  // If this fails, the caller will show what field needs to be set.
+  FLATBUFFERS_ASSERT(ok);
+  (void)ok;
+}
+
+/// @brief This can compute the start of a FlatBuffer from a root pointer, i.e.
+/// it is the opposite transformation of GetRoot().
+/// This may be useful if you want to pass on a root and have the recipient
+/// delete the buffer afterwards.
+inline const uint8_t *GetBufferStartFromRootPointer(const void *root) {
+  auto table = reinterpret_cast<const Table *>(root);
+  auto vtable = table->GetVTable();
+  // Either the vtable is before the root or after the root.
+  auto start = (std::min)(vtable, reinterpret_cast<const uint8_t *>(root));
+  // Align to at least sizeof(uoffset_t).
+  start = reinterpret_cast<const uint8_t *>(reinterpret_cast<uintptr_t>(start) &
+                                            ~(sizeof(uoffset_t) - 1));
+  // Additionally, there may be a file_identifier in the buffer, and the root
+  // offset. The buffer may have been aligned to any size between
+  // sizeof(uoffset_t) and FLATBUFFERS_MAX_ALIGNMENT (see "force_align").
+  // Sadly, the exact alignment is only known when constructing the buffer,
+  // since it depends on the presence of values with said alignment properties.
+  // So instead, we simply look at the next uoffset_t values (root,
+  // file_identifier, and alignment padding) to see which points to the root.
+  // None of the other values can "impersonate" the root since they will either
+  // be 0 or four ASCII characters.
+  static_assert(FlatBufferBuilder::kFileIdentifierLength == sizeof(uoffset_t),
+                "file_identifier is assumed to be the same size as uoffset_t");
+  for (auto possible_roots = FLATBUFFERS_MAX_ALIGNMENT / sizeof(uoffset_t) + 1;
+       possible_roots; possible_roots--) {
+    start -= sizeof(uoffset_t);
+    if (ReadScalar<uoffset_t>(start) + start ==
+        reinterpret_cast<const uint8_t *>(root))
+      return start;
+  }
+  // We didn't find the root, either the "root" passed isn't really a root,
+  // or the buffer is corrupt.
+  // Assert, because calling this function with bad data may cause reads
+  // outside of buffer boundaries.
+  FLATBUFFERS_ASSERT(false);
+  return nullptr;
+}
+
+/// @brief This return the prefixed size of a FlatBuffer.
+inline uoffset_t GetPrefixedSize(const uint8_t *buf) {
+  return ReadScalar<uoffset_t>(buf);
+}
+
+// Base class for native objects (FlatBuffer data de-serialized into native
+// C++ data structures).
+// Contains no functionality, purely documentative.
+struct NativeTable {};
+
+/// @brief Function types to be used with resolving hashes into objects and
+/// back again. The resolver gets a pointer to a field inside an object API
+/// object that is of the type specified in the schema using the attribute
+/// `cpp_type` (it is thus important whatever you write to this address
+/// matches that type). The value of this field is initially null, so you
+/// may choose to implement a delayed binding lookup using this function
+/// if you wish. The resolver does the opposite lookup, for when the object
+/// is being serialized again.
+typedef uint64_t hash_value_t;
+// clang-format off
+#ifdef FLATBUFFERS_CPP98_STL
+  typedef void (*resolver_function_t)(void **pointer_adr, hash_value_t hash);
+  typedef hash_value_t (*rehasher_function_t)(void *pointer);
+#else
+  typedef std::function<void (void **pointer_adr, hash_value_t hash)>
+          resolver_function_t;
+  typedef std::function<hash_value_t (void *pointer)> rehasher_function_t;
+#endif
+// clang-format on
+
+// Helper function to test if a field is present, using any of the field
+// enums in the generated code.
+// `table` must be a generated table type. Since this is a template parameter,
+// this is not typechecked to be a subclass of Table, so beware!
+// Note: this function will return false for fields equal to the default
+// value, since they're not stored in the buffer (unless force_defaults was
+// used).
+template<typename T>
+bool IsFieldPresent(const T *table, typename T::FlatBuffersVTableOffset field) {
+  // Cast, since Table is a private baseclass of any table types.
+  return reinterpret_cast<const Table *>(table)->CheckField(
+      static_cast<voffset_t>(field));
+}
+
+// Utility function for reverse lookups on the EnumNames*() functions
+// (in the generated C++ code)
+// names must be NULL terminated.
+inline int LookupEnum(const char **names, const char *name) {
+  for (const char **p = names; *p; p++)
+    if (!strcmp(*p, name)) return static_cast<int>(p - names);
+  return -1;
+}
+
+// These macros allow us to layout a struct with a guarantee that they'll end
+// up looking the same on different compilers and platforms.
+// It does this by disallowing the compiler to do any padding, and then
+// does padding itself by inserting extra padding fields that make every
+// element aligned to its own size.
+// Additionally, it manually sets the alignment of the struct as a whole,
+// which is typically its largest element, or a custom size set in the schema
+// by the force_align attribute.
+// These are used in the generated code only.
+
+// clang-format off
+#if defined(_MSC_VER)
+  #define FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(alignment) \
+    __pragma(pack(1)) \
+    struct __declspec(align(alignment))
+  #define FLATBUFFERS_STRUCT_END(name, size) \
+    __pragma(pack()) \
+    static_assert(sizeof(name) == size, "compiler breaks packing rules")
+#elif defined(__GNUC__) || defined(__clang__) || defined(__ICCARM__)
+  #define FLATBUFFERS_MANUALLY_ALIGNED_STRUCT(alignment) \
+    _Pragma("pack(1)") \
+    struct __attribute__((aligned(alignment)))
+  #define FLATBUFFERS_STRUCT_END(name, size) \
+    _Pragma("pack()") \
+    static_assert(sizeof(name) == size, "compiler breaks packing rules")
+#else
+  #error Unknown compiler, please define structure alignment macros
+#endif
+// clang-format on
+
+// Minimal reflection via code generation.
+// Besides full-fat reflection (see reflection.h) and parsing/printing by
+// loading schemas (see idl.h), we can also have code generation for mimimal
+// reflection data which allows pretty-printing and other uses without needing
+// a schema or a parser.
+// Generate code with --reflect-types (types only) or --reflect-names (names
+// also) to enable.
+// See minireflect.h for utilities using this functionality.
+
+// These types are organized slightly differently as the ones in idl.h.
+enum SequenceType { ST_TABLE, ST_STRUCT, ST_UNION, ST_ENUM };
+
+// Scalars have the same order as in idl.h
+// clang-format off
+#define FLATBUFFERS_GEN_ELEMENTARY_TYPES(ET) \
+  ET(ET_UTYPE) \
+  ET(ET_BOOL) \
+  ET(ET_CHAR) \
+  ET(ET_UCHAR) \
+  ET(ET_SHORT) \
+  ET(ET_USHORT) \
+  ET(ET_INT) \
+  ET(ET_UINT) \
+  ET(ET_LONG) \
+  ET(ET_ULONG) \
+  ET(ET_FLOAT) \
+  ET(ET_DOUBLE) \
+  ET(ET_STRING) \
+  ET(ET_SEQUENCE)  // See SequenceType.
+
+enum ElementaryType {
+  #define FLATBUFFERS_ET(E) E,
+    FLATBUFFERS_GEN_ELEMENTARY_TYPES(FLATBUFFERS_ET)
+  #undef FLATBUFFERS_ET
+};
+
+inline const char * const *ElementaryTypeNames() {
+  static const char * const names[] = {
+    #define FLATBUFFERS_ET(E) #E,
+      FLATBUFFERS_GEN_ELEMENTARY_TYPES(FLATBUFFERS_ET)
+    #undef FLATBUFFERS_ET
+  };
+  return names;
+}
+// clang-format on
+
+// Basic type info cost just 16bits per field!
+struct TypeCode {
+  uint16_t base_type : 4;  // ElementaryType
+  uint16_t is_vector : 1;
+  int16_t sequence_ref : 11;  // Index into type_refs below, or -1 for none.
+};
+
+static_assert(sizeof(TypeCode) == 2, "TypeCode");
+
+struct TypeTable;
+
+// Signature of the static method present in each type.
+typedef const TypeTable *(*TypeFunction)();
+
+struct TypeTable {
+  SequenceType st;
+  size_t num_elems;  // of type_codes, values, names (but not type_refs).
+  const TypeCode *type_codes;     // num_elems count
+  const TypeFunction *type_refs;  // less than num_elems entries (see TypeCode).
+  const int64_t *values;  // Only set for non-consecutive enum/union or structs.
+  const char *const *names;  // Only set if compiled with --reflect-names.
+};
+
+// String which identifies the current version of FlatBuffers.
+// flatbuffer_version_string is used by Google developers to identify which
+// applications uploaded to Google Play are using this library.  This allows
+// the development team at Google to determine the popularity of the library.
+// How it works: Applications that are uploaded to the Google Play Store are
+// scanned for this version string.  We track which applications are using it
+// to measure popularity.  You are free to remove it (of course) but we would
+// appreciate if you left it in.
+
+// Weak linkage is culled by VS & doesn't work on cygwin.
+// clang-format off
+#if !defined(_WIN32) && !defined(__CYGWIN__)
+
+extern volatile __attribute__((weak)) const char *flatbuffer_version_string;
+volatile __attribute__((weak)) const char *flatbuffer_version_string =
+  "FlatBuffers "
+  FLATBUFFERS_STRING(FLATBUFFERS_VERSION_MAJOR) "."
+  FLATBUFFERS_STRING(FLATBUFFERS_VERSION_MINOR) "."
+  FLATBUFFERS_STRING(FLATBUFFERS_VERSION_REVISION);
+
+#endif  // !defined(_WIN32) && !defined(__CYGWIN__)
+
+#define FLATBUFFERS_DEFINE_BITMASK_OPERATORS(E, T)\
+    inline E operator | (E lhs, E rhs){\
+        return E(T(lhs) | T(rhs));\
+    }\
+    inline E operator & (E lhs, E rhs){\
+        return E(T(lhs) & T(rhs));\
+    }\
+    inline E operator ^ (E lhs, E rhs){\
+        return E(T(lhs) ^ T(rhs));\
+    }\
+    inline E operator ~ (E lhs){\
+        return E(~T(lhs));\
+    }\
+    inline E operator |= (E &lhs, E rhs){\
+        lhs = lhs | rhs;\
+        return lhs;\
+    }\
+    inline E operator &= (E &lhs, E rhs){\
+        lhs = lhs & rhs;\
+        return lhs;\
+    }\
+    inline E operator ^= (E &lhs, E rhs){\
+        lhs = lhs ^ rhs;\
+        return lhs;\
+    }\
+    inline bool operator !(E rhs) \
+    {\
+        return !bool(T(rhs)); \
+    }
+/// @endcond
+}  // namespace flatbuffers
+
+// clang-format on
+
+#endif  // FLATBUFFERS_H_

+ 307 - 0
code/lib/tfmicro/flatbuffers/stl_emulation.h

@@ -0,0 +1,307 @@
+/*
+ * Copyright 2017 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FLATBUFFERS_STL_EMULATION_H_
+#define FLATBUFFERS_STL_EMULATION_H_
+
+// clang-format off
+
+#include <string>
+#include <type_traits>
+#include <vector>
+#include <memory>
+#include <limits>
+
+#if defined(_STLPORT_VERSION) && !defined(FLATBUFFERS_CPP98_STL)
+  #define FLATBUFFERS_CPP98_STL
+#endif  // defined(_STLPORT_VERSION) && !defined(FLATBUFFERS_CPP98_STL)
+
+#if defined(FLATBUFFERS_CPP98_STL)
+  #include <cctype>
+#endif  // defined(FLATBUFFERS_CPP98_STL)
+
+// Check if we can use template aliases
+// Not possible if Microsoft Compiler before 2012
+// Possible is the language feature __cpp_alias_templates is defined well
+// Or possible if the C++ std is C+11 or newer
+#if (defined(_MSC_VER) && _MSC_VER > 1700 /* MSVC2012 */) \
+    || (defined(__cpp_alias_templates) && __cpp_alias_templates >= 200704) \
+    || (defined(__cplusplus) && __cplusplus >= 201103L)
+  #define FLATBUFFERS_TEMPLATES_ALIASES
+#endif
+
+// This header provides backwards compatibility for C++98 STLs like stlport.
+namespace flatbuffers {
+
+// Retrieve ::back() from a string in a way that is compatible with pre C++11
+// STLs (e.g stlport).
+inline char& string_back(std::string &value) {
+  return value[value.length() - 1];
+}
+
+inline char string_back(const std::string &value) {
+  return value[value.length() - 1];
+}
+
+// Helper method that retrieves ::data() from a vector in a way that is
+// compatible with pre C++11 STLs (e.g stlport).
+template <typename T> inline T *vector_data(std::vector<T> &vector) {
+  // In some debug environments, operator[] does bounds checking, so &vector[0]
+  // can't be used.
+  return vector.empty() ? nullptr : &vector[0];
+}
+
+template <typename T> inline const T *vector_data(
+    const std::vector<T> &vector) {
+  return vector.empty() ? nullptr : &vector[0];
+}
+
+template <typename T, typename V>
+inline void vector_emplace_back(std::vector<T> *vector, V &&data) {
+  #if defined(FLATBUFFERS_CPP98_STL)
+    vector->push_back(data);
+  #else
+    vector->emplace_back(std::forward<V>(data));
+  #endif  // defined(FLATBUFFERS_CPP98_STL)
+}
+
+#ifndef FLATBUFFERS_CPP98_STL
+  #if defined(FLATBUFFERS_TEMPLATES_ALIASES)
+    template <typename T>
+    using numeric_limits = std::numeric_limits<T>;
+  #else
+    template <typename T> class numeric_limits :
+      public std::numeric_limits<T> {};
+  #endif  // defined(FLATBUFFERS_TEMPLATES_ALIASES)
+#else
+  template <typename T> class numeric_limits :
+      public std::numeric_limits<T> {
+    public:
+      // Android NDK fix.
+      static T lowest() {
+        return std::numeric_limits<T>::min();
+      }
+  };
+
+  template <> class numeric_limits<float> :
+      public std::numeric_limits<float> {
+    public:
+      static float lowest() { return -FLT_MAX; }
+  };
+
+  template <> class numeric_limits<double> :
+      public std::numeric_limits<double> {
+    public:
+      static double lowest() { return -DBL_MAX; }
+  };
+
+  template <> class numeric_limits<unsigned long long> {
+   public:
+    static unsigned long long min() { return 0ULL; }
+    static unsigned long long max() { return ~0ULL; }
+    static unsigned long long lowest() {
+      return numeric_limits<unsigned long long>::min();
+    }
+  };
+
+  template <> class numeric_limits<long long> {
+   public:
+    static long long min() {
+      return static_cast<long long>(1ULL << ((sizeof(long long) << 3) - 1));
+    }
+    static long long max() {
+      return static_cast<long long>(
+          (1ULL << ((sizeof(long long) << 3) - 1)) - 1);
+    }
+    static long long lowest() {
+      return numeric_limits<long long>::min();
+    }
+  };
+#endif  // FLATBUFFERS_CPP98_STL
+
+#if defined(FLATBUFFERS_TEMPLATES_ALIASES)
+  #ifndef FLATBUFFERS_CPP98_STL
+    template <typename T> using is_scalar = std::is_scalar<T>;
+    template <typename T, typename U> using is_same = std::is_same<T,U>;
+    template <typename T> using is_floating_point = std::is_floating_point<T>;
+    template <typename T> using is_unsigned = std::is_unsigned<T>;
+    template <typename T> using is_enum = std::is_enum<T>;
+    template <typename T> using make_unsigned = std::make_unsigned<T>;
+    template<bool B, class T, class F>
+    using conditional = std::conditional<B, T, F>;
+    template<class T, T v>
+    using integral_constant = std::integral_constant<T, v>;
+  #else
+    // Map C++ TR1 templates defined by stlport.
+    template <typename T> using is_scalar = std::tr1::is_scalar<T>;
+    template <typename T, typename U> using is_same = std::tr1::is_same<T,U>;
+    template <typename T> using is_floating_point =
+        std::tr1::is_floating_point<T>;
+    template <typename T> using is_unsigned = std::tr1::is_unsigned<T>;
+    template <typename T> using is_enum = std::tr1::is_enum<T>;
+    // Android NDK doesn't have std::make_unsigned or std::tr1::make_unsigned.
+    template<typename T> struct make_unsigned {
+      static_assert(is_unsigned<T>::value, "Specialization not implemented!");
+      using type = T;
+    };
+    template<> struct make_unsigned<char> { using type = unsigned char; };
+    template<> struct make_unsigned<short> { using type = unsigned short; };
+    template<> struct make_unsigned<int> { using type = unsigned int; };
+    template<> struct make_unsigned<long> { using type = unsigned long; };
+    template<>
+    struct make_unsigned<long long> { using type = unsigned long long; };
+    template<bool B, class T, class F>
+    using conditional = std::tr1::conditional<B, T, F>;
+    template<class T, T v>
+    using integral_constant = std::tr1::integral_constant<T, v>;
+  #endif  // !FLATBUFFERS_CPP98_STL
+#else
+  // MSVC 2010 doesn't support C++11 aliases.
+  template <typename T> struct is_scalar : public std::is_scalar<T> {};
+  template <typename T, typename U> struct is_same : public std::is_same<T,U> {};
+  template <typename T> struct is_floating_point :
+        public std::is_floating_point<T> {};
+  template <typename T> struct is_unsigned : public std::is_unsigned<T> {};
+  template <typename T> struct is_enum : public std::is_enum<T> {};
+  template <typename T> struct make_unsigned : public std::make_unsigned<T> {};
+  template<bool B, class T, class F>
+  struct conditional : public std::conditional<B, T, F> {};
+  template<class T, T v>
+  struct integral_constant : public std::integral_constant<T, v> {};
+#endif  // defined(FLATBUFFERS_TEMPLATES_ALIASES)
+
+#ifndef FLATBUFFERS_CPP98_STL
+  #if defined(FLATBUFFERS_TEMPLATES_ALIASES)
+    template <class T> using unique_ptr = std::unique_ptr<T>;
+  #else
+    // MSVC 2010 doesn't support C++11 aliases.
+    // We're manually "aliasing" the class here as we want to bring unique_ptr
+    // into the flatbuffers namespace.  We have unique_ptr in the flatbuffers
+    // namespace we have a completely independent implemenation (see below)
+    // for C++98 STL implementations.
+    template <class T> class unique_ptr : public std::unique_ptr<T> {
+     public:
+      unique_ptr() {}
+      explicit unique_ptr(T* p) : std::unique_ptr<T>(p) {}
+      unique_ptr(std::unique_ptr<T>&& u) { *this = std::move(u); }
+      unique_ptr(unique_ptr&& u) { *this = std::move(u); }
+      unique_ptr& operator=(std::unique_ptr<T>&& u) {
+        std::unique_ptr<T>::reset(u.release());
+        return *this;
+      }
+      unique_ptr& operator=(unique_ptr&& u) {
+        std::unique_ptr<T>::reset(u.release());
+        return *this;
+      }
+      unique_ptr& operator=(T* p) {
+        return std::unique_ptr<T>::operator=(p);
+      }
+    };
+  #endif  // defined(FLATBUFFERS_TEMPLATES_ALIASES)
+#else
+  // Very limited implementation of unique_ptr.
+  // This is provided simply to allow the C++ code generated from the default
+  // settings to function in C++98 environments with no modifications.
+  template <class T> class unique_ptr {
+   public:
+    typedef T element_type;
+
+    unique_ptr() : ptr_(nullptr) {}
+    explicit unique_ptr(T* p) : ptr_(p) {}
+    unique_ptr(unique_ptr&& u) : ptr_(nullptr) { reset(u.release()); }
+    unique_ptr(const unique_ptr& u) : ptr_(nullptr) {
+      reset(const_cast<unique_ptr*>(&u)->release());
+    }
+    ~unique_ptr() { reset(); }
+
+    unique_ptr& operator=(const unique_ptr& u) {
+      reset(const_cast<unique_ptr*>(&u)->release());
+      return *this;
+    }
+
+    unique_ptr& operator=(unique_ptr&& u) {
+      reset(u.release());
+      return *this;
+    }
+
+    unique_ptr& operator=(T* p) {
+      reset(p);
+      return *this;
+    }
+
+    const T& operator*() const { return *ptr_; }
+    T* operator->() const { return ptr_; }
+    T* get() const noexcept { return ptr_; }
+    explicit operator bool() const { return ptr_ != nullptr; }
+
+    // modifiers
+    T* release() {
+      T* value = ptr_;
+      ptr_ = nullptr;
+      return value;
+    }
+
+    void reset(T* p = nullptr) {
+      T* value = ptr_;
+      ptr_ = p;
+      if (value) delete value;
+    }
+
+    void swap(unique_ptr& u) {
+      T* temp_ptr = ptr_;
+      ptr_ = u.ptr_;
+      u.ptr_ = temp_ptr;
+    }
+
+   private:
+    T* ptr_;
+  };
+
+  template <class T> bool operator==(const unique_ptr<T>& x,
+                                     const unique_ptr<T>& y) {
+    return x.get() == y.get();
+  }
+
+  template <class T, class D> bool operator==(const unique_ptr<T>& x,
+                                              const D* y) {
+    return static_cast<D*>(x.get()) == y;
+  }
+
+  template <class T> bool operator==(const unique_ptr<T>& x, intptr_t y) {
+    return reinterpret_cast<intptr_t>(x.get()) == y;
+  }
+
+  template <class T> bool operator!=(const unique_ptr<T>& x, decltype(nullptr)) {
+    return !!x;
+  }
+
+  template <class T> bool operator!=(decltype(nullptr), const unique_ptr<T>& x) {
+    return !!x;
+  }
+
+  template <class T> bool operator==(const unique_ptr<T>& x, decltype(nullptr)) {
+    return !x;
+  }
+
+  template <class T> bool operator==(decltype(nullptr), const unique_ptr<T>& x) {
+    return !x;
+  }
+
+#endif  // !FLATBUFFERS_CPP98_STL
+
+}  // namespace flatbuffers
+
+#endif  // FLATBUFFERS_STL_EMULATION_H_

+ 166 - 0
code/lib/tfmicro/internal/detect_platform.h

@@ -0,0 +1,166 @@
+// Copyright 2018 The Gemmlowp Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// detect_platform.h: Sets up macros that control architecture-specific
+// features of gemmlowp's implementation.
+
+#ifndef GEMMLOWP_INTERNAL_DETECT_PLATFORM_H_
+#define GEMMLOWP_INTERNAL_DETECT_PLATFORM_H_
+
+// Our inline assembly path assume GCC/Clang syntax.
+// Native Client doesn't seem to support inline assembly(?).
+#if defined(__GNUC__) && !defined(__native_client__)
+#define GEMMLOWP_ALLOW_INLINE_ASM
+#endif
+
+// Define macro statement that avoids inlining for GCC.
+// For non-GCC, define as empty macro.
+#if defined(__GNUC__)
+#define GEMMLOWP_NOINLINE __attribute__((noinline))
+#else
+#define GEMMLOWP_NOINLINE
+#endif
+
+// Detect ARM, 32-bit or 64-bit
+#ifdef __arm__
+#define GEMMLOWP_ARM_32
+#endif
+
+#ifdef __aarch64__
+#define GEMMLOWP_ARM_64
+#endif
+
+#if defined(GEMMLOWP_ARM_32) || defined(GEMMLOWP_ARM_64)
+#define GEMMLOWP_ARM
+#endif
+
+// Detect MIPS, 32-bit or 64-bit
+#if defined(__mips) && !defined(__LP64__)
+#define GEMMLOWP_MIPS_32
+#endif
+
+#if defined(__mips) && defined(__LP64__)
+#define GEMMLOWP_MIPS_64
+#endif
+
+#if defined(GEMMLOWP_MIPS_32) || defined(GEMMLOWP_MIPS_64)
+#define GEMMLOWP_MIPS
+#endif
+
+// Detect x86, 32-bit or 64-bit
+#if defined(__i386__) || defined(_M_IX86) || defined(_X86_) || defined(__i386)
+#define GEMMLOWP_X86_32
+#endif
+
+#if defined(__x86_64__) || defined(_M_X64) || defined(__amd64)
+#define GEMMLOWP_X86_64
+#endif
+
+#if defined(GEMMLOWP_X86_32) || defined(GEMMLOWP_X86_64)
+#define GEMMLOWP_X86
+#endif
+
+// Some of our optimized paths use inline assembly and for
+// now we don't bother enabling some other optimized paths using intrinddics
+// where we can't use inline assembly paths.
+#ifdef GEMMLOWP_ALLOW_INLINE_ASM
+
+// Detect NEON. It's important to check for both tokens.
+#if (defined __ARM_NEON) || (defined __ARM_NEON__)
+#define GEMMLOWP_NEON
+#endif
+
+// Convenience NEON tokens for 32-bit or 64-bit
+#if defined(GEMMLOWP_NEON) && defined(GEMMLOWP_ARM_32)
+#define GEMMLOWP_NEON_32
+#endif
+
+#if defined(GEMMLOWP_NEON) && defined(GEMMLOWP_ARM_64)
+#define GEMMLOWP_NEON_64
+#endif
+
+// Detect MIPS MSA.
+// Limit MSA optimizations to little-endian CPUs for now.
+// TODO: Perhaps, eventually support MSA optimizations on big-endian CPUs?
+#if defined(GEMMLOWP_MIPS) && (__mips_isa_rev >= 5) && defined(__mips_msa) && \
+    defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+#define GEMMLOWP_MSA
+#endif
+
+// Convenience MIPS MSA tokens for 32-bit or 64-bit.
+#if defined(GEMMLOWP_MSA) && defined(GEMMLOWP_MIPS_32)
+#define GEMMLOWP_MSA_32
+#endif
+
+#if defined(GEMMLOWP_MSA) && defined(GEMMLOWP_MIPS_64)
+#define GEMMLOWP_MSA_64
+#endif
+
+// compiler define for AVX2 -D GEMMLOWP_ENABLE_AVX2
+// Detect AVX2
+#if defined(__AVX2__) && defined(GEMMLOWP_ENABLE_AVX2)
+#define GEMMLOWP_AVX2
+// Detect SSE4.
+// MSVC does not have __SSE4_1__ macro, but will enable SSE4
+// when AVX is turned on.
+#elif defined(__SSE4_1__) || (defined(_MSC_VER) && defined(__AVX__))
+#define GEMMLOWP_SSE4
+// Detect SSE3.
+#elif defined(__SSE3__)
+#define GEMMLOWP_SSE3
+#endif
+
+// Convenience SSE4 tokens for 32-bit or 64-bit
+#if defined(GEMMLOWP_SSE4) && defined(GEMMLOWP_X86_32) && \
+    !defined(GEMMLOWP_DISABLE_SSE4)
+#define GEMMLOWP_SSE4_32
+#endif
+
+#if defined(GEMMLOWP_SSE3) && defined(GEMMLOWP_X86_32)
+#define GEMMLOWP_SSE3_32
+#endif
+
+#if defined(GEMMLOWP_SSE4) && defined(GEMMLOWP_X86_64) && \
+    !defined(GEMMLOWP_DISABLE_SSE4)
+#define GEMMLOWP_SSE4_64
+#endif
+
+#if defined(GEMMLOWP_SSE3) && defined(GEMMLOWP_X86_64)
+#define GEMMLOWP_SSE3_64
+#endif
+
+#if defined(GEMMLOWP_AVX2) && defined(GEMMLOWP_X86_64)
+#define GEMMLOWP_AVX2_64
+#endif
+
+#if defined(__has_feature)
+#if __has_feature(memory_sanitizer)
+#include <sanitizer/msan_interface.h>
+#define GEMMLOWP_MARK_MEMORY_AS_INITIALIZED __msan_unpoison
+#elif __has_feature(address_sanitizer)
+#include <sanitizer/asan_interface.h>
+#define GEMMLOWP_MARK_MEMORY_AS_INITIALIZED __asan_unpoison_memory_region
+#endif
+#endif
+
+#endif  // GEMMLOWP_ALLOW_INLINE_ASM
+
+// Detect Android. Don't conflate with ARM - we care about tuning
+// for non-ARM Android devices too. This can be used in conjunction
+// with x86 to tune differently for mobile x86 CPUs (Atom) vs. desktop x86 CPUs.
+#if defined(__ANDROID__) || defined(ANDROID)
+#define GEMMLOWP_ANDROID
+#endif
+
+#endif  // GEMMLOWP_INTERNAL_DETECT_PLATFORM_H_

+ 11 - 0
code/lib/tfmicro/kissfft/COPYING

@@ -0,0 +1,11 @@
+Copyright (c) 2003-2010 Mark Borgerding
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+    * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 164 - 0
code/lib/tfmicro/kissfft/_kiss_fft_guts.h

@@ -0,0 +1,164 @@
+/*
+Copyright (c) 2003-2010, Mark Borgerding
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+    * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* kiss_fft.h
+   defines kiss_fft_scalar as either short or a float type
+   and defines
+   typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */
+#include "kiss_fft.h"
+#include <limits.h>
+
+#define MAXFACTORS 32
+/* e.g. an fft of length 128 has 4 factors 
+ as far as kissfft is concerned
+ 4*4*4*2
+ */
+
+struct kiss_fft_state{
+    int nfft;
+    int inverse;
+    int factors[2*MAXFACTORS];
+    kiss_fft_cpx twiddles[1];
+};
+
+/*
+  Explanation of macros dealing with complex math:
+
+   C_MUL(m,a,b)         : m = a*b
+   C_FIXDIV( c , div )  : if a fixed point impl., c /= div. noop otherwise
+   C_SUB( res, a,b)     : res = a - b
+   C_SUBFROM( res , a)  : res -= a
+   C_ADDTO( res , a)    : res += a
+ * */
+#ifdef FIXED_POINT
+#if (FIXED_POINT==32)
+# define FRACBITS 31
+# define SAMPPROD int64_t
+#define SAMP_MAX 2147483647
+#else
+# define FRACBITS 15
+# define SAMPPROD int32_t 
+#define SAMP_MAX 32767
+#endif
+
+#define SAMP_MIN -SAMP_MAX
+
+#if defined(CHECK_OVERFLOW)
+#  define CHECK_OVERFLOW_OP(a,op,b)  \
+	if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \
+		fprintf(stderr,"WARNING:overflow @ " __FILE__ "(%d): (%d " #op" %d) = %ld\n",__LINE__,(a),(b),(SAMPPROD)(a) op (SAMPPROD)(b) );  }
+#endif
+
+
+#   define smul(a,b) ( (SAMPPROD)(a)*(b) )
+#   define sround( x )  (kiss_fft_scalar)( ( (x) + (1<<(FRACBITS-1)) ) >> FRACBITS )
+
+#   define S_MUL(a,b) sround( smul(a,b) )
+
+#   define C_MUL(m,a,b) \
+      do{ (m).r = sround( smul((a).r,(b).r) - smul((a).i,(b).i) ); \
+          (m).i = sround( smul((a).r,(b).i) + smul((a).i,(b).r) ); }while(0)
+
+#   define DIVSCALAR(x,k) \
+	(x) = sround( smul(  x, SAMP_MAX/k ) )
+
+#   define C_FIXDIV(c,div) \
+	do {    DIVSCALAR( (c).r , div);  \
+		DIVSCALAR( (c).i  , div); }while (0)
+
+#   define C_MULBYSCALAR( c, s ) \
+    do{ (c).r =  sround( smul( (c).r , s ) ) ;\
+        (c).i =  sround( smul( (c).i , s ) ) ; }while(0)
+
+#else  /* not FIXED_POINT*/
+
+#   define S_MUL(a,b) ( (a)*(b) )
+#define C_MUL(m,a,b) \
+    do{ (m).r = (a).r*(b).r - (a).i*(b).i;\
+        (m).i = (a).r*(b).i + (a).i*(b).r; }while(0)
+#   define C_FIXDIV(c,div) /* NOOP */
+#   define C_MULBYSCALAR( c, s ) \
+    do{ (c).r *= (s);\
+        (c).i *= (s); }while(0)
+#endif
+
+#ifndef CHECK_OVERFLOW_OP
+#  define CHECK_OVERFLOW_OP(a,op,b) /* noop */
+#endif
+
+#define  C_ADD( res, a,b)\
+    do { \
+	    CHECK_OVERFLOW_OP((a).r,+,(b).r)\
+	    CHECK_OVERFLOW_OP((a).i,+,(b).i)\
+	    (res).r=(a).r+(b).r;  (res).i=(a).i+(b).i; \
+    }while(0)
+#define  C_SUB( res, a,b)\
+    do { \
+	    CHECK_OVERFLOW_OP((a).r,-,(b).r)\
+	    CHECK_OVERFLOW_OP((a).i,-,(b).i)\
+	    (res).r=(a).r-(b).r;  (res).i=(a).i-(b).i; \
+    }while(0)
+#define C_ADDTO( res , a)\
+    do { \
+	    CHECK_OVERFLOW_OP((res).r,+,(a).r)\
+	    CHECK_OVERFLOW_OP((res).i,+,(a).i)\
+	    (res).r += (a).r;  (res).i += (a).i;\
+    }while(0)
+
+#define C_SUBFROM( res , a)\
+    do {\
+	    CHECK_OVERFLOW_OP((res).r,-,(a).r)\
+	    CHECK_OVERFLOW_OP((res).i,-,(a).i)\
+	    (res).r -= (a).r;  (res).i -= (a).i; \
+    }while(0)
+
+
+#ifdef FIXED_POINT
+#  define KISS_FFT_COS(phase)  floor(.5+SAMP_MAX * cos (phase))
+#  define KISS_FFT_SIN(phase)  floor(.5+SAMP_MAX * sin (phase))
+#  define HALF_OF(x) ((x)>>1)
+#elif defined(USE_SIMD)
+#  define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) )
+#  define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) )
+#  define HALF_OF(x) ((x)*_mm_set1_ps(.5))
+#else
+#  define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase)
+#  define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase)
+#  define HALF_OF(x) ((x)*.5)
+#endif
+
+#define  kf_cexp(x,phase) \
+	do{ \
+		(x)->r = KISS_FFT_COS(phase);\
+		(x)->i = KISS_FFT_SIN(phase);\
+	}while(0)
+
+
+/* a debugging function */
+#define pcpx(c)\
+    fprintf(stderr,"%g + %gi\n",(double)((c)->r),(double)((c)->i) )
+
+
+#ifdef KISS_FFT_USE_ALLOCA
+// define this to allow use of alloca instead of malloc for temporary buffers
+// Temporary buffers are used in two case: 
+// 1. FFT sizes that have "bad" factors. i.e. not 2,3 and 5
+// 2. "in-place" FFTs.  Notice the quotes, since kissfft does not really do an in-place transform.
+#include <alloca.h>
+#define  KISS_FFT_TMP_ALLOC(nbytes) alloca(nbytes)
+#define  KISS_FFT_TMP_FREE(ptr) 
+#else
+#define  KISS_FFT_TMP_ALLOC(nbytes) KISS_FFT_MALLOC(nbytes)
+#define  KISS_FFT_TMP_FREE(ptr) KISS_FFT_FREE(ptr)
+#endif

+ 131 - 0
code/lib/tfmicro/kissfft/kiss_fft.h

@@ -0,0 +1,131 @@
+#ifndef KISS_FFT_H
+#define KISS_FFT_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ ATTENTION!
+ If you would like a :
+ -- a utility that will handle the caching of fft objects
+ -- real-only (no imaginary time component ) FFT
+ -- a multi-dimensional FFT
+ -- a command-line utility to perform ffts
+ -- a command-line utility to perform fast-convolution filtering
+
+ Then see kfc.h kiss_fftr.h kiss_fftnd.h fftutil.c kiss_fastfir.c
+  in the tools/ directory.
+*/
+
+#ifdef USE_SIMD
+# include <xmmintrin.h>
+# define kiss_fft_scalar __m128
+#define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes,16)
+#define KISS_FFT_FREE _mm_free
+#else	
+#define KISS_FFT_MALLOC(X) (void*)(0) /* Patched. */
+#define KISS_FFT_FREE(X) /* Patched. */
+#endif	
+
+
+// Patched automatically by download_dependencies.sh so default is 16 bit.
+#ifndef FIXED_POINT
+#define FIXED_POINT (16)
+#endif
+// End patch.
+
+#ifdef FIXED_POINT
+#include <stdint.h> /* Patched. */
+#include <sys/types.h>	
+# if (FIXED_POINT == 32)
+#  define kiss_fft_scalar int32_t
+# else	
+#  define kiss_fft_scalar int16_t
+# endif
+#else
+# ifndef kiss_fft_scalar
+/*  default is float */
+#   define kiss_fft_scalar float
+# endif
+#endif
+
+typedef struct {
+    kiss_fft_scalar r;
+    kiss_fft_scalar i;
+}kiss_fft_cpx;
+
+typedef struct kiss_fft_state* kiss_fft_cfg;
+
+/* 
+ *  kiss_fft_alloc
+ *  
+ *  Initialize a FFT (or IFFT) algorithm's cfg/state buffer.
+ *
+ *  typical usage:      kiss_fft_cfg mycfg=kiss_fft_alloc(1024,0,NULL,NULL);
+ *
+ *  The return value from fft_alloc is a cfg buffer used internally
+ *  by the fft routine or NULL.
+ *
+ *  If lenmem is NULL, then kiss_fft_alloc will allocate a cfg buffer using malloc.
+ *  The returned value should be free()d when done to avoid memory leaks.
+ *  
+ *  The state can be placed in a user supplied buffer 'mem':
+ *  If lenmem is not NULL and mem is not NULL and *lenmem is large enough,
+ *      then the function places the cfg in mem and the size used in *lenmem
+ *      and returns mem.
+ *  
+ *  If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough),
+ *      then the function returns NULL and places the minimum cfg 
+ *      buffer size in *lenmem.
+ * */
+
+kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem); 
+
+/*
+ * kiss_fft(cfg,in_out_buf)
+ *
+ * Perform an FFT on a complex input buffer.
+ * for a forward FFT,
+ * fin should be  f[0] , f[1] , ... ,f[nfft-1]
+ * fout will be   F[0] , F[1] , ... ,F[nfft-1]
+ * Note that each element is complex and can be accessed like
+    f[k].r and f[k].i
+ * */
+void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout);
+
+/*
+ A more generic version of the above function. It reads its input from every Nth sample.
+ * */
+void kiss_fft_stride(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int fin_stride);
+
+/* If kiss_fft_alloc allocated a buffer, it is one contiguous 
+   buffer and can be simply free()d when no longer needed*/
+#define kiss_fft_free free
+
+/*
+ Cleans up some memory that gets managed internally. Not necessary to call, but it might clean up 
+ your compiler output to call this before you exit.
+*/
+void kiss_fft_cleanup(void);
+	
+
+/*
+ * Returns the smallest integer k, such that k>=n and k has only "fast" factors (2,3,5)
+ */
+int kiss_fft_next_fast_size(int n);
+
+/* for real ffts, we need an even size */
+#define kiss_fftr_next_fast_size_real(n) \
+        (kiss_fft_next_fast_size( ((n)+1)>>1)<<1)
+
+#ifdef __cplusplus
+} 
+#endif
+
+#endif

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů