diff --git a/.gitignore b/.gitignore index 9f05840..b0a2125 100644 --- a/.gitignore +++ b/.gitignore @@ -66,4 +66,5 @@ github_source examples_private* *.sconsign.dblite -github_source/* \ No newline at end of file +github_source/* +**setup.ini \ No newline at end of file diff --git a/examples/lcd_hello_world/.gitignore b/examples/lcd_hello_world/.gitignore new file mode 100644 index 0000000..76b743d --- /dev/null +++ b/examples/lcd_hello_world/.gitignore @@ -0,0 +1,6 @@ + +dist +build +.config.mk +.flash.conf.json + diff --git a/examples/lcd_hello_world/SConstruct b/examples/lcd_hello_world/SConstruct new file mode 100644 index 0000000..076d65c --- /dev/null +++ b/examples/lcd_hello_world/SConstruct @@ -0,0 +1,4 @@ +from pathlib import Path +import os +with open(str(Path(os.getcwd())/'..'/'..'/'tools'/'scons'/'project.py')) as f: + exec(f.read()) diff --git a/examples/lcd_hello_world/config_defaults.mk b/examples/lcd_hello_world/config_defaults.mk new file mode 100644 index 0000000..9bc0dda --- /dev/null +++ b/examples/lcd_hello_world/config_defaults.mk @@ -0,0 +1,10 @@ +# unix +CONFIG_TOOLCHAIN_PATH="/opt/gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf/bin" +# win +# CONFIG_TOOLCHAIN_PATH="..\\gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf\\bin" + +CONFIG_TOOLCHAIN_PREFIX="arm-linux-gnueabihf-" + +CONFIG_MINICV2_COMPONENT_ENABLED=y +CONFIG_DEVICE_DRIVER_ENABLED=y +CONFIG_DEVICE_FRAMEBUFFER_ENABLED=y \ No newline at end of file diff --git a/examples/lcd_hello_world/main/Kconfig b/examples/lcd_hello_world/main/Kconfig new file mode 100644 index 0000000..e69de29 diff --git a/examples/lcd_hello_world/main/SConstruct b/examples/lcd_hello_world/main/SConstruct new file mode 100644 index 0000000..db77bf6 --- /dev/null +++ b/examples/lcd_hello_world/main/SConstruct @@ -0,0 +1,33 @@ +# project_root/src/SConscript +import os +# Import the environment from the SConstruct file +Import('env') +with open(env['PROJECT_TOOL_S']) as f: + exec(f.read()) + + +SRCS = Glob('src/*.c*') +INCLUDE = [ADir('include'), ADir('.')] +PRIVATE_INCLUDE = [] +REQUIREMENTS = ['pthread', 'DeviceDriver', 'minicv2'] +STATIC_LIB = [] +DYNAMIC_LIB = [] +DEFINITIONS = [] +DEFINITIONS_PRIVATE = [] +LDFLAGS = [] +LINK_SEARCH_PATH = [] + + +env['COMPONENTS'].append({'target':env['PROJECT_NAME'], + 'SRCS':SRCS, + 'INCLUDE':INCLUDE, + 'PRIVATE_INCLUDE':PRIVATE_INCLUDE, + 'REQUIREMENTS':REQUIREMENTS, + 'STATIC_LIB':STATIC_LIB, + 'DYNAMIC_LIB':DYNAMIC_LIB, + 'DEFINITIONS':DEFINITIONS, + 'DEFINITIONS_PRIVATE':DEFINITIONS_PRIVATE, + 'LDFLAGS':LDFLAGS, + 'LINK_SEARCH_PATH':LINK_SEARCH_PATH, + 'REGISTER':'project' + }) \ No newline at end of file diff --git a/examples/lcd_hello_world/main/include/main.h b/examples/lcd_hello_world/main/include/main.h new file mode 100644 index 0000000..45dcbb0 --- /dev/null +++ b/examples/lcd_hello_world/main/include/main.h @@ -0,0 +1,3 @@ +#pragma once + + diff --git a/examples/lcd_hello_world/main/src/main.c b/examples/lcd_hello_world/main/src/main.c new file mode 100644 index 0000000..1185ab8 --- /dev/null +++ b/examples/lcd_hello_world/main/src/main.c @@ -0,0 +1,30 @@ +#include +#include +#include "framebuffer/fbtools.h" +#include "imlib.h" + +int main(int argc, char **argv) +{ + FBDEV fbdev; + memset(&fbdev, 0, sizeof(FBDEV)); + strcpy(fbdev.dev, "/dev/fb1"); + if (fb_open(&fbdev) == 0) + { + printf("open frame buffer error/n"); + return -1; + } + int w = fbdev.fb_var.xres; + int h = fbdev.fb_var.yres; + int color = fbdev.fb_var.bits_per_pixel; + + image_t *img = imlib_image_create(w, h, PIXFORMAT_RGB565, w * h * 2, (void*)fbdev.fb_mem, 0); + fb_memset((void*)(fbdev.fb_mem + fbdev.fb_mem_offset), 0, fbdev.fb_fix.smem_len); + + char strs[100]; + sprintf(strs, "hello_world!"); + imlib_draw_string(img, 75, 110, strs, COLOR_R8_G8_B8_TO_RGB565(0xff, 0xff, 0xff), 2, 0, 0, 1, 0, 0, 0, 0, 0, 0); + + imlib_image_destroy(&img); + fb_close(&fbdev); + return 0; +} diff --git a/github_source/minicv2/.gitattributes b/github_source/minicv2/.gitattributes new file mode 100644 index 0000000..fc73bea --- /dev/null +++ b/github_source/minicv2/.gitattributes @@ -0,0 +1,2 @@ +CMakeLists.txt +Kconfig \ No newline at end of file diff --git a/github_source/minicv2/.gitignore b/github_source/minicv2/.gitignore new file mode 100644 index 0000000..d204fed --- /dev/null +++ b/github_source/minicv2/.gitignore @@ -0,0 +1,62 @@ +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf +.vscode-ctags +*tempCodeRunnerFile.c +examples/*/build +test/tmp.* +.vscode/settings.json +test/tmp_img_big.bmp +test/imlib_base_test + +Kconfig +CMakeLists.txt \ No newline at end of file diff --git a/github_source/minicv2/.vscode/settings.json b/github_source/minicv2/.vscode/settings.json new file mode 100644 index 0000000..746304f --- /dev/null +++ b/github_source/minicv2/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "files.associations": { + "array": "c", + "string": "c", + "string_view": "c", + "ranges": "c", + "span": "c" + } +} \ No newline at end of file diff --git a/github_source/minicv2/CMakeLists.txt b/github_source/minicv2/CMakeLists.txt new file mode 100644 index 0000000..f42cad7 --- /dev/null +++ b/github_source/minicv2/CMakeLists.txt @@ -0,0 +1,22 @@ + + +if(CONFIG_COMPONENT_IMLIB_ENABLE) + +list(APPEND ADD_INCLUDE "include") +list(APPEND ADD_INCLUDE "${CMAKE_CURRENT_SOURCE_DIR}/../maix_smart/include") +add_definitions(-DIMLIB_CONFIG_H_FILE="costom_imlib_config.h") + +append_srcs_dir(ADD_SRCS "src") +add_definitions("-lm") +############ Add static libs ################## +# list(APPEND ADD_STATIC_LIB "core/lib/libmaix_nn.a") +############################################### + +# include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../libmaix/include) + +############ Add dynamic libs ################## + +############################################### + +register_component(DYNAMIC) +endif() \ No newline at end of file diff --git a/github_source/minicv2/Kconfig b/github_source/minicv2/Kconfig new file mode 100644 index 0000000..2a67c17 --- /dev/null +++ b/github_source/minicv2/Kconfig @@ -0,0 +1,6 @@ + +config COMPONENT_IMLIB_ENABLE + bool "Enable imlib component" + default y + help + Select this option to enable use imlib. \ No newline at end of file diff --git a/github_source/minicv2/LICENSE b/github_source/minicv2/LICENSE new file mode 100644 index 0000000..69a611f --- /dev/null +++ b/github_source/minicv2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 dianjixz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/github_source/minicv2/Makefile b/github_source/minicv2/Makefile new file mode 100644 index 0000000..e696b58 --- /dev/null +++ b/github_source/minicv2/Makefile @@ -0,0 +1,107 @@ + + +CROSS = +CC = $(CROSS)gcc +CXX = $(CROSS)g++ + +DEBUG = +CFLAGS = $(DEBUG) -Wall -c -fPIC -Wno-comment +LDFLAGS = -fPIC -shared -lm +MV = mv -f +RM = rm -rf +LN = ln -sf + +TARGET = libimlib.so + + +TOP_PATH = $(shell pwd) +SRC_PATH = $(TOP_PATH)/src + +INC_PATH = +INC_PATH += -I$(TOP_PATH)/include + + + +MOD_PATH = $(TOP_PATH)/modules +MOD_LIB_PATH = $(MOD_PATH)/lib +MOD_INC_PATH = $(MOD_PATH)/include + +DIRS = $(shell find $(SRC_PATH) -maxdepth 3 -type d) +FILES = $(foreach dir, $(DIRS), $(wildcard $(dir)/*.cpp)) + + +########################################################## +# modules +########################################################## +modules = +MODULES_PATH = $(foreach m, $(modules), $(MOD_PATH)/$(m)) + + +########################################################## +# srcs +########################################################## +SRCS_CPP += $(foreach dir, $(DIRS), $(wildcard $(dir)/*.cpp)) +SRCS_CC += $(foreach dir, $(DIRS), $(wildcard $(dir)/*.cc)) +SRCS_C += $(foreach dir, $(DIRS), $(wildcard $(dir)/*.c)) + + +########################################################## +# objs +########################################################## +OBJS_CPP = $(patsubst %.cpp, %.o, $(SRCS_CPP)) +OBJS_CC = $(patsubst %.cc, %.o, $(SRCS_CC)) +OBJS_C = $(patsubst %.c, %.o, $(SRCS_C)) + +########################################################## +# paths +########################################################## +INC_PATH += -I$(MOD_INC_PATH) +INC_PATH += -I$(MOD_PATH) +LIB_PATH += -L$(TOP_PATH)/lib +LIB_PATH += -L$(MOD_LIB_PATH) + +########################################################## +# libs +########################################################## + +########################################################## +# building +########################################################## +all:$(TARGET) + +$(TARGET) : $(OBJS_CPP) $(OBJS_CC) $(OBJS_C) + @ for i in $(MODULES_PATH); \ + do \ + make -C $$i; \ + done + + $(CXX) $^ -o $@ $(LIB_PATH) $(LIBS) $(LDFLAGS) + # $(AR) -r libimlib.a $(OBJS_C) + @ echo Create $(TARGET) ok... + +$(OBJS_CPP):%.o : %.cpp + $(CXX) $(CFLAGS) $< -o $@ $(INC_PATH) + +$(OBJS_CC):%.o : %.cc + $(CC) $(CFLAGS) $< -o $@ $(INC_PATH) + +$(OBJS_C):%.o : %.c + $(CC) $(CFLAGS) $< -o $@ $(INC_PATH) + +.PHONY : clean +clean: + @ $(RM) $(TARGET) $(OBJS_CPP) $(OBJS_CC) $(OBJS_C) + # @ $(RM) libimlib.a + @ for i in $(MODULES_PATH); \ + do \ + make clean -C $$i; \ + done + @echo clean all... + +clean_o: + @ $(RM) $(OBJS_CPP) $(OBJS_CC) $(OBJS_C) + @echo clean all... + +rebuild : + make clean + make \ No newline at end of file diff --git a/github_source/minicv2/README.md b/github_source/minicv2/README.md new file mode 100644 index 0000000..b991cd4 --- /dev/null +++ b/github_source/minicv2/README.md @@ -0,0 +1,95 @@ +# openmv核心算法imlib的移植 + + +### 项目信息 + +1、相关函数使用参考代码将会放在reference文件夹内,主要是openmv的参考代码。 + +2、项目结构 +~~~ bash +├── bug_report #bug报告 +├── include +├── LICENSE +├── Makefile +├── readme.md +├── reference #imlib使用参考代码 +├── src #源码文件 +└── test +~~~ + +### 相关工作 + +1、修改内存管理 + +2、做相关指令移植工作 + +3、添加rgb888的支持 + +4、完成imlib的相关库函数 + + + +### 移植要点 + +1、尽量在原openmv imlib库上做兼容修改。 + +2、尽量保持最小的修改。 + +3、尽量保持代码风格统一。 + +4、当需要添加新的模块程序时,如果不是必须,请新建源文件进行添加。 + +5、fb_alloc内存区使用要小心,更改了fb_alloc的内存释放fb_free()接口,方便做程序的移植。 + +以上要点可以在保持原汁原味的opemv风格下,添加我们自己的相关代码。好处是当openmv代码更新时可以最快的速度更新到我们自己代码中, +做到代码共享。 + + +### 移植进度 + +1、完成了xalloc内存管理的移植。(动态内存的移植) + +2、完成了fb_alloc内存管理的移植。(栈内存的移植) + +3、完成了大部分的rgb888的支持添加,对imlib_draw_image()函数的rgb888支持工作还在进行中,目前需要测试后才能继续做支持添加工作。 + +4、正在进行相关测试。测试参考代码将会放在examples文件夹内。 + +5、目前的主要测试工作是rgb888的支持测试,测试imlib库对rgb888支持的一些缺陷,然后做修补工作。 + +6、添加了一些简易函数的实现,方便该库进行单独的移植和使用 + +### 准备进行的工作 + +1、png图片的打开和编解码操作 + +2、jpeg 图片的打开和编解码操作 + + +### 如何使用 + +进入 test 文件。 +~~~ bash +make + +./imlib_base_test +~~~ + + +### 使用注意 + +1、目前imlib库的图片输入输出只进行了bpm的移植。其他的正在移植工作中。 + +2、测试出现问题后,可以写到bug_report文件夹内,作者看到后会进行处理。 + +3、imlib算法内原生支持rgb565,有关rgb565的算法可以直接测试使用。 + +4、目前该库只支持了 bmp 图片格式的实现,其他格式的图片打开请持续关注该项目。 + +5、如果想要参与该项目,可以提交PR或者联系作者,作者将会将其添加到项目管理者内,让合作更加顺畅。 + + + + + + diff --git a/github_source/minicv2/doc/imlib库函数指南.md b/github_source/minicv2/doc/imlib库函数指南.md new file mode 100644 index 0000000..5a23663 --- /dev/null +++ b/github_source/minicv2/doc/imlib库函数指南.md @@ -0,0 +1,56 @@ +- 常用函数列表 +~~~ c +// 库的初始化函数 +void imlib_init_all(); +void imlib_deinit_all(); + +// 创建一个堆区图片 +image_t* imlib_image_create(int w, int h, pixformat_t pixfmt, uint32_t size, void *pixels, bool is_data_alloc); +// 创建一个临时的栈内存图片 +image_t* imlib_image_create_fb_buff(int w, int h, pixformat_t pixfmt, uint32_t size, void *pixels, bool is_data_alloc); +// 释放栈内存图片 +void imlib_image_destroy_fb_buff(image_t **obj); +// 释放堆内存图片 +void imlib_image_destroy(image_t **obj); +// 初始化一张图片 +void imlib_image_init(image_t *ptr, int w, int h, pixformat_t pixfmt, uint32_t size, void *pixels); +// 复制一张图片 +void image_copy(image_t *dst, image_t *src); +// 获取图片的大小 +size_t image_size(image_t *ptr); + + +// 转换图片的格式,兼容截图的功能 +void imlib_pixfmt_to(image_t *dst, image_t *src, rectangle_t *roi_i); + +// 图片的缩放 +void imlib_image_resize(image_t *dst, image_t *src, int hist); + +// 获取图像的某个像素 +int imlib_get_pixel(image_t *img, int x, int y); +//快速获取图像的某个像素,以行为单位 +int imlib_get_pixel_fast(image_t *img, const void *row_ptr, int x); +//设置图片的某个像素 +void imlib_set_pixel(image_t *img, int x, int y, int p); +// 在图像上画线 +void imlib_draw_line(image_t *img, int x0, int y0, int x1, int y1, int c, int thickness); +// 在图像上画框 +void imlib_draw_rectangle(image_t *img, int rx, int ry, int rw, int rh, int c, int thickness, bool fill); +// 在图像上画圆 +void imlib_draw_circle(image_t *img, int cx, int cy, int r, int c, int thickness, bool fill); +// 在图像上画椭圆 +void imlib_draw_ellipse(image_t *img, int cx, int cy, int rx, int ry, int rotation, int c, int thickness, bool fill); +// 在图像上写字 +void imlib_draw_string(image_t *img, int x_off, int y_off, const char *str, int c, float scale, int x_spacing, int y_spacing, bool mono_space, + int char_rotation, bool char_hmirror, bool char_vflip, int string_rotation, bool string_hmirror, bool string_hflip); +// 快速的贴图 +void imlib_draw_image_fast(image_t *img, image_t *other, int x_off, int y_off, float x_scale, float y_scale, float alpha, image_t *mask); +// 在图像上画十字标 +void imlib_draw_cross(image_t *img, int x, int y, int c, int size, int thickness); + + + + + + +~~~ \ No newline at end of file diff --git a/github_source/minicv2/include/arm_compat.h b/github_source/minicv2/include/arm_compat.h new file mode 100644 index 0000000..28a6f20 --- /dev/null +++ b/github_source/minicv2/include/arm_compat.h @@ -0,0 +1,206 @@ +#ifndef __ARM_COMPAT__H +#define __ARM_COMPAT__H +#ifdef __cplusplus +extern "C" +{ +#endif + +// typedef __uint8_t uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +// typedef __uint64_t uint64_t; +#ifndef __STATIC_FORCEINLINE + #define __STATIC_FORCEINLINE __attribute__((always_inline)) static inline +#endif +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + + + +#else //__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + + +// Reverse the bit order in a 32-bit word. +__STATIC_FORCEINLINE uint32_t __RBIT( + uint32_t i ) +{ + i = ( ( i & 0x55555555 ) << 1 ) | ( ( i >> 1 ) & 0x55555555 ); + i = ( ( i & 0x33333333 ) << 2 ) | ( ( i >> 2 ) & 0x33333333 ); + i = ( ( i & 0x0f0f0f0f ) << 4 ) | ( ( i >> 4 ) & 0x0f0f0f0f ); + i = ( i << 24 ) | ( ( i & 0xff00 ) << 8 ) // + | ( ( i >> 8 ) & 0xff00 ) | ( i >> 24 ); + return i; +} + +// Reverse byte order in each halfword independently +// converts 16-bit big-endian data into little-endian data +// or 16-bit little-endian data into big-endian data +__STATIC_FORCEINLINE short __REV16( + short s ) +{ + return __builtin_bswap16(s); + // return ( s << 8 ) | ( s >> 8 ); +} + +// Reverse byte order in a word +// converts 32-bit big-endian data into little-endian data +// or 32-bit little-endian data into big-endian data. +__STATIC_FORCEINLINE uint32_t __REV32( + uint32_t i ) +{ + return __builtin_bswap32(i); + // return ( i & 0x000000FFU ) << 24 | ( i & 0x0000FF00U ) << 8 + // | ( i & 0x00FF0000U ) >> 8 | ( i & 0xFF000000U ) >> 24; +} + +__STATIC_FORCEINLINE uint32_t __SSUB16(uint32_t op1, uint32_t op2) +{ + return ((op1 & 0xFFFF0000) - (op2 & 0xFFFF0000)) | ((op1 - op2) & 0xFFFF); +} + +__STATIC_FORCEINLINE uint32_t __UXTB_RORn(uint32_t op1, uint32_t rotate) +{ + return (op1 >> rotate) & 0xFF; +} + +#define __CLZ (uint8_t)__builtin_clz + +#define __PKHBT(ARG1,ARG2,ARG3) ( ((((uint32_t)(ARG1)) ) & 0x0000FFFFUL) | \ + ((((uint32_t)(ARG2)) << (ARG3)) & 0xFFFF0000UL) ) + +#define __PKHTB(ARG1,ARG2,ARG3) ( ((((uint32_t)(ARG1)) ) & 0xFFFF0000UL) | \ + ((((uint32_t)(ARG2)) >> (ARG3)) & 0x0000FFFFUL) ) + +__STATIC_FORCEINLINE uint32_t __SMLAD (uint32_t op1, uint32_t op2, uint32_t op3) +{ + uint32_t result; + uint16_t *op1_s = (uint16_t *) &op1, *op2_s = (uint16_t *) &op2; + + result = op1_s[0] * op2_s[0]; + result += op1_s[1] * op2_s[1]; + result += op3; + + return result; +} + +__STATIC_FORCEINLINE uint32_t __SMUAD (uint32_t op1, uint32_t op2) +{ + uint32_t result; + uint16_t *op1_s = (uint16_t *) &op1, *op2_s = (uint16_t *) &op2; + + result = op1_s[0] * op2_s[0] + op1_s[1] * op2_s[1]; + + return result; +} + +__STATIC_FORCEINLINE uint32_t __QADD16(uint32_t op1, uint32_t op2) +{ + uint32_t result; + uint16_t *op1_s = (uint16_t *) &op1, *op2_s = (uint16_t *) &op2, *result_s = (uint16_t *) &result; + + result_s[0] = op1_s[0] + op2_s[0]; + result_s[1] = op1_s[1] + op2_s[1]; + return(result); +} + +__STATIC_FORCEINLINE uint32_t __SMLADX (uint32_t op1, uint32_t op2, uint32_t op3) +{ + uint32_t result; + uint16_t *op1_s = (uint16_t *) &op1, *op2_s = (uint16_t *) &op2; + + result = op1_s[0] * op2_s[1]; + result += op1_s[1] * op2_s[0]; + result += op3; + + return result; +} + +#define __USAT_ASR(ARG1,ARG2,ARG3) \ +({ \ + uint32_t __RES, __ARG1 = (ARG1), __ARG2 = 1<>ARG3; \ + if(__ARG1 >= __ARG2) __RES = __ARG2; \ + else __RES = __ARG1; \ + __RES; \ + }) + +float arm_cos_f32(float x); +float arm_sin_f32(float x); +#define __USAT(val1, val2) \ +({\ + __typeof__ (val1) _val1 = val1;\ + __typeof__ (val2) _val2 = val2;\ + ((uint32_t)((0xffffffff >> (32 - _val2)) & _val1));\ +}) +#define __USAT16(val1, val2) \ +({\ + __typeof__ (val1) _val1 = val1;\ + __typeof__ (val2) _val2 = val2;\ + ((_val1 & ((0xffff >> (16 - _val2)) << 16)) | (_val1 & (0xffff >> (16 - _val2))));\ +}) +#endif //__BYTE_ORDER__ == __ORDER_LITTER_ENDIAN__ + +#ifdef __cplusplus +} +#endif + +#endif //__ARM_COMPAT__H + + + +#ifdef _CC_ARM_asdxasxsaadsadsaxasadasdsadsads +//备份宏定义 +#define __SMLAD(x, y, sum) \ +({\ + __typeof__ (x) __x = x;\ + __typeof__ (y) __y = y;\ + __typeof__ (sum) __sum = sum;\ + ((uint32_t)(((((int32_t)__x << 16) >> 16) * (((int32_t)__y << 16) >> 16)) + ((((int32_t)__x) >> 16) * (((int32_t)__y) >> 16)) + ( ((int32_t)__sum))));\ +}) + +#define __SMUAD(val1, val2) \ +({\ + __typeof__ (val1) _val1 = val1;\ + __typeof__ (val2) _val2 = val2;\ + ((uint32_t)(((((int32_t)_val1 << 16) >> 16) * (((int32_t)_val2 << 16) >> 16)) + ((((int32_t)_val1) >> 16) * (((int32_t)_val2) >> 16))));\ +}) + + +#define __QADD16(val1, val2) \ +({\ + __typeof__ (val1) _val1 = val1;\ + __typeof__ (val2) _val2 = val2;\ + ((uint32_t)((((((int32_t)_val1 << 16) >> 16) + (((int32_t)_val2 << 16) >> 16))) | (((((int32_t)_val1) >> 16) + (((int32_t)_val2) >> 16)) << 16)));\ +}) + +#define __USAT(val1, val2) \ +({\ + __typeof__ (val1) _val1 = val1;\ + __typeof__ (val2) _val2 = val2;\ + ((uint32_t)((0xffffffff >> (32 - _val2)) & _val1));\ +}) + +#define __USAT16(val1, val2) \ +({\ + __typeof__ (val1) _val1 = val1;\ + __typeof__ (val2) _val2 = val2;\ + ((_val1 & ((0xffff >> (16 - _val2)) << 16)) | (_val1 & (0xffff >> (16 - _val2))));\ +}) + +#define __SSUB16(val1, val2) \ +({\ + __typeof__ (val1) _val1 = val1;\ + __typeof__ (val2) _val2 = val2;\ + ((((_val1 >> 16) - (_val2 >> 16)) << 16) | ((_val1 & 0xffff) - (_val2 & 0xffff)));\ +}) + +#define __REV16(_x) __builtin_bswap16(_x) + + +#define __CLZ(val1) \ +({\ + __typeof__ (val1) _val1 = val1;\ + uint32_t tmp_0 = 0, tmp_1 = 0x80000000, tmp_2 = 0;\ + if(_val1 == 0){tmp_2 = 32;}else{for(tmp_0 = 0; tmp_0 < 32; tmp_0 ++){if(_val1 & tmp_1){break;}else{tmp_2 ++;tmp_1 = tmp_1 >> 1;}}}\ + tmp_2;\ +}) +#endif //_CC_ARM_asdxasxsaadsadsaxasadasdsadsadsaxsa \ No newline at end of file diff --git a/github_source/minicv2/include/array.h b/github_source/minicv2/include/array.h new file mode 100644 index 0000000..1217b7e --- /dev/null +++ b/github_source/minicv2/include/array.h @@ -0,0 +1,44 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Dynamic array. + */ +#ifndef __ARRAY_H__ +#define __ARRAY_H__ +#ifdef __cplusplus +extern "C" +{ +#endif +typedef void (*array_dtor_t)(void*); +typedef int (*array_comp_t)(const void*, const void*); +// (left < right) == negative +// (left == right) == zero +// (left > right) == positive +typedef struct { + int index; + int length; + void **data; + array_dtor_t dtor; +} array_t; +void array_alloc(array_t **a, array_dtor_t dtor); +void array_alloc_init(array_t **a, array_dtor_t dtor, int size); +void array_clear(array_t *array); +void array_free(array_t *array); +int array_length(array_t *array); +void *array_at(array_t *array, int idx); +void array_push_back(array_t *array, void *element); +void *array_pop_back(array_t *array); +void *array_take(array_t *array, int idx); +void array_erase(array_t *array, int idx); +void array_resize(array_t *array, int num); +void array_sort(array_t *array, array_comp_t comp); +void array_isort(array_t *array, array_comp_t comp); +#ifdef __cplusplus +} +#endif +#endif //__ARRAY_H__ diff --git a/github_source/minicv2/include/cascade.h b/github_source/minicv2/include/cascade.h new file mode 100644 index 0000000..c6f14cf --- /dev/null +++ b/github_source/minicv2/include/cascade.h @@ -0,0 +1,30 @@ +#ifdef __cplusplus +extern "C" +{ +#endif +const int frontalface_window_w=24; +const int frontalface_window_h=24; +const int frontalface_n_stages=25; +const uint8_t frontalface_stages_array[]={9, 16, 27, 32, 52, 53, 62, 72, 83, 91, 99, 115, 127, 135, 136, 137, 159, 155, 169, 196, 197, 181, 199, 211, 200}; +const int16_t frontalface_stages_thresh_array[]={-1290, -1275, -1191, -1140, -1122, -1057, -1029, -994, -983, -933, -990, -951, -912, -947, -877, -899, -920, -868, -829, -821, -838, -849, -833, -862, -766}; +const int16_t frontalface_tree_thresh_array[]={-129, 50, 89, 23, 61, 407, 11, -77, 24, -86, 83, 87, 375, 148, -78, 33, 75, -28, -40, 64, -84, -563, 58, 41, 374, 285, 129, 58, 59, -12, 134, -29, 206, 192, -284, -200, 347, -7, 473, -210, -174, 1522, 79, 71, 162, -37, 7, 123, -322, 8, 110, -184, -269, 64, 596, 25, 27, 75, 81, -1136, 37, -154, 75, -45, 138, -146, -46, -267, -173, 7, -529, 93, -139, 107, 91, -23, 178, 234, 9, 53, -108, -23, -67, -279, 163, 770, 319, 0, 348, 36, 36, -96, 28, 138, -13, 119, -34, -44, -100, 15, -50, -19, 314, 117, 80, -119, -119, 80, 17, -145, -66, -90, -93, 68, -54, -138, 69, 13, 342, 1056, -149, -67, -15, -26, -15, -186, -98, -317, 96, -10, 491, 9, 285, -191, -205, 123, 373, 52, 65, 9, 130, 11, -49, 87, 124, -184, -293, 242, 27, 168, -3, -124, -52, 153, 100, 233, -66, -722, 721, -30, 249, -119, -186, 152, -99, -244, -123, 30, -8, 85, -27, 76, -181, 93, -4, 70, -141, 274, 973, -52, 43, 69, -29, 43, 25, 53, 12, -447, 33, 128, 130, 27, 107, 52, 107, -61, -159, -23, -6, -116, 271, 36, 46, -11, 46, 29, 130, 103, 30, 134, -11, -155, -159, 11, -221, -34, 138, -460, -42, -20, -38, -48, -95, 69, -98, -151, -252, 88, -15, 183, 234, -46, -49, 92, -81, 65, -37, -18, 521, 195, 219, -162, -275, 546, -856, -268, 253, -104, -142, -74, 61, 189, 63, 52, 201, 51, -76, 171, -210, -290, 68, -25, -161, 0, -91, 7, 4, 160, 254, 8, 3, -28, -97, -420, -39, 163, -53, -207, 102, -31, 175, 0, 37, 45, -214, -942, -67, -70, -150, -42, -56, 120, 98, 25, -91, -28, -166, -100, 10, -80, -121, -61, -248, -52, -82, -125, -84, -7, -128, 77, 25, -41, -5, -16, -180, -248, -134, -603, -48, 594, 210, 12, -178, 528, -373, 58, 134, 51, 60, -137, 583, -25, 74, 102, 190, -36, 167, -140, -162, 10, 112, 143, 18, 11, 144, 106, -64, -31, 85, 245, 159, 88, -112, 42, 101, -65, 199, 5, -360, 75, 144, -835, -68, 154, 9, -60, -197, -120, -189, -114, -23, -41, 46, 212, 136, -59, -140, -330, -3, 397, 149, 211, -100, 1340, 31, 662, -19, -75, 318, 77, -325, -278, -24, 130, -122, -329, 15, 137, 33, 413, -40, 29, 102, 1143, -181, -57, 564, 141, 76, 102, 234, 61, 36, 124, -180, 75, 43, -188, 339, -36, 175, -35, -17, 33, 396, -125, -249, -156, -39, 200, -170, -82, -4, -137, 79, -1, -1, -382, -318, 69, -87, -52, 32, 421, -153, 104, 2, -1182, 373, 493, -302, -135, -179, 741, -48, 18, 28, -97, -275, -267, 93, -77, -28, -164, -166, -50, -111, -361, -32, -171, 187, -577, -242, 17, -8, 1127, -108, 167, 22, 130, -169, -393, -47, 75, -139, -100, 200, -84, -94, 264, 51, -49, -108, -104, 160, -24, -139, 166, 104, 817, 50, 160, -126, -145, -252, -48, 274, -84, -91, 4, 146, 125, 22, -25, -124, -39, -233, 16, 138, -141, 192, -35, 268, -180, 70, 135, -86, 121, 226, -137, 80, -85, 133, -44, -40, -15, -171, -140, 41, -368, 106, -15, 130, 79, 7, -180, -183, -440, -526, -183, -180, -502, -81, -63, -200, 229, -40, 55, 26, 29, 19, 39, -112, -161, -125, -6, 781, 21, 98, -108, 22, 222, 0, 62, 69, 124, 26, 580, 79, -70, -25, -65, -414, -30, 181, -476, 19, 91, -49, 229, -35, 27, -74, -93, 52, -56, 128, 381, 106, 67, -7, -36, 92, -154, -22, -97, -108, 50, 395, -112, -64, -8, 49, -63, -17, -86, -69, -167, -33, -78, -181, -255, -4, 97, 87, 82, -117, 14, 233, -384, 72, 935, -749, -286, 62, 27, -65, 53, 53, -163, 61, -84, -91, -32, 62, -129, -126, -63, 144, -73, -13, 64, 122, 12, 347, -240, 183, 165, 154, 248, -81, -679, 282, 46, 6, 326, -234, 30, -73, 387, 22, 28, 141, -212, -283, -22, 280, -274, -86, 83, -192, 768, -177, 81, 33, 111, -375, -51, 60, 119, 35, -224, -60, 102, 190, 72, 668, 53, -64, 329, 144, 135, 49, 176, 124, 145, -59, 51, 41, 118, 2, 198, 132, 136, 26, -23, 52, 24, 10, -69, 115, 42, 40, 106, -104, -14, 37, 86, -209, -255, -135, -153, 508, -36, -245, 25, -72, 72, 21, -43, 855, -108, 241, -47, 188, -93, -33, 14, 202, 14, -126, 354, -559, -23, -73, -81, -235, -340, -220, -34, 226, -275, -97, 22, 87, -100, -80, -218, 29, -92, -337, 536, 58, 26, -188, 236, -24, -213, 190, 30, 88, -73, -152, -1, 102, 38, 132, -25, 210, -108, -63, 79, 137, 118, 0, -201, 313, 97, 15, -366, -61, -45, 387, 2254, 169, 101, 208, -69, -498, -14, 474, 151, 47, -82, -117, -23, -227, -60, -29, -184, 263, -60, 184, -4, 202, 119, 142, -25, 63, 11, -219, -78, -226, 230, -97, 7, -154, -98, 112, 473, -91, 54, -15, -10, 13, 154, -56, -11, -157, -142, 95, 143, -54, 52, 14, 412, 0, 47, -147, -86, 60, -21, 96, -102, -3, -165, 115, 187, 162, 206, -70, 328, 400, -63, -62, -67, -107, 36, -110, 31, -65, 85, 350, 97, -160, -319, -69, 486, 639, -188, -42, 392, 56, 9, 136, -136, 11, -269, 8, 91, -235, 27, 50, -33, 150, -1647, -90, -53, -52, 88, 48, -80, 263, 446, -139, -15, -44, -47, 106, 17, -195, 1, 472, 65, 231, -43, 508, -22, 48, -176, -135, -87, -50, -69, -10, -184, 159, 27, -67, 25, 187, 16, 0, 29, -204, -102, 126, 189, -13, -99, 49, 53, 242, -168, -344, 182, 100, -17, 100, -348, 89, -68, 133, 10, 226, -435, -32, 309, -380, 202, -48, 351, 331, -138, 63, 224, 87, 32, -153, 652, -282, -138, -259, 30, -39, -535, 235, -29, 127, 146, -129, -79, -29, 33, -178, 108, 131, -295, 128, -1, 11, 134, -59, 155, 11, -170, -101, 41, -85, 91, -152, -43, 227, 88, 0, 59, 441, 147, -16, 85, -122, 106, 43, 35, 87, 305, 19, 7, 4, 115, -133, 92, -88, 31, 59, 114, 23, -40, -16, -92, -162, -71, 36, -32, 110, -84, -294, -110, -194, -446, 55, -27, -16, -154, 35, -131, 239, -167, -81, -18, 68, 38, -80, 44, 155, 67, -81, 45, 21, -45, -43, 431, 224, 72, -127, -234, -46, 125, 7, 46, 333, 219, -98, 27, -132, 155, 63, -181, -94, 79, 425, -77, 158, 93, -128, 39, -201, -161, 196, 210, 58, -375, 26, 146, 207, -59, -158, -165, 97, 35, -544, 40, 20, -250, -1, 13, 86, 30, 101, -145, 81, 61, -94, -76, 1846, 48, -101, -183, -59, -100, 94, -102, 4, 63, -109, 5, -2, -130, -20, 127, -137, 49, -142, 40, 244, -267, -380, -168, 87, -104, -168, -72, 36, -47, -30, 3, -125, -77, -33, -142, 77, -77, -364, 28, -115, -1, -443, 65, 35, -103, -55, -31, 293, -55, 12, -208, -36, 877, 57, 174, 81, -137, 260, 89, -321, 58, -275, 534, -189, -122, -1, -91, -6, 49, 99, -193, -101, 89, 770, -318, -199, -70, -11, -404, -89, 250, -100, 138, 156, -82, 101, -99, -108, -14, 438, 184, 181, 4, 292, 146, -85, 1741, 46, -62, -62, -77, -13, 381, -51, -110, -96, -58, 115, 208, 47, -60, 935, 454, 13, 349, 90, -64, 1356, 36, 188, -154, -335, 891, 60, 214, 37, 32, -106, -12, 234, -25, -165, -83, -70, -99, 232, 1, 40, -215, -56, -124, -1230, -147, -225, 138, -33, -22, 12, 219, -513, 379, 157, -8, 39, 98, -73, -43, -29, 98, -75, 64, -199, 27, 40, 60, 397, 197, 40, -163, 93, 27, 244, 28, 64, -203, 214, 91, 168, -88, -339, 34, 323, -369, -119, 28, -33, 80, -60, 103, -64, 120, -34, 100, -138, -8, 124, 16, 113, 32, 180, -132, 85, 103, 26, -239, 130, -124, 61, -200, 340, 97, 67, -48, 0, 78, -41, -57, -422, -391, -169, 9, 439, 13, 119, 46, -49, -52, 100, 188, -111, 164, 94, -97, 317, -54, -88, -292, -22, 109, -161, 106, 200, 151, 323, 118, 25, -269, -282, -477, -5, -182, 209, -129, 86, -566, 213, 106, -49, -99, -103, 51, 234, 68, -93, 0, -31, 385, -255, 71, -90, -42, -38, -118, -86, -151, 43, 670, 388, 144, 52, 569, 48, -40, -24, -5, 132, -57, 4, 0, -1, 16, 58, -226, 383, 109, 15, -130, -92, 103, -127, -108, -56, -257, -183, -83, -32, 35, -111, -67, -56, 119, 153, -102, -261, -38, -3, -89, -73, -101, 643, 282, -45, -56, -126, 87, 381, 121, 0, -172, -92, -52, 114, -113, -25, -83, -50, -165, 121, 28, 66, 205, 8, 102, -64, 152, -324, -70, 134, -481, 493, 17, -297, 725, 34, -53, 77, 87, 259, -132, -96, 76, 127, -45, -52, -52, 281, 21, -158, 25, 717, 476, -94, -210, 920, 38, -485, 154, 90, -148, -540, -170, -135, 64, -161, -277, -109, 163, 412, -331, -87, -43, 3, 14, 77, -104, -16, -3, -202, 47, 141, -33, -91, -126, 179, 176, 111, 38, 386, 697, -193, 458, -58, 139, 88, 89, 337, 346, -225, -265, -93, 224, 0, 402, -29, 205, -23, 57, 87, -119, 1, 7, 35, 260, -114, 200, -120, 508, 32, 124, 103, 41, -68, -11, 173, -198, 118, -164, -168, 48, -87, -97, 73, -178, -37, 194, -58, 15, 14, -119, -26, -123, 32, 36, 393, -134, -54, 62, 49, -312, -49, 89, -11, -199, -42, -27, 35, 81, 90, -213, 80, 94, -61, -204, -283, 19, -138, -66, -205, 233, 167, -12, -133, 403, -156, -188, -489, -493, 289, 34, 93, 2, 141, -18, 96, 52, -46, -170, -382, -111, -89, -39, 284, 127, -203, -83, -62, -207, -84, -126, -18, -187, 68, 13, 100, -326, 182, -513, 73, 78, 163, 55, 66, 45, 160, -39, 114, -96, 110, 1, -168, 27, 196, -12, -35, -30, -7, -353, 191, 0, -66, 187, -112, -113, 31, -2, 452, 281, 7, 787, 644, -202, 212, 204, -174, -153, -152, 57, -1, 131, -17, 40, 382, 70, 34, -57, -31, 114, -77, -76, -149, 132, 244, 40, -144, 11, 33, 364, -123, -89, 154, 11, -43, 531, -72, -315, -78, -209, 8, 104, -97, -26, -154, 886, -54, 291, 229, 165, 258, 42, 256, -161, -22, 441, 69, 127, -94, -45, -19, -71, 77, 29, 77, 127, 85, 46, -233, 295, -81, -68, -163, 110, -16, 93, -282, 176, 35, 59, -47, -449, 185, -110, 73, 206, -122, 155, 760, -16, 41, -47, -26, 43, -83, 9, -6, 35, -99, 304, 69, -100, 123, 49, 355, -173, -10, -232, 96, -85, 29, 1399, 25, 133, 0, 2, 223, -41, -77, -21, -44, -204, 49, -9, 12, 16, -30, 212, 75, 716, 221, -1312, -110, 317, 97, 47, 133, -181, -239, 79, -183, -247, 47, 114, 267, 39, 10, 130, 135, 194, -80, -224, -92, 438, -149, 57, 85, 201, 148, 168, 64, -66, -12, -564, -39, -101, -571, -336, 15, -27, -65, -208, 68, 65, 14, -352, 135, -16, -98, 35, -113, -796, -445, -79, 12, 242, -222, -161, 337, -30, 30, 28, -63, -11, -289, -47, 2, -151, -133, -306, 169, -118, 189, 1041, 9, -339, -46, -528, 157, 417, -78, -248, 101, 109, 61, 107, -153, -21, 72, -139, -65, 80, -424, -78, -52, -66, -522, 78, 133, 38, 20, 169, -312, -298, 244, 83, -328, -73, 46, -104, -3, -59, 35, 224, -443, 94, 11, -8, -92, 340, -27, 313, 22, -42, 113, -95, -227, -166, -30, 69, -151, -80, -96, -177, -90, 67, -134, 292, 3, -34, -70, -76, -37, 75, -206, -96, -111, 26, 95, 53, -27, -92, -261, -204, 27, -228, 1308, 331, -61, 191, 24, -140, -143, 12, -57, -27, -216, -8, 75, 51, 52, -73, 7, -60, -61, 59, -44, -37, 18, 96, 130, -75, 80, 1685, -170, -42, 50, -35, 66, -42, -50, -206, 202, -168, 4, -205, -35, -205, 418, -58, 42, -48, 295, -77, -19, -238, 4, -202, -487, -74, -32, 212, 273, -56, -72, -172, -55, -45, -503, 195, 130, 17, -251, -11, -280, 424, 64, -40, -36, -261, 159, -163, 206, 189, 254, -265, 112, 1, -17, 193, 51, 188, 813, 68, 8, 91, -56, -31, -54, 200, 83, -68, -693, -464, -318, -63, -270, 34, 145, -159, -40, -94, 12, 53, 60, -246, 212, 101, -49, -404, 481, -77, -116, 53, -477, -15, 127, 103, -115, 149, -296, -170, 195, 269, 56, -113, -65, 303, -3, 73, -10, -37, 201, -125, 410, 13, 145, 1, 103, -21, 6, -66, -121, -6, -221, -271, 114, 118, -83, 50, 177, 762, 130, 57, -25, -22, 68, 106, -109, -69, 24, -11, -179, 211, 33, -216, 215, -51, 47, -97, -252, -7, 144, -75, -157, 408, 345, 164, 241, 612, 2, -136, 38, 176, -276, -1276, 121, 43, -118, -23, 116, -118, 102, 49, -174, 42, -283, -19, -57, -62, -41, -208, 125, -45, -25, 321, -41, 127, 164, 66, -186, -74, -57, -158, 129, -44, 49, 289, 2176, -60, -9, 204, -195, -374, 155, -63, -63, -235, -24, -286, -102, 70, -181, 180, 65, -379, 290, 236, -67, 98, 51, -222, -54, 25, 118, -90, 21, 352, -35, 27, -26, 36, 13, 169, -27, 125, -30, 364, 29, -74, -105, 447, -46, -235, 420, 110, -55, -1317, 837, -288, 154, -287, 258, 149, 16, -201, -293, -155, -12, 79, 46, -137, 376, 15, 52, -586, -396, -36, 65, 288, -155, 2113, -134, -148, 27, -66, 34, -563, 724, 32, 449, -124, -94, -12, -136, 54, 60, -54, -66, -118, -415, 154, -1169, 629, 0, -84, 153, 234, 20, -223, 103, 99, 147, -409, 345, 65, 138, -253, 286, -114, -52, 88, 411, 106, 116, 158, -190, -175, 15, 173, 80, 3, -17, 69, 147, -290, -258, 121, 155, -136, -129, 4, -293, -332, 18, -172, -268, 74, -211, -193, 71, -103, -166, -154, -54, 0, -46, 152, 13, -92, 95, -57, 30, -47, 215, 215, -48, 392, -65, 142, 142, 66, -181, -22, -269, -300, 67, -37, 24, -3, 841, -69, -78, -106, -89, -98, 193, -188, 108, -199, -76, 51, -4, -201, -71, -60, -938, -520, 42, 28, 1188, -975, 255, 19, -113, -69, -203, -306, 131, -386, -63, -16, 12, -41, -158, 141, -19, 2, 144, -96, -7, -68, 2705, 449, 55, -93, -335, -215, -103, -179, -74, 96, 140, 105, -108, 249, 592, 218, 46, -9, -121, 111, -14, -51, -363, -78, -68, 52, -55, 77, -26, -99, -121, 20, -23, 68, 156, -233, -220, -10, 1217, -364, -230, 151, -34, -9, -293, 21, -25, 63, 106, -49, -277, -60, 102, 77, -87, 38, 940, -155, -55, 148, 27, 395, -146, 44, 324, 134, -113, -16, 30, 459, -486, -170, -114, -512, 969, -120, 154, 295, 40, 213, -179, -157, -404, -499, -490, 126, 44, 232, 4, -115, -655, 20, 192, 99, 287, 40, -230, 449, 85, 143, 163, -19, 9, 103, -131, 308, -75, -52, -108, 90, 600, 14, 38, -35, -160, 101, -143, -75, -55, 25, -75, 58, -133, -10, -3, 194, -28, -176, 84, -91, 204, 253, -171, -13, 99, -70, -16, -58, -37, -506, -336, 268, -129, -326, -77, -20, -50, 5, 121, 115, 124, -70, -344, 30, 231, -21, -61, 224, -80, -275, -58, 122, 212, 168, -526, 9, 31, 186, -322, 32, -55, 118, -112, -298, -57, 177, 120, -130, 155, -91, 241, 127, 153, -85, -104, -29, -208, -84, 43, 130, -97, -24, 97, 114, 59, 445, -57, 16, -20, -348, 8, 1490, 904, -66, -197, 71, -140, -18, 528, 124, 180, 12, -107, -114, 48, 6, -14, -129, -131, 636, 360, -6, 38, 152, 328, -3, -20, 489, -18, -121, 109, 181, -99, 80, 22, -950, -104, -26, 16, -146, -58, -517, 281, 351, 63, 332, 75, -353, 296, -320, 396, -163, -39, 1, 49, -85, 237, 0, -70, 125, -3, 360, -159, 328, 161, 84, -274, 191, 321, 271, 123, 70, 82, 135, -60, -42, -117, -19, 1318, -69, -30, -122, -46, 19, 20, 792, 22, -279, -143, 20, 390, -257, -697, 43, -170, 520, 338, 349, 227, 18, 53, 237, -93, 197, 105, 28, -141, 120, -9, -392, 68, 106, 1, -27, 77, 0, -312, 205, -11, 66, 154, -50, 237, 19, 187, 87, 642, -42, 9, -95, -28, -140, -86, 8, -17, -58, -33, -38, -155, 19, -18, 21, -39, 184, 58, 670, 10, -15, -103, -79, 59, 211, -155, -121, -160, -119, -342, 1720, 245, -77, -24, -238, -50, 190, 4, -363, -94, 176, 0, 36, -72, 25, 93, -88, 252, -319, 46, -104, -155, 40, -56, 34, -292, 40, 450, 144, -457, -465, 68, -32, -135, 51, -172, 103, -99, -50, -466, -347, -100, -36, 45, -120, 26, 57, -54, 1164, -971, -457, 523, -257, 71, 5, 112, -178, 45, 85, -91, 133, 50, 34, 153, -57, 233, 20, -100, -46, 141, 99, -32, 143, 18, -340, -57, 5, -68, -314, -969, -411, 5, 90, -460, 67, 278, 65, 19, 27, 19, 10, 11, -123, 58, -247, -81, 127, 74, 4, -150, 49, 306, -961, 577, 25, -234, -226, -88, 105, -53, 9, 36, -36, 16, 102, -24, 17, -138, 182, -167, 161, -288, 146, -175, -86, -644, 32, 96, 305, -2, -66, -135, 199, 9, 185, 438, -165, 130, -235, 55, 292, -61, -41, 15, 66, -164, 110, 214, -78, -15, 310, -90}; +const int16_t frontalface_alpha1_array[]={534, -477, -386, -223, -199, 142, -432, -378, -219, 318, -414, -497, -142, 68, -684, -277, -90, 237, 296, -107, 373, 286, -89, -155, 99, -259, -421, 118, -167, -357, -129, 93, -77, -103, 269, -416, 72, -259, -42, 388, 451, -80, -25, -103, 43, 227, -95, 16, -447, -240, -13, -468, 295, -400, -147, -373, -213, -80, -111, 381, -246, -626, 44, 124, 45, -501, 253, -660, 368, -126, -596, -216, -369, 46, 17, 100, 37, 63, -193, -93, -594, 108, 284, -851, -311, -123, -276, -307, -112, -47, 77, 319, -152, 72, 123, 68, -335, 116, -443, -49, -412, 190, -68, -15, -89, -268, 211, 52, 52, -332, -335, -269, -351, -9, -255, 370, -95, -147, 4, -20, -294, 95, 67, 193, 57, -323, 222, -355, 16, -137, -90, -150, -85, 178, 220, 49, -228, -322, -220, -191, -323, -251, 164, -61, -87, 281, 402, -70, -280, 78, 66, -315, 104, -24, -105, 64, -240, 318, -83, 89, 14, -262, 263, 55, -408, -263, -378, -61, 74, -59, -309, 62, -350, 54, 83, -72, -591, 73, -69, -392, 19, 36, -282, 3, -88, 51, -104, -569, -73, -227, -285, -258, 66, -146, -141, -329, 446, -269, 145, 334, -118, -106, 92, -228, 75, -203, 39, 8, -100, 22, 141, -473, -123, -115, -216, 90, 47, -320, -208, -237, 144, 205, -217, -103, -391, 161, 150, -65, 74, -101, 53, 112, 240, 2, -259, -96, -206, -270, 51, -97, 54, -262, -263, -53, 225, 267, 35, -425, 204, -245, 50, -265, -315, -194, -99, -183, 141, -114, -279, 214, -65, 80, -268, 41, -176, 63, -129, 10, 36, -229, -116, 86, -202, -584, 100, 8, -277, -481, 37, -260, 39, -197, -29, 17, -450, 245, 119, 181, -281, -279, -67, -56, 47, -237, 502, 54, -300, -287, -43, 211, -295, -268, -279, 108, -235, -408, -169, 49, -162, -48, -27, -276, 87, 121, 249, -556, -164, -377, 108, 6, 40, -103, -510, -159, 259, -262, -291, -145, 78, -440, 59, -311, 83, -81, -28, 101, 0, 192, -212, -152, 40, 8, -133, -136, 51, 11, -233, 23, 54, -69, -26, 16, -237, 34, 50, -292, 43, -121, -553, 11, -8, -337, 94, -65, -19, -201, 435, 198, -382, -546, 145, 173, 63, 3, -2, 115, -243, -515, 101, -63, -14, 11, -125, -76, -153, -7, 95, -255, 36, -54, -337, 126, 108, -7, -202, -576, -65, -57, -73, -8, 152, -122, 58, -66, -153, 181, -143, -182, -285, -104, -97, -179, -139, -25, 216, 67, 39, -509, -82, 152, 5, -112, -228, 54, 3, 257, -376, -208, 29, 33, -301, 161, 47, -238, 9, 93, 50, -429, -787, 54, -293, 214, -71, 45, 246, 2, -136, 210, -50, -6, -347, -165, 215, 49, -186, -92, 14, 120, -290, 251, -72, -163, 95, -334, -523, 198, 44, -384, 73, 354, -57, -406, -305, -39, 66, -22, 192, 31, -93, -19, 200, -229, 211, 4, 289, -147, -5, -139, -313, 37, -71, -62, -219, 177, -42, 112, -250, -231, -202, -77, -230, -107, 117, 233, -376, -268, 74, -329, -219, 41, 40, 5, -42, -249, 252, 121, -245, -134, 43, -290, 66, 50, -13, 272, -47, -7, 255, -7, 0, -391, 8, 196, 41, -250, 118, 65, -206, -336, 51, 249, -48, -174, 48, -60, 63, -266, 131, 414, 764, 154, -158, 169, -287, -275, 207, -5, 173, 14, -33, -96, -149, -77, 151, 248, 233, -154, 11, -239, 46, -330, -11, -3, -68, -131, 106, -63, -57, 16, 48, -242, 94, 246, -785, 58, 0, 243, -25, 2, 165, -9, 177, -103, -165, 250, -26, 156, -260, -105, -149, -237, 30, -148, -98, 301, -220, -191, 235, 68, -72, -157, 147, 83, 22, 88, 60, -190, -231, -88, -239, -136, 235, -181, -222, -58, -77, 68, -302, -139, -69, -233, -112, 6, 202, 205, -51, -11, -231, 90, -50, -358, 0, -125, -312, 95, -75, -368, -577, 96, -75, -255, 12, 38, -3, -36, -4, -443, -61, 1, 9, 19, -434, 161, -85, 58, 49, 23, -446, -61, 301, 35, -139, -55, 16, 175, 445, 78, -54, -203, 95, -3, 310, -5, -271, -8, 9, -20, -491, 123, -50, 50, -49, 463, 199, 39, -42, -26, -9, -14, 71, 32, 5, 48, 18, 12, -69, 13, 97, 39, 6, 41, -157, -217, -208, -93, -304, 84, -130, -268, -129, -254, -24, 59, -26, 0, -167, 72, 39, -74, 349, 312, -209, -312, 30, -299, -273, -92, 125, 150, -19, 70, -1, 210, 33, -232, 2, 455, 146, -82, 49, 17, -99, -6, -491, -328, -103, -186, 148, 234, -132, 61, 42, -349, -437, -80, 38, 190, -104, 208, 84, -321, 353, -9, -47, -114, 173, -3, 86, -271, 37, -62, 33, -268, -387, 35, 73, -69, 47, 83, 29, -283, 205, -67, 4, 3, -78, -411, 19, -1, -61, 490, -64, -177, 46, -7, 16, 2, 38, 99, -397, 55, -12, -65, -46, 139, -177, 75, 236, -203, 84, -351, 16, 92, -39, 34, 27, -2, 0, -120, -2, -88, 383, -254, -147, -8, 102, 46, 139, 174, -230, -144, 92, -142, -274, -183, -120, 54, 171, -244, 208, 315, -78, 54, -231, 57, -101, 47, 39, 55, -378, -43, 9, 85, 1, 115, 39, -333, -62, 7, -57, 52, 175, -2, -51, 121, -283, 259, 106, 54, -296, 90, -393, 51, -6, 43, -306, -279, 71, -11, -67, 154, 97, 33, 30, -87, -43, 156, -124, -1030, -100, -22, 293, -5, 9, 144, -44, 323, 171, -105, -234, 0, -95, -108, -42, 38, 352, -86, 195, -177, -3, -26, 273, 47, -56, 65, -2, -73, -9, 84, -89, -368, -302, 566, -478, -196, -161, 218, -8, -49, 527, -29, -4, -10, -170, -14, 156, -146, 14, 44, -171, 75, -72, -27, -13, 115, -520, 43, -5, 77, -79, -460, -13, 53, -51, -244, -36, -279, 26, 15, -343, 12, -262, 21, -37, 168, -232, -127, -108, -122, 130, -59, 103, 115, -217, -238, -327, 149, -13, -222, -19, -63, -287, -371, 137, 17, 292, -63, -10, 150, 39, 43, -38, -102, 71, 0, 105, -365, -64, 11, -240, -69, -264, 161, 41, -64, -74, -2, 28, -49, 79, -1, -117, -3, -19, -68, 46, -48, -37, -134, -98, -1, -148, 5, -166, -86, 38, -64, -28, -249, 97, -266, -1410, 244, 2, 57, 42, -221, -721, -331, -208, 168, 1, 78, 65, -367, -43, -166, -13, -235, 137, -139, 39, -62, -130, -55, 29, -3, 311, -64, 57, 64, -83, -14, 0, -78, -62, 120, 98, -12, 54, -43, 29, -11, -103, -84, -185, -40, 49, 210, -110, -7, 28, 557, -12, -83, 294, -99, -429, -249, 53, -42, 60, -237, -188, 36, 2, -304, 622, 183, 40, -208, 238, -144, -202, -362, 97, -104, -61, -223, 39, -293, 39, 10, 111, 111, -24, -97, 228, 220, 153, -406, 43, 130, -110, -80, 270, -183, 63, -176, -151, 11, -157, -78, -351, -143, 1, 400, -404, -397, 44, -334, -353, -181, -10, 147, -126, -125, -154, 60, -20, -308, 59, -207, 157, -75, -156, -136, -329, -43, -28, 261, -200, -225, 29, -207, -18, -329, 121, -15, 44, -51, -17, -326, 31, 3, 158, -92, 134, -43, -304, 214, 90, -225, -36, -74, -8, 177, -165, -7, -2, 217, -531, -219, 98, -441, 140, -9, 149, -3, 38, 132, -5, -220, -116, 33, 33, -64, 5, -100, 21, -46, -158, -12, 45, -215, -48, -203, -60, -14, 67, -171, 172, 77, 37, -47, 48, 115, 34, -53, 82, -51, 40, -160, 42, -64, 39, 145, 146, -98, 56, -73, -166, -74, 116, -131, 4, 100, 304, -174, -217, -282, -50, -104, -75, -334, 60, 74, -620, 225, 205, 37, -208, -181, -186, 43, 708, 29, -1, 59, -79, -12, -297, -69, -138, 46, 160, 61, -240, -19, 10, 43, -8, 24, -101, -58, -70, -27, -12, 38, -5, -205, -53, 51, -46, 127, 299, -16, -59, -210, 155, -10, -294, -2, 96, -25, 171, 40, 97, 38, -174, 65, -7, -90, -9, -6, 27, 119, -72, -5, -83, -313, -4, 167, -133, -200, 0, -13, 4, -159, 45, 11, 116, 85, -598, -169, 117, -68, -47, -6, -8, 1, 108, -5, -8, 28, 74, 30, 37, -137, -15, -115, 310, -590, -183, 18, -313, 34, -7, 34, -37, 49, -95, 207, 214, -242, 11, -497, -54, 153, -56, 161, -59, 46, -178, 88, -224, 60, -15, -50, 247, -15, -116, 29, 463, 59, 126, 155, 102, -217, -202, -172, 9, 35, -35, 35, -51, -119, -241, 83, 70, 60, -147, -156, -144, -205, -207, 35, -42, 369, 34, -86, -29, -254, -123, 9, -278, 244, -265, 230, -259, 157, -21, 16, -239, -215, 155, -7, 33, -289, 194, 76, 5, -218, -15, 91, 0, -8, 151, 152, -300, -4, 41, -57, 70, -194, -58, 49, 42, 328, -138, 162, -127, -303, 5, 7, -53, 0, -56, -2, 114, -52, -196, -361, 49, 215, 32, -119, 132, -7, 62, 250, 51, -65, 43, -219, 143, -65, 1, -154, 107, 58, 23, -68, -185, -89, 29, -2, 52, 148, 4, -84, 351, 0, -3, 96, -703, 121, -148, -2, 89, 364, 61, -2, -4, -231, -54, 50, -23, -141, 47, 496, -67, -140, -655, -63, 41, 56, 79, -244, 32, -15, 10, -11, 10, 7, 264, -17, -152, -16, 14, -1, 37, -45, -152, -276, 199, -16, -4, -14, 87, -67, -33, 7, 6, 115, -50, -138, -3, 17, 174, -52, 182, -94, -220, -69, -88, -81, -176, -53, -126, 343, 11, -182, 257, -3, -209, 138, -86, -306, -227, 42, 160, -72, -163, -196, 116, -195, 11, -12, -5, -245, -179, -72, -64, -178, 117, 46, -161, -263, 88, -74, -113, 45, -2, 423, -1, 0, -158, 180, 100, -6, 120, 82, -314, 11, -42, 86, -218, 14, 133, 160, -157, -216, -16, -45, -7, -62, -60, 100, -68, 44, -277, 184, -304, 161, 338, -86, -65, 36, -298, -101, 126, 479, -227, -298, -171, -122, 30, -19, -51, 236, -68, -138, 4, -3, -45, 53, 5, -4, -48, 104, -52, -434, -7, -51, -115, 60, -46, -70, -118, 106, 37, 192, -48, 90, -164, 4, 270, 76, -55, 61, -8, -1, 19, 20, -35, -476, -47, 36, 411, -207, -356, 8, -141, 5, 113, 46, -16, 51, -81, 222, 163, 44, 61, 138, 612, 40, 0, -29, -269, -51, -54, 28, -439, 165, -2, 50, -221, 35, 86, -640, 129, -750, -153, 86, -283, 114, -266, 8, 135, -137, -128, -84, -81, 27, -36, 241, -139, 3, -80, -1, -195, 61, -24, -202, -26, -103, 52, 0, -1, -93, -365, -10, 67, -214, -125, -48, 59, -9, -456, -55, -45, -2, 77, -243, 8, 250, -5, -14, 167, 6, -1, 87, -1, -134, -149, 5, -93, 9, -37, -55, -277, -39, 11, -396, 42, -197, 28, 283, 70, -206, 36, 50, -12, -42, -32, -8, -16, -93, 30, -133, 166, 44, -50, -130, -17, -104, -54, -127, -52, 46, 3, -53, 63, -488, -182, -43, 48, 1, 43, -578, 616, -69, 80, -371, -4, -59, 36, -56, -29, 6, 45, -37, -134, 225, -123, -54, -18, -63, 2, -45, 33, -11, 44, -289, -57, 116, -38, -174, 166, 114, -22, -119, 74, -309, -11, -68, -33, 497, 39, -182, 235, -57, -185, 319, -370, -200, -218, -38, 140, 93, -8, -157, -16, -87, -77, 19, -249, 47, -15, 83, -75, -310, 33, -169, 42, -13, 51, -201, 73, 442, 4, -19, 81, 196, 47, -60, 44, -11, 205, -209, 38, -186, 145, 10, -507, 128, 102, -196, 221, -143, 10, -49, 47, -12, 362, 337, 12, -53, -319, 66, 58, -220, 80, 64, 68, -138, 183, -149, -190, 45, -275, 6, -115, -69, -125, 106, 41, -282, 166, 107, 90, -74, -338, -224, 66, -253, 162, 6, -144, 0, -24, -167, -119, -271, 129, -78, -285, -222, 168, -58, 46, -84, -30, 98, -228, 137, -14, -390, 19, -50, -163, 21, -110, 102, 135, -99, 224, -298, 279, 35, 34, -3, 45, -135, -28, 100, -65, -6, 202, -122, -44, 0, 4, 51, 47, -15, -83, -159, -8, 50, 52, -145, 191, 217, 42, -340, -15, 195, 57, -407, 30, -335, 0, 167, 18, -172, 85, 116, -11, 68, -212, -172, -18, 7, 34, -152, 103, -278, 74, 167, -501, -58, 40, -99, 439, -97, -791, -35, -16, -144, 64, -670, 15, 239, 35, -3, 15, 182, 37, -95, -60, -7, 47, -39, 38, -42, -18, -5, -46, -116, 68, -39, 17, 70, -787, -374, 226, 35, -263, 19, 30, 172, 54, 114, 9, -50, 34, 215, 44, -45, -36, 267, 28, -201, -155, -3, -523, -107, 6, -44, -56, -17, 330, -297, 17, -45, 56, 158, -118, -32, -77, -57, 64, 74, 49, -193, 21, -68, 34, -103, 41, 79, -68, 39, 293, -182, 106, -341, 36, -12, 163, -55, -206, -81, -164, -117, 117, 93, 6, 44, -246, -181, 18, -191, 174, -32, 18, 244, -72, 98, 0, 217, -236, -139, -1, 184, 49, 29, -13, -27, -46, 42, 52, 239, 0, 0, 185, 256, -11, 3, -241, -111, -45, 148, -5, -36, 249, -21, -529, 112, 73, -146, 88, 143, -37, 61, 110, 5, 46, 38, -50, 0, 323, 166, -264, -122, -53, 132, -54, 46, -37, -72, -114, 10, 101, 563, -71, 87, 73, 163, 20, -114, -251, 58, 214, 29, -9, -346, -45, 32, 205, 41, 39, -471, -206, -35, -6, -188, -116, 53, 102, -5, -127, 45, 11, 44, -118, 13, 38, 35, -73, -77, -251, 12, 60, 120, -53, 42, -144, -911, -9, -144, -7, -136, -56, 36, -88, 245, 445, 355, 13, -23, 9, 243, -34, 58, -56, 329, -1012, 96, -6, 43, -239, 33, -292, 126, -79, -97, -47, -151, -39, 82, -40, 193, -226, 61, -479, 33, -6, 119, 102, -400, -492, 34, 261, -24, 28, 154, -48, 29, -71, 185, -49, 39, -14, -412, -15, 41, -45, 1190, -43, 233, 56, -230, -96, -97, -46, -57, 181, 122, -47, 10, -59, -117, 85, -42, 57, 38, -380, -49, 34, -277, -151, -125, 152, -302, -156, -292, -421, -79, -177, -183, 57, 264, 115, -218, 148, -96, -67, -7, 52, 171, 44, -214, -8, 107, 17, -40, -181, -41, 99, 4, 12, -69, 216, 39, -237, 132, 35, -230, 50, 24, -15, 62, 156, 232, -80, -170, 15, 204, 48, 150, -65, -3, 52, -274, -148, -169, -123, 147, -13, 31, 28, -444, 34, -120, 178, 431, 203, -259, 36, 129, -40, -139, -44, 64, 238, -8, 89, 17, 36, -263, -50, -198, 33, -39, 38, -182, 284, 238, -50, 107, -132, -11, 13, -60, -226, -52, 34, -44, 14, 40, 182, -40, -88, -142, -924, 132, -22, 7, 60, -10, 117, -195, -957, -163, 49, -41, 5, -434, 303, -104, 39, 125, -62, -12, 111, 48, -112, -52, 79, -79, 35, -130, 122, 115, 33, -10, -88, 1, 20, 297, -82, -46, 0, -37, -101, -46, 37, -15, 87, 79, -9, -45, -258, -137, 123, 67, 9, -153, 39, -37, 3, -4, 91, 306, -158, -467, -7680, -61, -8, -39, -15, -165, 278, -66, 35, -53, 37, 7, 323, -32, -175, -122, -120, 65, -123, -61, 194, -89, -202, 120, 171, 63, -55, 71, 14, -255, -305, 38, -363, -72, 121, -15, -219, 42, -300, 67, 9, -10, 73, -360, -54, 86, -64, 10, 135, 64, 1, -127, 21, -133, -161, 329, 213, 28, -345, -346, 103, -67, 150, -42, 3, -4, -61, -137, 192, -41, -44, 59, 64, 33, -214, 603, 48, 37, -11, 45, -252, -41, -61, 36, -266, 50, -232, -7, -255, 187, 71, 1, -51, 165, -47, -74, -17, -3, -53, -91, 277, 54, 132, -112, 8, 3, 87, 84, -64, 35, -3, 48, 89, -9, -109, 170, -125, 33, -14, -147, 249, 45, -207, 71, -34, -17, -46, -40, 74, 113, -49, -2, -108, -218, 214, 25, -47, 64, -90, 41, -37, -54, -182, 8, -69, 92, -12, 33, -275, 6, -66, -454, 76, 50, -110, -130, 199, -161, -11, 30, -4, 22, 10, -486, -15, 227, -56, 147, -138, -20, -51, 106, -7, -30, 84, -5, -112, 30, 234, 28, -36, 51, 83, 40, -19, 29, -42, 57, -49, 29, -229, 91, -117, 60, -7, -130, -138, -227, 206, 3, -11, 18, -50, -1391, 114, -3, -38, 118, -422, -9, 88, 31, -15, 4, -70, -45, -82, 32, -127, 11, -10, 0, -391, 9, 25, 159, -238, -103, 24, 95, -59, 10, -127, 8, -128, 9, -16, 124, 34, -113, 7, 3, 3, 74, -103, 84, -136, -369, -202, -68, -139, 5, -127, -202, 204, -84, -69, -135, -144, -44, -23, -14, 60, 45, -109, 148, 8, 17, -321, 136, 298, 100, -188, -36, 30, -362, 113, -356, 131, -14, -20, -221, 133, -41, -43, -1, 162, -86, -8, 165, 13, 167, 49, -238, -174, 3, 257, -59, -185, -56, 42, -61, 130, 231, 35, -169, 205, -85, -142, -15, 87, 71, 300, 209, -47, 83, 50, -239, 6, -54, 189, -49, 178, 100, -18, 244, -13, 19, 13, 184, 36, 10, 137, -11, 8, -66, 40, -187, 21, -90, 72, -215, 38, -48, 113, -14, -79, 420, -199, -59, -92, 199, 302, -120, 56, -9, 107, -42, 40, -1, -7, -58, -15, -76, 56, 311, 3, -382, -98, -54, 0, -159, -108, 6, 33, 301, 8, -81, 216, 94, -133, -15, 202, -299, 10, -91, 53, -48, 65, 8, -253, -34, 86, -46, -251, -8, 298, 163, -59, -56, 41, -43, 66, -196, -69, 19, -9, -45, 48, 180, 17, 192, 49, -12, -114, 166, -14, -39, -156, -12, 28, -204, -48, -34, 124}; +const int16_t frontalface_alpha2_array[]={-567, 339, 272, 301, 322, -479, 112, 113, 218, -402, 302, 179, 442, -558, 116, 137, 238, -169, -76, 347, -50, -135, 292, 197, -387, 375, 256, -408, 212, 108, 269, -344, 371, 310, -117, 39, -400, 59, 327, -77, -13, 393, 239, 246, -757, -112, 102, -677, 72, 59, 275, 25, -274, 196, 353, 132, 149, 299, 244, -35, 70, 60, -343, -230, -418, 46, -97, 63, -75, 161, 13, 99, 25, -322, -609, -70, -291, -324, 69, 181, 9, -12, -89, 54, 277, 359, 189, 96, 323, 117, -245, 11, 138, -381, -134, -409, 39, -184, 17, 174, 19, -55, 335, 312, 217, 76, -83, -214, -171, 35, 19, 49, 17, 199, 31, 3, 135, 100, -542, 252, 24, -37, -148, -43, -163, 64, -69, 60, -323, 77, 135, 61, 132, -3, -66, -151, 267, 141, 163, 136, 92, 92, -128, 218, 292, -46, -80, 267, 50, -340, -179, 57, -131, 158, 121, -175, 29, -14, 211, -45, -396, 61, -81, -211, 13, 33, 9, 126, -146, 163, 16, -255, 9, -266, -138, 113, 0, -165, 205, 54, -270, -219, 16, 162, 144, -385, 96, 31, 173, 243, 125, 127, -320, 152, 77, 57, -25, 47, -119, -67, 106, 151, -117, 36, -249, 46, -339, -536, 131, -328, -118, 11, 88, 109, 42, -120, -427, 9, 59, 25, -48, -97, 50, 129, 59, -81, -3, 266, -213, 116, -384, -98, -27, -430, 61, 119, 45, 18, -395, 96, -317, 13, 58, 314, -11, -55, -486, 1, -21, 16, -195, 210, 75, 148, 229, 129, -180, 181, 68, -98, 66, -150, 43, -224, 60, -144, 98, -355, -273, 50, 111, -114, 57, -1, -133, -386, 47, 0, -568, 15, -303, 31, 181, -269, 49, -64, -54, -71, 62, 14, 50, 269, -440, 15, 7, -123, 41, 10, 82, -67, 38, 10, 39, -108, 47, 0, 79, -166, 39, 391, 166, 9, -25, -87, -4, -7, 42, 0, -45, -327, -388, 83, 38, 284, -157, 101, 73, 115, -174, 15, -442, 31, -207, 172, 215, -121, 242, -80, 45, 63, -109, -409, 96, 63, -369, -348, 69, -208, -191, 207, 220, -253, 39, -180, -103, 18, -184, 67, 37, -275, 311, 3, -39, 180, 85, 19, 12, -62, 31, -6, -30, -68, -165, -317, 260, -92, 52, -5, -75, 277, 311, -272, 43, 132, 63, -592, -83, 18, -441, 260, 38, -74, -86, -600, 39, -7, 60, 236, 79, -693, -8, 58, -267, 196, 71, -65, 280, 135, 103, 189, 188, 97, 93, 203, -84, -247, -271, 34, 154, -54, -375, 52, 26, -102, -411, -34, 2, 66, -183, -421, 6, -26, -137, 51, -258, -70, -136, 53, -9, -182, 4, -16, 203, -175, -55, 319, 37, -3, 276, 291, -1, 61, -52, -312, 13, 74, -171, 4, 6, 7, 151, 67, -85, 40, -6, -11, -114, 36, -97, 16, 203, 29, -1, 104, -98, 196, -57, -372, 66, 124, -56, 37, -51, 69, -48, 40, -419, 61, -1, -115, 112, 64, 6, 0, 389, -55, 5, 164, 147, 336, 74, 136, -114, -70, 52, 17, -133, 11, 47, -176, -215, -349, 66, 16, -4, -83, 51, 57, -274, 9, -183, -136, 249, -60, 117, -682, 6, -555, 191, 2, 254, -63, -156, 7, -34, -133, 38, 0, -157, -53, 122, 28, -383, 208, -17, 12, -1, -47, 24, -69, 40, -60, 50, 5, -4, -444, -14, -197, 171, 79, 65, 105, 4, -53, 10, 43, 209, 6, -87, 0, 64, -366, 85, 33, -79, 181, 49, -227, -70, 6, -44, -51, 29, -116, 100, -51, 52, -261, -23, -493, -17, 47, 56, -47, 95, -68, 147, 258, 144, 79, -286, 84, 134, -8, 30, 53, -72, -179, 187, 39, -87, -33, -245, -119, -134, 55, 16, 55, 12, 44, -56, 46, 14, 134, 143, -179, 11, 66, 148, 50, 54, 197, -63, -9, 282, 184, 11, -96, 286, 49, -297, 42, -3, -21, 152, 34, -8, 4, 136, 41, -192, -167, -314, 110, -305, 36, 138, 144, -203, 379, -7, 8, 76, -97, -135, 538, -10, 91, -45, -332, 35, 100, -184, 16, -42, -42, 187, 52, -75, 103, -44, 178, 0, 137, -191, 85, -9, 4, 186, -125, 197, 17, -47, -410, 304, 100, -412, 138, -81, -263, -202, -214, -160, 402, 98, 134, -72, -78, -223, -51, 20, 145, 114, 173, 49, -182, 29, 51, 93, 32, 147, -134, 122, -398, 48, -114, -54, 133, 7, -57, 37, 4, -252, 5, 50, 97, -37, -71, 154, -96, 264, -57, -303, 11, 274, -44, -18, 102, -311, -182, 46, -395, 42, -4, 60, 14, -4, -54, 47, -101, -657, -3, 42, 84, -124, -57, 48, -53, -153, -5, 15, -394, 95, 35, -4, -313, 0, -3, -317, 131, -181, 0, 37, -119, -106, 111, -243, -78, -506, -2, -8, 99, 150, -242, 54, -7, 297, -285, 53, -40, 46, 11, -191, -428, 195, -226, -630, -76, 41, -95, 152, 141, 104, -60, 40, -87, 24, 8, -13, -5, 234, -73, 136, -113, -655, -283, 145, 32, 223, 53, 14, -2, 43, -355, 0, -106, 4, -50, 132, 180, -171, 91, 48, 67, 68, -276, -71, 61, -63, 1, 181, -368, 12, -114, 88, -343, -132, -186, -6, 49, -224, -61, -320, -21, -124, 46, 159, 236, 198, -278, -59, 158, 258, 11, 1, 4, -73, -42, -2, -75, -7, -182, -388, -99, -5, 37, -105, 105, 141, 4, -75, -118, -132, 53, 367, -10, 34, 27, 57, 96, -50, 149, -171, -19, 298, 11, -55, 51, 10, 91, 49, 62, 325, -551, -41, 54, -50, 55, -255, 125, -44, -191, 139, -129, -245, 43, -336, 3, 61, 39, -3, 16, -11, 39, 13, 1, -341, 95, -38, 65, -267, 101, 8, 96, -53, 45, -165, -253, 8, 0, 120, 146, -487, -2, -13, -314, -277, -94, 60, 39, -486, 5, 156, 47, 550, 33, -132, 316, -8, 411, -1, 243, 495, -178, 78, 146, 148, 110, -51, 281, 14, -85, 57, 15, 47, -66, 182, 19, 232, 185, 53, -3, -29, -196, 10, 151, 83, -65, -143, -134, 75, 64, -120, -289, -67, -4, 40, -179, 59, 116, 36, -65, -453, 138, 85, -298, -638, 245, -65, -258, 49, -256, 106, 100, -92, 237, 85, 23, 62, -322, 43, -224, 33, 56, -129, 117, 142, 4, -43, 1, 28, -47, 210, -88, -356, 0, 29, -6, 30, -53, 136, -79, -13, -3, 107, 10, 162, 2, -16, 21, -102, 131, 35, 160, -698, -276, 8, 112, -61, -78, 66, -501, 189, 67, 43, -66, -73, -451, -6, 263, -319, -439, 52, 52, 51, 427, -90, -46, 31, -296, -1198, -37, 87, 78, 6, 55, 40, -2, -176, 311, -105, -4, 49, -107, 200, -8, 16, -48, -202, 150, -75, 106, 43, 6, -106, 91, 220, 25, -177, 9, -177, -247, 0, -83, 185, 77, -26, -55, -40, -5, -97, -69, 67, 142, 7, 16, -53, 16, 71, -226, 40, 108, 40, 31, 210, -43, 37, -7, -177, -6, 37, 9, 205, -63, 50, 34, 47, -89, 53, -3, -116, 3, 8, 69, 44, 17, 30, 284, 117, -47, 36, 2, -282, 0, 89, -7, -37, -634, -112, 180, 157, -6, -275, -181, 8, 44, 3, 287, 44, -46, -61, 0, 66, 66, 150, -55, 39, -290, 318, -48, 31, 2, -29, -14, -10, -276, 0, -216, -203, -54, 109, 0, 57, -98, -203, 104, 203, 29, 320, 197, 40, -471, -39, 0, 43, 1, 63, -469, -98, 5, -3, -72, -360, 204, -21, -56, -330, 139, -41, 136, -43, 10, -264, 81, -418, -51, -172, 231, -327, 193, 57, 79, -98, 70, -310, -79, -52, 52, 9, 40, 302, 84, 106, 45, -114, -28, -10, -12, -52, -290, 4, 57, 10, -285, -37, -1014, -252, -191, 77, 134, -1, 60, 20, -171, -53, -267, 0, 157, -217, -130, -325, 696, 39, 35, 87, 123, -514, -28, -298, 36, 157, -192, 256, -8, -47, 74, 152, 45, -54, 154, -6, 145, -69, 63, -52, -194, -65, -73, 8, -68, -293, 76, -339, 180, -115, -15, 112, 180, 61, 29, -280, 19, 29, 42, -218, 107, -166, 39, -87, 202, -57, -1, -15, 51, -57, 63, 186, 73, -285, 170, -67, 48, -281, -750, -70, -160, -94, 49, -498, 47, -39, 28, 5, 252, -11, -301, -239, -383, 400, -173, 27, 7, -43, 33, -133, 33, 124, 2, 138, -5, 127, -56, 4, 18, -2, -73, -571, 104, -51, 69, 22, -280, -37, -108, -52, 7, -55, 36, -3, 32, -162, -120, 499, -542, 126, 195, 101, -162, -147, -175, 70, 62, 69, 29, 61, -169, 107, -48, -234, 100, 113, 0, 43, -205, 46, -53, 56, -48, 37, -60, 55, -154, 39, 3, -23, -358, -126, -3, 0, -75, 51, 12, 38, -67, 266, -301, -14, -62, 43, -273, -342, 116, -95, 4, 60, -82, -261, -44, 61, -53, 44, -8, 257, -153, 96, -183, 82, -198, -15, 147, 32, -13, -162, -46, -543, 22, 4, -282, -98, -43, -98, 90, -233, -5, 0, 88, 89, 10, -13, -82, 2560, 85, 45, 42, -394, -255, 3, -51, 277, 50, 17, -215, 93, -70, 27, -59, 44, -214, -44, -37, 3, -194, 195, -2, 56, -91, 66, 7, -171, -37, 53, 12, 33, 102, -182, -74, 0, -2, -301, -475, 99, -284, 252, -177, 17, -639, 38, -547, 200, -184, -349, 186, 49, -10, 0, -465, 53, -362, -30, 66, 44, -156, 77, -58, 53, 17, 133, -126, 20, 128, -149, 153, 55, 156, 129, 105, 24, 60, 46, 10, -209, 57, -50, 206, 5, -19, 108, 39, 2, -232, -66, 68, 25, 57, -67, 35, -185, 131, -277, 37, 7, 64, 119, 33, -61, -157, 8, 44, -70, 61, 36, -61, -242, 24, -220, 98, 7, 12, -61, 64, -59, -52, -10, 154, 229, -69, 5, 163, -59, 8, 8, 42, -508, 97, -235, 58, 138, -32, 82, -155, -7, 7, -11, 2, -38, 43, 121, -89, -10, 40, -51, 22, -1, 36, 1, 38, -115, 71, 172, 23, 85, 35, -174, 138, 201, -122, -156, 106, 189, -34, 157, 37, -279, 57, 14, -54, 158, 64, 10, 0, -86, 2, 123, -44, 2, 81, -44, -2, 121, -68, -261, 146, -107, 737, 534, 36, 138, -400, -37, 33, -14, 147, 5, 95, -58, -104, -433, -117, 39, 8, -47, -122, -67, 13, -34, -173, -187, 78, -8, 83, 111, -1218, -15, -8, -196, -21, -6, -570, -61, 32, -50, 35, 7, -36, -12, -17, -10, 209, -48, 155, 112, 140, 118, -251, 182, -55, 64, -276, 131, -318, 52, -89, 52, 5, 140, 68, -261, -223, 205, 58, 36, -489, -83, 0, 42, 213, -18, -295, 38, 129, 74, -228, -11, -5, 247, -44, 70, -455, -6, -180, 84, -77, 148, 11, 48, -176, 39, -153, 96, 132, 36, 302, 234, -14, -256, -1, -431, -39, -47, -4, -65, -79, 107, 237, 103, -253, 65, 30, -263, 8, 0, -87, 38, 7, 47, 20, 57, 16, 56, -111, 97, 102, -68, -17, 40, 198, -154, -158, -181, -18, 21, 70, -15, -15, 129, 78, -128, 100, 51, -136, -160, 363, 40, -42, 38, 108, 37, 68, 110, 177, -86, -346, -15, -10, 60, -54, 53, -2, 11, -60, 70, 19, -5, -10, 128, 67, 81, -35, -7, -3, 11, 81, 43, -37, 31, -6, 42, 288, 9, -52, 138, 0, 107, 32, 55, -105, 28, -76, 63, -59, 39, -13, -595, -2, -171, -324, 3, -6, -7, -36, 96, -867, 4, -45, -79, 84, -46, -289, 17, -4, -47, -4, 3, -106, 30, -50, -6, -6, 16, 0, 125, 130, -41, -289, 22, -37, 219, 86, 30, -62, -75, 0, -36, -72, -72, 156, -105, 75, 36, -175, 31, -262, 54, 124, 80, -76, -255, 5, -7, -68, -96, 105, 33, 0, -54, -2, -14, -187, 42, -238, 64, 17, 41, -5, -39, 188, 46, -3, -9, 108, -252, 54, 76, -62, 36, -52, 102, -13, 318, 153, 40, -116, 57, -61, 10, 36, 21, -8, 13, -86, -104, -209, -83, 11, 56, -56, 45, -223, 5, 13, 88, -167, 150, -82, -60, -411, 38, 3, 142, -96, -109, 11, 11, -45, -76, -12, 47, -46, -16, -15, -361, -13, 113, -47, 208, 0, 14, -51, 58, -66, 33, 4, 36, -143, -75, 3, 0, -10, -64, -46, 37, 87, -258, 21, 15, 21, 30, 486, 66, 11, -10, -18, 220, -40, -654, -181, 422, -44, -20, 25, 68, -217, -143, 248, -281, 210, 73, -200, 52, 16, -45, 283, 178, -64, 29, -13, 11, -88, 29, -112, -186, -46, 9, -53, 71, 139, -28, -42, -201, 170, 41, -40, -1149, 3, 33, -187, 35, 20, 107, 165, 36, -599, 21, -13, 188, 178, -52, -45, 48, 839, 60, 76, -34, -74, -174, -3, 278, 50, -145, 36, -142, -58, 50, -87, 23, 0, 6, -12, -131, -305, 9, 126, 102, 176, 65, 79, -70, -69, -226, -139, 6, 54, -174, 60, -54, 172, -206, 4, 120, -15, -260, 1, 0, 63, -240, 2, -91, -417, -434, 132, 243, -296, -84, 0, -198, 190, -47, 8, -327, 170, -5, 59, 219, 7, -247, 132, -46, 81, -15, 5, -74, 59, -66, 15, 419, -114, -60, 206, -84, -363, 149, 99, -40, 2, -8, 41, 139, -3, 194, -189, 393, 52, 13, 75, -72, 22, 64, 4, -64, 22, -104, 44, -9, -206, -44, -503, -263, 31, 190, -113, -44, -31, -85, 37, -7, 84, -213, 45, 17, -96, -53, 116, 19, -72, -141, -53, 17, 193, -81, -291, 48, 42, -5, 135, -71, 16, 130, -371, 6, 30, -261, 47, -212, 36, 122, -156, 30, 16, -36, 16, -138, 100, -138, 9, 586, -153, 95, 12, -18, -11, -204, -161, -10, -404, -12, -8, 43, 41, 144, 30, 237, -41, 260, 8, -2, -29, -17, -172, -190, -6, -54, 36, -17, -579, -38, 106, -106, 15, 118, -338, 49, 19, 117, -127, -394, 29, -375, -28, 146, 24, 222, 14, -71, 75, 155, 100, 150, 163, -37, -74, 134, -228, 113, 45, -76, 409, -136, -107, 33, 251, -144, -2, 34, 24, -10, -7, 57, -7, 32, 65, 39, 0, -141, -44, 10, -3, -4, 35, 60, -331, -47, -50, -83, -1, 151, -60, 187, 279, 43, 257, -13, -240, 139, 103, 8, -89, 43, -51, -126, -4, -42, -106, 181, -78, 6, -42, 51, 1, 224, -44, -155, -49, 41, -196, -29, -9, 47, 1, 31, -49, 62, -99, -7680, -16, -179, 15, 0, -36, 0, -4, -107, -52, 45, 7, 77, -67, 18, -219, -12, -115, -119, -11, 73, -2, -902, 375, -333, -2, 21, -43, 64, -62, 51, -272, 127, 106, 34, 149, -805, 177, 77, -81, 14, 235, 51, 5, 33, -49, 40, -141, -11, -241, -1, -5, 28, 2, -21, 290, 195, -15, 23, 21, -281, -51, 36, -315, 3, -82, 58, 130, 18, 40, -45, 14, -18, -50, -220, -290, 40, -157, 178, -38, 44, 158, 108, 320, 36, 152, -201, -364, 7, -57, 81, 166, 28, 5, 8, -65, 232, 2, -245, 350, 55, -226, 16, -38, 32, -16, 28, 93, 70, 276, 52, 6, 14, 53, -400, 134, -335, -130, 16, 787, 99, 115, 109, -170, 71, 113, -64, 88, 8, -15, -62, -123, 184, -87, -210, 48, -7, -138, -10, 39, -56, 155, -3, -70, -10, -14, -140, 123, -84, 32, 138, 11, 106, 176, -58, -55, -185, 47, -118, 61, 8, 19, -47, -7680, -12, 40, -64, 47, -49, 58, -170, 165, 89, 53, -45, 78, 256, -16, -78, -240, -6, 21, -79, -216, -342, -155, -9, 83, 75, -384, -11, -37, -9, 153, -9, 14, -67, 91, 131, 0, 157, 46, -493, 157, 113, 62, -38, -46, -48, 58, -132, 89, -55, -73, 67, -127, -197, -82, -57, 131, 12, 1, 17, -485, -365, 46, -42, -71, -4, -1, 650, 73, 167, 69, -64, 14, 119, 65, 18, 43, -45, 611, 159, -16, 27, -234, 381, 50, 0, 267, 69, 14, -247, -89, -13, 71, 53, 29, -57, -25, 20, 41, -44, 32, -284, -1234, -163, 628, -130, 28, -362, 10, 85, 11, 0, 91, 112, -11, -235, 51, -59, 68, 12, -724, -40, -510, 334, -11, -52, -244, -541, -412, 179, -102, 113, -403, -10, -3, 6, -16, -215, 41, 1, 34, -41, 141, -275, 299, 97, 28, -47, 47, 243, 9, -16, 107, -54, -544, -380, 82, 48, 71, 68, -155, 5, 124, -238, 87, -15, 164, -101, -117, 55, 108, -162, -77, 103, -199, 41, -204, 65, -181, 189, -62, -33, 35, 229, -220, 218, -75, 49, -65, 55, -11, 48, 80, 42, -159, 49, -3, -8, 53, 47, 13, 49, 244, 63, -419, -23, -91, 51, -48, 209, -117, 36, -52, 13, -56, 36, 458, -483, -14, -26, -12, -23, -365, 82, -8, -4, 279, 79, -176, -1, 32, 100, -51, 232, -50, -132, -8, 32, -162, 16, 79, 43, 90, -190, 106, 0, -42, -133, 0, 15, 37, 33, -350, -1, -79, 21, -45, 36, -60, -5, -5, 118, 102, 7, 111, 17, -53, 92, -39, 71, -93, 106, -43, -167, -117, 18, -257, 108, 67, -266, -5, 400, 37, 0, -9, -223, 152, -14, -348, 65, -36, 43, 73, 52, -39, 19, 20, -94, -236, 20, 183, -224, -151, 123, 86, 80, 45, -75, -36, 142, -16, 50, 75, 171, 0, 30, -129, -55, -38, 102, 29, 21, -48, 40, -273, 13, -15, 169, 15, -63, 101, -24, -117, 37, 404, 19, 120, 30, -214, 20, -45, 32, 69, -110, 150, -9, -5, 36, -106, 53, 162, -131, -45, 175, -40, -62, -225, 45, -42, 88, 221, 30, -230, -277, -8, 55, 430, 0}; +const int8_t frontalface_num_rectangles_array[]={2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 3, 3, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 3, 3, 2, 2, 2, 2, 3, 3, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 3, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 3, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 3, 3, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 3, 3, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 3, 3, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 3, 2, 3, 3, 2, 2, 3, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 3, 3, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 2, 3, 2, 2, 3, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 3, 2, 2, 3, 3, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 3, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 3, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 3, 3, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 2, 3, 3, 3, 3, 3, 2, 3, 2, 2, 3, 3, 2, 2, 3, 2, 3, 3, 3, 3, 2, 2, 2, 2, 3, 3, 2, 3, 2, 3, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 2, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 3, 3, 2, 2, 2, 3, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 3, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 3, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 3, 2, 3, 3, 3, 2, 2, 2, 2, 3, 2, 3, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 3, 2, 3, 3, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 3, 3, 3, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 3, 2, 3, 3, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 3, 3, 3, 2, 2, 3, 3, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 3, 2, 3, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 3, 2, 2, 2, 2, 3, 3, 2, 2, 3, 3, 2, 2, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 3, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 3, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 3, 2, 2, 2, 3, 2, 2, 3, 3, 3, 2, 2, 3, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 3, 3, 3, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 3, 3, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 2, 2, 3, 2, 3, 3, 2, 3, 2, 2, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 3, 3, 2, 3, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 3, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 3, 2, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 3, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3}; +const int8_t frontalface_weights_array[]={-1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2}; +const int8_t frontalface_rectangles_array[]={6, 4, 12, 9, 6, 7, 12, 3, 6, 4, 12, 7, 10, 4, 4, 7, 3, 9, 18, 9, 3, 12, 18, 3, 8, 18, 9, 6, 8, 20, 9, 2, 3, 5, 4, 19, 5, 5, 2, 19, 6, 5, 12, 16, 6, 13, 12, 8, 5, 8, 12, 6, 5, 11, 12, 3, 11, 14, 4, 10, 11, 19, 4, 5, 4, 0, 7, 6, 4, 3, 7, 3, 6, 6, 12, 6, 6, 8, 12, 2, 6, 4, 12, 7, 10, 4, 4, 7, 1, 8, 19, 12, 1, 12, 19, 4, 0, 2, 24, 3, 8, 2, 8, 3, 9, 9, 6, 15, 9, 14, 6, 5, 5, 6, 14, 10, 5, 11, 14, 5, 5, 0, 14, 9, 5, 3, 14, 3, 13, 11, 9, 6, 16, 11, 3, 6, 7, 5, 6, 10, 9, 5, 2, 10, 10, 8, 6, 10, 12, 8, 2, 10, 2, 5, 4, 9, 4, 5, 2, 9, 18, 0, 6, 11, 20, 0, 2, 11, 0, 6, 24, 13, 8, 6, 8, 13, 9, 6, 6, 9, 11, 6, 2, 9, 7, 18, 10, 6, 7, 20, 10, 2, 5, 7, 14, 12, 5, 13, 14, 6, 0, 3, 24, 3, 8, 3, 8, 3, 5, 8, 15, 6, 5, 11, 15, 3, 9, 6, 5, 14, 9, 13, 5, 7, 9, 5, 6, 10, 11, 5, 2, 10, 6, 6, 3, 12, 6, 12, 3, 6, 3, 21, 18, 3, 9, 21, 6, 3, 5, 6, 13, 6, 5, 8, 13, 2, 18, 1, 6, 15, 18, 1, 3, 15, 1, 1, 6, 15, 4, 1, 3, 15, 0, 8, 24, 15, 8, 8, 8, 15, 5, 6, 14, 12, 5, 6, 7, 6, 12, 12, 7, 6, 2, 12, 21, 12, 2, 16, 21, 4, 8, 1, 4, 10, 10, 1, 2, 10, 2, 13, 20, 10, 2, 13, 10, 10, 0, 1, 6, 13, 2, 1, 2, 13, 20, 2, 4, 13, 20, 2, 2, 13, 0, 5, 22, 19, 11, 5, 11, 19, 18, 4, 6, 9, 20, 4, 2, 9, 0, 3, 6, 11, 2, 3, 2, 11, 12, 1, 4, 9, 12, 1, 2, 9, 0, 6, 19, 3, 0, 7, 19, 1, 12, 1, 4, 9, 12, 1, 2, 9, 8, 1, 4, 9, 10, 1, 2, 9, 5, 5, 14, 14, 12, 5, 7, 7, 5, 12, 7, 7, 1, 10, 18, 2, 1, 11, 18, 1, 17, 13, 4, 11, 17, 13, 2, 11, 0, 4, 6, 9, 0, 7, 6, 3, 6, 4, 12, 9, 6, 7, 12, 3, 6, 5, 12, 6, 10, 5, 4, 6, 0, 1, 24, 5, 8, 1, 8, 5, 4, 10, 18, 6, 4, 12, 18, 2, 2, 17, 12, 6, 2, 17, 6, 3, 8, 20, 6, 3, 19, 3, 4, 13, 19, 3, 2, 13, 1, 3, 4, 13, 3, 3, 2, 13, 0, 1, 24, 23, 8, 1, 8, 23, 1, 7, 8, 12, 1, 11, 8, 4, 14, 7, 3, 14, 14, 14, 3, 7, 3, 12, 16, 6, 3, 12, 8, 3, 11, 15, 8, 3, 6, 6, 12, 6, 6, 8, 12, 2, 8, 7, 6, 12, 8, 13, 6, 6, 15, 15, 9, 6, 15, 17, 9, 2, 1, 17, 18, 3, 1, 18, 18, 1, 4, 4, 16, 12, 4, 10, 16, 6, 0, 1, 4, 20, 2, 1, 2, 20, 3, 0, 18, 2, 3, 1, 18, 1, 1, 5, 20, 14, 1, 5, 10, 7, 11, 12, 10, 7, 5, 8, 14, 12, 5, 12, 14, 4, 3, 14, 7, 9, 3, 17, 7, 3, 14, 15, 9, 6, 14, 17, 9, 2, 1, 15, 9, 6, 1, 17, 9, 2, 11, 6, 8, 10, 15, 6, 4, 5, 11, 11, 4, 5, 5, 5, 14, 14, 5, 5, 7, 7, 12, 12, 7, 7, 6, 0, 12, 5, 10, 0, 4, 5, 9, 0, 6, 9, 9, 3, 6, 3, 9, 6, 6, 9, 11, 6, 2, 9, 7, 0, 6, 9, 9, 0, 2, 9, 10, 6, 6, 9, 12, 6, 2, 9, 8, 6, 6, 9, 10, 6, 2, 9, 3, 8, 18, 4, 9, 8, 6, 4, 6, 0, 12, 9, 6, 3, 12, 3, 0, 0, 24, 6, 8, 0, 8, 6, 4, 7, 16, 12, 4, 11, 16, 4, 11, 6, 6, 6, 11, 6, 3, 6, 0, 20, 24, 3, 8, 20, 8, 3, 11, 6, 4, 9, 11, 6, 2, 9, 4, 13, 15, 4, 9, 13, 5, 4, 11, 6, 4, 9, 11, 6, 2, 9, 9, 6, 4, 9, 11, 6, 2, 9, 9, 12, 6, 12, 9, 18, 6, 6, 1, 22, 18, 2, 1, 23, 18, 1, 10, 7, 4, 10, 10, 12, 4, 5, 6, 7, 8, 10, 6, 12, 8, 5, 7, 6, 10, 6, 7, 8, 10, 2, 0, 14, 10, 4, 0, 16, 10, 2, 6, 18, 18, 2, 6, 19, 18, 1, 1, 1, 22, 3, 1, 2, 22, 1, 6, 16, 18, 3, 6, 17, 18, 1, 2, 4, 6, 15, 5, 4, 3, 15, 20, 4, 4, 10, 20, 4, 2, 10, 0, 4, 4, 10, 2, 4, 2, 10, 2, 16, 20, 6, 12, 16, 10, 3, 2, 19, 10, 3, 0, 12, 8, 9, 4, 12, 4, 9, 12, 0, 6, 9, 14, 0, 2, 9, 5, 10, 6, 6, 8, 10, 3, 6, 11, 8, 12, 6, 17, 8, 6, 3, 11, 11, 6, 3, 0, 8, 12, 6, 0, 8, 6, 3, 6, 11, 6, 3, 12, 0, 6, 9, 14, 0, 2, 9, 6, 0, 6, 9, 8, 0, 2, 9, 8, 14, 9, 6, 8, 16, 9, 2, 0, 16, 9, 6, 0, 18, 9, 2, 10, 8, 6, 10, 12, 8, 2, 10, 3, 19, 12, 3, 9, 19, 6, 3, 2, 10, 20, 2, 2, 11, 20, 1, 2, 9, 18, 12, 2, 9, 9, 6, 11, 15, 9, 6, 3, 0, 18, 24, 3, 0, 9, 24, 5, 6, 14, 10, 5, 6, 7, 5, 12, 11, 7, 5, 9, 5, 10, 12, 14, 5, 5, 6, 9, 11, 5, 6, 4, 5, 12, 12, 4, 5, 6, 6, 10, 11, 6, 6, 4, 14, 18, 3, 4, 15, 18, 1, 6, 13, 8, 8, 6, 17, 8, 4, 3, 16, 18, 6, 3, 19, 18, 3, 0, 0, 6, 6, 3, 0, 3, 6, 6, 6, 12, 18, 10, 6, 4, 18, 6, 1, 4, 14, 8, 1, 2, 14, 3, 2, 19, 2, 3, 3, 19, 1, 1, 8, 22, 13, 12, 8, 11, 13, 8, 9, 11, 4, 8, 11, 11, 2, 0, 12, 15, 10, 5, 12, 5, 10, 12, 16, 12, 6, 16, 16, 4, 6, 0, 16, 12, 6, 4, 16, 4, 6, 19, 1, 5, 12, 19, 5, 5, 4, 0, 2, 24, 4, 8, 2, 8, 4, 6, 8, 12, 4, 6, 10, 12, 2, 7, 5, 9, 6, 10, 5, 3, 6, 9, 17, 6, 6, 9, 20, 6, 3, 0, 7, 22, 15, 0, 12, 22, 5, 4, 1, 17, 9, 4, 4, 17, 3, 7, 5, 6, 10, 9, 5, 2, 10, 18, 1, 6, 8, 18, 1, 3, 8, 0, 1, 6, 7, 3, 1, 3, 7, 18, 0, 6, 22, 18, 0, 3, 22, 0, 0, 6, 22, 3, 0, 3, 22, 16, 7, 8, 16, 16, 7, 4, 16, 2, 10, 19, 6, 2, 12, 19, 2, 9, 9, 6, 12, 9, 13, 6, 4, 2, 15, 17, 6, 2, 17, 17, 2, 14, 7, 3, 14, 14, 14, 3, 7, 5, 6, 8, 10, 5, 6, 4, 5, 9, 11, 4, 5, 15, 8, 9, 11, 18, 8, 3, 11, 0, 8, 9, 11, 3, 8, 3, 11, 8, 6, 10, 18, 8, 15, 10, 9, 7, 7, 3, 14, 7, 14, 3, 7, 0, 14, 24, 8, 8, 14, 8, 8, 1, 10, 18, 14, 10, 10, 9, 14, 14, 12, 6, 6, 14, 15, 6, 3, 7, 0, 10, 16, 7, 0, 5, 8, 12, 8, 5, 8, 10, 0, 9, 6, 13, 0, 3, 6, 4, 3, 16, 4, 12, 3, 8, 4, 10, 0, 9, 6, 13, 0, 3, 6, 1, 1, 20, 4, 1, 1, 10, 2, 11, 3, 10, 2, 10, 0, 9, 6, 13, 0, 3, 6, 5, 0, 9, 6, 8, 0, 3, 6, 8, 18, 10, 6, 8, 20, 10, 2, 6, 3, 6, 9, 8, 3, 2, 9, 7, 3, 12, 6, 7, 5, 12, 2, 0, 10, 18, 3, 0, 11, 18, 1, 1, 10, 22, 3, 1, 11, 22, 1, 5, 11, 8, 8, 9, 11, 4, 8, 12, 11, 6, 6, 12, 11, 3, 6, 6, 11, 6, 6, 9, 11, 3, 6, 7, 10, 11, 6, 7, 12, 11, 2, 0, 13, 24, 4, 0, 13, 12, 2, 12, 15, 12, 2, 2, 4, 22, 12, 13, 4, 11, 6, 2, 10, 11, 6, 2, 0, 20, 17, 12, 0, 10, 17, 14, 0, 2, 24, 14, 0, 1, 24, 8, 0, 2, 24, 9, 0, 1, 24, 14, 1, 2, 22, 14, 1, 1, 22, 8, 1, 2, 22, 9, 1, 1, 22, 17, 6, 3, 18, 18, 6, 1, 18, 6, 14, 9, 6, 6, 16, 9, 2, 13, 14, 9, 4, 13, 16, 9, 2, 3, 18, 18, 3, 3, 19, 18, 1, 9, 4, 8, 18, 13, 4, 4, 9, 9, 13, 4, 9, 0, 17, 18, 3, 0, 18, 18, 1, 0, 2, 12, 4, 6, 2, 6, 4, 6, 8, 14, 6, 6, 11, 14, 3, 7, 5, 6, 6, 10, 5, 3, 6, 10, 5, 6, 16, 10, 13, 6, 8, 1, 4, 9, 16, 4, 4, 3, 16, 5, 0, 18, 9, 5, 3, 18, 3, 9, 15, 5, 8, 9, 19, 5, 4, 20, 0, 4, 9, 20, 0, 2, 9, 2, 0, 18, 3, 2, 1, 18, 1, 5, 22, 19, 2, 5, 23, 19, 1, 0, 0, 4, 9, 2, 0, 2, 9, 5, 6, 19, 18, 5, 12, 19, 6, 0, 1, 6, 9, 2, 1, 2, 9, 6, 5, 14, 12, 13, 5, 7, 6, 6, 11, 7, 6, 0, 1, 20, 2, 0, 2, 20, 1, 1, 2, 22, 3, 1, 3, 22, 1, 2, 8, 7, 9, 2, 11, 7, 3, 2, 12, 22, 4, 13, 12, 11, 2, 2, 14, 11, 2, 0, 12, 22, 4, 0, 12, 11, 2, 11, 14, 11, 2, 9, 7, 6, 11, 11, 7, 2, 11, 7, 1, 9, 6, 10, 1, 3, 6, 11, 2, 4, 10, 11, 7, 4, 5, 6, 4, 12, 12, 6, 10, 12, 6, 18, 1, 6, 15, 18, 6, 6, 5, 3, 15, 18, 3, 3, 16, 18, 1, 18, 5, 6, 9, 18, 8, 6, 3, 1, 5, 16, 6, 1, 5, 8, 3, 9, 8, 8, 3, 11, 0, 6, 9, 13, 0, 2, 9, 0, 4, 24, 14, 0, 4, 12, 7, 12, 11, 12, 7, 13, 0, 4, 13, 13, 0, 2, 13, 7, 0, 4, 13, 9, 0, 2, 13, 11, 6, 6, 9, 13, 6, 2, 9, 8, 7, 6, 9, 10, 7, 2, 9, 13, 17, 9, 6, 13, 19, 9, 2, 2, 18, 14, 6, 2, 18, 7, 3, 9, 21, 7, 3, 3, 18, 18, 4, 12, 18, 9, 2, 3, 20, 9, 2, 0, 20, 15, 4, 5, 20, 5, 4, 9, 15, 15, 9, 14, 15, 5, 9, 4, 4, 16, 4, 4, 6, 16, 2, 7, 6, 10, 6, 7, 8, 10, 2, 0, 14, 15, 10, 5, 14, 5, 10, 7, 9, 10, 14, 12, 9, 5, 7, 7, 16, 5, 7, 7, 6, 6, 9, 9, 6, 2, 9, 3, 6, 18, 3, 3, 7, 18, 1, 0, 10, 18, 3, 0, 11, 18, 1, 3, 16, 18, 4, 12, 16, 9, 2, 3, 18, 9, 2, 4, 6, 14, 6, 4, 6, 7, 3, 11, 9, 7, 3, 13, 0, 2, 18, 13, 0, 1, 18, 9, 0, 2, 18, 10, 0, 1, 18, 5, 7, 15, 10, 10, 7, 5, 10, 1, 20, 21, 4, 8, 20, 7, 4, 10, 5, 5, 18, 10, 14, 5, 9, 0, 2, 24, 6, 0, 2, 12, 3, 12, 5, 12, 3, 1, 1, 22, 8, 12, 1, 11, 4, 1, 5, 11, 4, 4, 0, 15, 9, 4, 3, 15, 3, 0, 0, 24, 19, 8, 0, 8, 19, 2, 21, 18, 3, 11, 21, 9, 3, 9, 7, 10, 4, 9, 7, 5, 4, 5, 7, 10, 4, 10, 7, 5, 4, 17, 8, 6, 16, 20, 8, 3, 8, 17, 16, 3, 8, 1, 15, 20, 4, 1, 15, 10, 2, 11, 17, 10, 2, 14, 15, 10, 6, 14, 17, 10, 2, 3, 0, 16, 9, 3, 3, 16, 3, 15, 6, 7, 15, 15, 11, 7, 5, 9, 1, 6, 13, 11, 1, 2, 13, 17, 2, 6, 14, 17, 2, 3, 14, 3, 14, 12, 10, 3, 14, 6, 5, 9, 19, 6, 5, 7, 6, 10, 6, 7, 8, 10, 2, 1, 2, 6, 14, 4, 2, 3, 14, 10, 4, 5, 12, 10, 8, 5, 4, 0, 17, 24, 5, 8, 17, 8, 5, 15, 7, 5, 12, 15, 11, 5, 4, 3, 1, 6, 12, 3, 1, 3, 6, 6, 7, 3, 6, 12, 13, 6, 6, 12, 16, 6, 3, 6, 13, 6, 6, 6, 16, 6, 3, 14, 6, 3, 16, 14, 14, 3, 8, 1, 12, 13, 6, 1, 14, 13, 2, 13, 1, 4, 9, 13, 1, 2, 9, 7, 0, 9, 6, 10, 0, 3, 6, 12, 2, 6, 9, 12, 2, 3, 9, 6, 2, 6, 9, 9, 2, 3, 9, 6, 18, 12, 6, 6, 20, 12, 2, 7, 6, 6, 9, 9, 6, 2, 9, 7, 7, 12, 3, 7, 7, 6, 3, 8, 3, 8, 21, 8, 10, 8, 7, 7, 4, 10, 12, 7, 8, 10, 4, 0, 1, 6, 9, 0, 4, 6, 3, 15, 2, 2, 20, 15, 2, 1, 20, 0, 3, 6, 9, 0, 6, 6, 3, 15, 3, 2, 21, 15, 3, 1, 21, 7, 0, 2, 23, 8, 0, 1, 23, 15, 8, 9, 4, 15, 10, 9, 2, 0, 8, 9, 4, 0, 10, 9, 2, 8, 14, 9, 6, 8, 16, 9, 2, 0, 14, 9, 6, 0, 16, 9, 2, 3, 10, 18, 4, 9, 10, 6, 4, 0, 0, 24, 19, 8, 0, 8, 19, 9, 1, 8, 12, 9, 7, 8, 6, 10, 6, 4, 10, 12, 6, 2, 10, 7, 9, 10, 12, 12, 9, 5, 6, 7, 15, 5, 6, 5, 0, 3, 19, 6, 0, 1, 19, 14, 0, 6, 10, 16, 0, 2, 10, 2, 0, 6, 12, 2, 0, 3, 6, 5, 6, 3, 6, 0, 11, 24, 2, 0, 12, 24, 1, 4, 9, 13, 4, 4, 11, 13, 2, 9, 8, 6, 9, 9, 11, 6, 3, 0, 12, 16, 4, 0, 14, 16, 2, 18, 12, 6, 9, 18, 15, 6, 3, 0, 12, 6, 9, 0, 15, 6, 3, 8, 7, 10, 4, 8, 7, 5, 4, 8, 7, 6, 9, 10, 7, 2, 9, 11, 0, 6, 9, 13, 0, 2, 9, 7, 0, 6, 9, 9, 0, 2, 9, 12, 3, 6, 15, 14, 3, 2, 15, 6, 3, 6, 15, 8, 3, 2, 15, 15, 2, 9, 4, 15, 4, 9, 2, 5, 10, 6, 7, 8, 10, 3, 7, 9, 14, 6, 10, 9, 19, 6, 5, 7, 13, 5, 8, 7, 17, 5, 4, 14, 5, 3, 16, 14, 13, 3, 8, 2, 17, 18, 3, 2, 18, 18, 1, 5, 18, 19, 3, 5, 19, 19, 1, 9, 0, 6, 9, 11, 0, 2, 9, 12, 4, 3, 18, 13, 4, 1, 18, 9, 4, 3, 18, 10, 4, 1, 18, 3, 3, 18, 9, 9, 3, 6, 9, 6, 1, 6, 14, 8, 1, 2, 14, 12, 16, 9, 6, 12, 19, 9, 3, 1, 3, 20, 16, 1, 3, 10, 8, 11, 11, 10, 8, 12, 5, 6, 12, 15, 5, 3, 6, 12, 11, 3, 6, 1, 2, 22, 16, 1, 2, 11, 8, 12, 10, 11, 8, 10, 14, 5, 10, 10, 19, 5, 5, 3, 21, 18, 3, 3, 22, 18, 1, 10, 14, 6, 10, 12, 14, 2, 10, 0, 2, 24, 4, 8, 2, 8, 4, 6, 4, 12, 9, 6, 7, 12, 3, 6, 6, 12, 5, 10, 6, 4, 5, 5, 8, 14, 12, 5, 12, 14, 4, 4, 14, 8, 10, 4, 14, 4, 5, 8, 19, 4, 5, 11, 6, 5, 14, 11, 13, 5, 7, 7, 6, 3, 16, 7, 14, 3, 8, 3, 7, 18, 8, 9, 7, 6, 8, 2, 3, 20, 2, 2, 4, 20, 1, 3, 12, 19, 6, 3, 14, 19, 2, 8, 6, 6, 9, 10, 6, 2, 9, 16, 6, 6, 14, 16, 6, 3, 14, 7, 9, 6, 12, 9, 9, 2, 12, 18, 6, 6, 18, 21, 6, 3, 9, 18, 15, 3, 9, 0, 6, 6, 18, 0, 6, 3, 9, 3, 15, 3, 9, 18, 2, 6, 9, 18, 5, 6, 3, 3, 18, 15, 6, 3, 20, 15, 2, 18, 2, 6, 9, 18, 5, 6, 3, 0, 2, 6, 9, 0, 5, 6, 3, 5, 10, 18, 2, 5, 11, 18, 1, 6, 0, 12, 6, 6, 2, 12, 2, 10, 0, 6, 9, 12, 0, 2, 9, 8, 0, 6, 9, 10, 0, 2, 9, 15, 12, 9, 6, 15, 14, 9, 2, 3, 6, 13, 6, 3, 8, 13, 2, 15, 12, 9, 6, 15, 14, 9, 2, 2, 5, 6, 15, 5, 5, 3, 15, 8, 8, 9, 6, 11, 8, 3, 6, 8, 6, 3, 14, 8, 13, 3, 7, 15, 12, 9, 6, 15, 14, 9, 2, 4, 12, 10, 4, 9, 12, 5, 4, 13, 1, 4, 19, 13, 1, 2, 19, 7, 1, 4, 19, 9, 1, 2, 19, 18, 9, 6, 9, 18, 12, 6, 3, 1, 21, 18, 3, 1, 22, 18, 1, 14, 13, 10, 9, 14, 16, 10, 3, 1, 13, 22, 4, 1, 13, 11, 2, 12, 15, 11, 2, 4, 6, 16, 6, 12, 6, 8, 3, 4, 9, 8, 3, 1, 0, 18, 22, 1, 0, 9, 11, 10, 11, 9, 11, 10, 7, 8, 14, 14, 7, 4, 7, 10, 14, 4, 7, 0, 4, 6, 20, 0, 4, 3, 10, 3, 14, 3, 10, 15, 0, 6, 9, 17, 0, 2, 9, 3, 0, 6, 9, 5, 0, 2, 9, 15, 12, 6, 12, 18, 12, 3, 6, 15, 18, 3, 6, 3, 12, 6, 12, 3, 12, 3, 6, 6, 18, 3, 6, 15, 12, 9, 6, 15, 14, 9, 2, 0, 12, 9, 6, 0, 14, 9, 2, 4, 14, 19, 3, 4, 15, 19, 1, 2, 13, 19, 3, 2, 14, 19, 1, 14, 15, 10, 6, 14, 17, 10, 2, 6, 0, 10, 12, 6, 0, 5, 6, 11, 6, 5, 6, 17, 1, 6, 12, 20, 1, 3, 6, 17, 7, 3, 6, 1, 1, 6, 12, 1, 1, 3, 6, 4, 7, 3, 6, 16, 14, 6, 9, 16, 17, 6, 3, 7, 3, 9, 12, 7, 9, 9, 6, 12, 1, 4, 12, 12, 7, 4, 6, 4, 0, 14, 8, 4, 4, 14, 4, 10, 6, 6, 9, 12, 6, 2, 9, 2, 10, 18, 3, 8, 10, 6, 3, 15, 15, 9, 6, 15, 17, 9, 2, 0, 1, 21, 23, 7, 1, 7, 23, 6, 9, 17, 4, 6, 11, 17, 2, 1, 0, 11, 18, 1, 6, 11, 6, 6, 15, 13, 6, 6, 17, 13, 2, 0, 15, 9, 6, 0, 17, 9, 2, 8, 7, 15, 4, 13, 7, 5, 4, 9, 12, 6, 9, 9, 15, 6, 3, 6, 8, 18, 3, 12, 8, 6, 3, 0, 14, 24, 4, 8, 14, 8, 4, 16, 10, 3, 12, 16, 16, 3, 6, 0, 3, 24, 3, 0, 4, 24, 1, 14, 17, 10, 6, 14, 19, 10, 2, 1, 13, 18, 3, 7, 13, 6, 3, 5, 0, 18, 9, 5, 3, 18, 3, 4, 3, 16, 9, 4, 6, 16, 3, 16, 5, 3, 12, 16, 11, 3, 6, 0, 7, 18, 4, 6, 7, 6, 4, 10, 6, 6, 9, 12, 6, 2, 9, 9, 8, 6, 10, 11, 8, 2, 10, 9, 15, 6, 9, 11, 15, 2, 9, 3, 1, 18, 21, 12, 1, 9, 21, 6, 8, 12, 7, 6, 8, 6, 7, 8, 5, 6, 9, 10, 5, 2, 9, 0, 2, 24, 4, 8, 2, 8, 4, 14, 7, 5, 12, 14, 11, 5, 4, 5, 7, 5, 12, 5, 11, 5, 4, 9, 6, 6, 9, 11, 6, 2, 9, 0, 1, 6, 17, 3, 1, 3, 17, 3, 1, 19, 9, 3, 4, 19, 3, 3, 18, 12, 6, 3, 18, 6, 3, 9, 21, 6, 3, 20, 4, 4, 19, 20, 4, 2, 19, 0, 16, 10, 7, 5, 16, 5, 7, 8, 7, 10, 12, 13, 7, 5, 6, 8, 13, 5, 6, 6, 7, 10, 12, 6, 7, 5, 6, 11, 13, 5, 6, 9, 2, 9, 6, 12, 2, 3, 6, 1, 20, 21, 4, 8, 20, 7, 4, 9, 12, 9, 6, 9, 14, 9, 2, 7, 2, 9, 6, 10, 2, 3, 6, 13, 0, 4, 14, 13, 0, 2, 14, 7, 0, 4, 14, 9, 0, 2, 14, 14, 15, 9, 6, 14, 17, 9, 2, 2, 8, 18, 5, 8, 8, 6, 5, 18, 3, 6, 11, 20, 3, 2, 11, 6, 5, 11, 14, 6, 12, 11, 7, 18, 4, 6, 9, 18, 7, 6, 3, 7, 6, 9, 6, 7, 8, 9, 2, 18, 4, 6, 9, 18, 7, 6, 3, 0, 4, 6, 9, 0, 7, 6, 3, 9, 4, 9, 4, 9, 6, 9, 2, 0, 22, 19, 2, 0, 23, 19, 1, 17, 14, 6, 9, 17, 17, 6, 3, 1, 14, 6, 9, 1, 17, 6, 3, 14, 11, 4, 9, 14, 11, 2, 9, 6, 11, 4, 9, 8, 11, 2, 9, 3, 9, 18, 7, 9, 9, 6, 7, 9, 12, 6, 10, 9, 17, 6, 5, 12, 0, 6, 9, 14, 0, 2, 9, 6, 0, 6, 9, 8, 0, 2, 9, 6, 17, 18, 3, 6, 18, 18, 1, 1, 17, 18, 3, 1, 18, 18, 1, 10, 6, 11, 12, 10, 12, 11, 6, 5, 6, 14, 6, 5, 6, 7, 3, 12, 9, 7, 3, 5, 4, 15, 4, 5, 6, 15, 2, 0, 0, 22, 2, 0, 1, 22, 1, 0, 0, 24, 24, 8, 0, 8, 24, 1, 15, 18, 4, 10, 15, 9, 4, 6, 8, 12, 9, 6, 11, 12, 3, 4, 12, 7, 12, 4, 16, 7, 4, 1, 2, 22, 6, 12, 2, 11, 3, 1, 5, 11, 3, 5, 20, 14, 3, 12, 20, 7, 3, 0, 0, 24, 16, 12, 0, 12, 8, 0, 8, 12, 8, 3, 13, 18, 4, 3, 13, 9, 2, 12, 15, 9, 2, 2, 10, 22, 2, 2, 11, 22, 1, 6, 3, 11, 8, 6, 7, 11, 4, 14, 5, 6, 6, 14, 8, 6, 3, 0, 7, 24, 6, 0, 9, 24, 2, 14, 0, 10, 10, 19, 0, 5, 5, 14, 5, 5, 5, 0, 0, 10, 10, 0, 0, 5, 5, 5, 5, 5, 5, 0, 1, 24, 4, 12, 1, 12, 2, 0, 3, 12, 2, 0, 17, 18, 3, 0, 18, 18, 1, 5, 15, 16, 6, 13, 15, 8, 3, 5, 18, 8, 3, 3, 15, 16, 6, 3, 15, 8, 3, 11, 18, 8, 3, 6, 16, 18, 3, 6, 17, 18, 1, 0, 13, 21, 10, 0, 18, 21, 5, 13, 0, 6, 24, 15, 0, 2, 24, 7, 4, 6, 11, 9, 4, 2, 11, 9, 5, 9, 6, 12, 5, 3, 6, 1, 4, 2, 20, 1, 14, 2, 10, 13, 0, 6, 24, 15, 0, 2, 24, 5, 0, 6, 24, 7, 0, 2, 24, 16, 7, 6, 14, 19, 7, 3, 7, 16, 14, 3, 7, 4, 7, 4, 12, 6, 7, 2, 12, 0, 5, 24, 14, 8, 5, 8, 14, 5, 13, 10, 6, 5, 15, 10, 2, 12, 0, 6, 9, 14, 0, 2, 9, 2, 7, 6, 14, 2, 7, 3, 7, 5, 14, 3, 7, 15, 2, 9, 15, 18, 2, 3, 15, 0, 2, 6, 9, 2, 2, 2, 9, 12, 2, 10, 14, 17, 2, 5, 7, 12, 9, 5, 7, 11, 6, 2, 18, 12, 6, 1, 18, 9, 5, 15, 6, 14, 5, 5, 6, 8, 6, 6, 10, 10, 6, 2, 10, 12, 0, 6, 9, 14, 0, 2, 9, 3, 3, 9, 7, 6, 3, 3, 7, 6, 7, 14, 3, 6, 7, 7, 3, 7, 7, 8, 6, 11, 7, 4, 6, 12, 7, 7, 12, 12, 13, 7, 6, 10, 6, 4, 18, 10, 6, 2, 9, 12, 15, 2, 9, 16, 14, 6, 9, 16, 17, 6, 3, 4, 0, 6, 13, 6, 0, 2, 13, 2, 2, 21, 3, 9, 2, 7, 3, 5, 4, 5, 12, 5, 8, 5, 4, 10, 3, 4, 10, 10, 8, 4, 5, 8, 4, 5, 8, 8, 8, 5, 4, 6, 0, 11, 9, 6, 3, 11, 3, 6, 6, 12, 5, 10, 6, 4, 5, 0, 0, 24, 5, 8, 0, 8, 5, 1, 10, 23, 6, 1, 12, 23, 2, 3, 21, 18, 3, 9, 21, 6, 3, 3, 6, 21, 6, 3, 8, 21, 2, 0, 5, 6, 12, 2, 5, 2, 12, 10, 2, 4, 15, 10, 7, 4, 5, 8, 7, 8, 10, 8, 12, 8, 5, 5, 7, 15, 12, 10, 7, 5, 12, 0, 17, 10, 6, 0, 19, 10, 2, 14, 18, 9, 6, 14, 20, 9, 2, 9, 6, 6, 16, 9, 14, 6, 8, 14, 18, 9, 6, 14, 20, 9, 2, 1, 18, 9, 6, 1, 20, 9, 2, 15, 9, 9, 6, 15, 11, 9, 2, 0, 9, 9, 6, 0, 11, 9, 2, 17, 3, 6, 9, 19, 3, 2, 9, 2, 17, 18, 3, 2, 18, 18, 1, 3, 15, 21, 6, 3, 17, 21, 2, 9, 17, 6, 6, 9, 20, 6, 3, 18, 3, 6, 9, 18, 6, 6, 3, 0, 3, 6, 9, 0, 6, 6, 3, 4, 0, 16, 10, 12, 0, 8, 5, 4, 5, 8, 5, 2, 0, 10, 16, 2, 0, 5, 8, 7, 8, 5, 8, 14, 0, 10, 5, 14, 0, 5, 5, 0, 0, 10, 5, 5, 0, 5, 5, 18, 3, 6, 10, 18, 3, 3, 10, 5, 11, 12, 6, 5, 11, 6, 3, 11, 14, 6, 3, 21, 0, 3, 18, 22, 0, 1, 18, 6, 0, 6, 9, 8, 0, 2, 9, 8, 8, 9, 7, 11, 8, 3, 7, 7, 12, 8, 10, 7, 12, 4, 5, 11, 17, 4, 5, 21, 0, 3, 18, 22, 0, 1, 18, 10, 6, 4, 9, 12, 6, 2, 9, 15, 0, 9, 6, 15, 2, 9, 2, 0, 2, 24, 3, 0, 3, 24, 1, 11, 7, 6, 9, 13, 7, 2, 9, 7, 6, 6, 10, 9, 6, 2, 10, 12, 1, 6, 12, 14, 1, 2, 12, 6, 4, 12, 12, 6, 10, 12, 6, 14, 3, 2, 21, 14, 3, 1, 21, 6, 1, 12, 8, 6, 5, 12, 4, 3, 0, 18, 8, 3, 4, 18, 4, 3, 0, 18, 3, 3, 1, 18, 1, 0, 13, 24, 4, 12, 13, 12, 2, 0, 15, 12, 2, 10, 5, 4, 9, 12, 5, 2, 9, 11, 1, 6, 9, 13, 1, 2, 9, 6, 2, 6, 22, 8, 2, 2, 22, 16, 10, 8, 14, 20, 10, 4, 7, 16, 17, 4, 7, 3, 4, 16, 15, 3, 9, 16, 5, 16, 10, 8, 14, 20, 10, 4, 7, 16, 17, 4, 7, 0, 10, 8, 14, 0, 10, 4, 7, 4, 17, 4, 7, 10, 14, 11, 6, 10, 17, 11, 3, 0, 7, 24, 9, 8, 7, 8, 9, 13, 1, 4, 16, 13, 1, 2, 16, 7, 1, 4, 16, 9, 1, 2, 16, 5, 5, 16, 8, 13, 5, 8, 4, 5, 9, 8, 4, 0, 9, 6, 9, 0, 12, 6, 3, 6, 16, 18, 3, 6, 17, 18, 1, 3, 12, 6, 9, 3, 15, 6, 3, 8, 14, 9, 6, 8, 16, 9, 2, 2, 13, 8, 10, 2, 13, 4, 5, 6, 18, 4, 5, 15, 5, 3, 18, 15, 11, 3, 6, 3, 5, 18, 3, 3, 6, 18, 1, 17, 5, 6, 11, 19, 5, 2, 11, 1, 5, 6, 11, 3, 5, 2, 11, 19, 1, 4, 9, 19, 1, 2, 9, 1, 1, 4, 9, 3, 1, 2, 9, 4, 15, 18, 9, 4, 15, 9, 9, 6, 9, 12, 4, 6, 11, 12, 2, 15, 2, 9, 6, 15, 4, 9, 2, 0, 2, 9, 6, 0, 4, 9, 2, 15, 0, 6, 17, 17, 0, 2, 17, 3, 0, 6, 17, 5, 0, 2, 17, 8, 17, 9, 4, 8, 19, 9, 2, 6, 5, 3, 18, 6, 11, 3, 6, 5, 2, 14, 12, 5, 8, 14, 6, 10, 2, 3, 12, 10, 8, 3, 6, 10, 7, 14, 15, 10, 12, 14, 5, 0, 7, 14, 15, 0, 12, 14, 5, 15, 0, 9, 6, 15, 2, 9, 2, 0, 0, 9, 6, 0, 2, 9, 2, 12, 6, 6, 14, 14, 6, 2, 14, 9, 7, 6, 9, 11, 7, 2, 9, 12, 6, 6, 15, 14, 6, 2, 15, 6, 6, 6, 15, 8, 6, 2, 15, 15, 3, 8, 9, 15, 3, 4, 9, 0, 0, 9, 21, 3, 0, 3, 21, 11, 9, 8, 12, 11, 13, 8, 4, 6, 7, 10, 12, 6, 7, 5, 6, 11, 13, 5, 6, 10, 6, 4, 18, 12, 6, 2, 9, 10, 15, 2, 9, 0, 0, 6, 9, 0, 3, 6, 3, 3, 14, 18, 3, 3, 15, 18, 1, 3, 14, 8, 10, 3, 14, 4, 5, 7, 19, 4, 5, 0, 12, 24, 4, 12, 12, 12, 2, 0, 14, 12, 2, 0, 2, 3, 20, 1, 2, 1, 20, 12, 16, 10, 8, 17, 16, 5, 4, 12, 20, 5, 4, 2, 16, 10, 8, 2, 16, 5, 4, 7, 20, 5, 4, 7, 0, 10, 9, 7, 3, 10, 3, 0, 0, 24, 3, 8, 0, 8, 3, 3, 8, 15, 4, 3, 10, 15, 2, 6, 5, 12, 6, 10, 5, 4, 6, 5, 13, 14, 6, 5, 16, 14, 3, 11, 14, 4, 10, 11, 19, 4, 5, 0, 6, 6, 7, 3, 6, 3, 7, 18, 0, 6, 6, 18, 0, 3, 6, 3, 1, 18, 3, 3, 2, 18, 1, 9, 6, 14, 18, 9, 12, 14, 6, 0, 0, 6, 6, 3, 0, 3, 6, 13, 11, 6, 6, 13, 11, 3, 6, 0, 20, 24, 3, 8, 20, 8, 3, 13, 11, 6, 7, 13, 11, 3, 7, 4, 12, 10, 6, 4, 14, 10, 2, 13, 11, 6, 6, 13, 11, 3, 6, 5, 11, 6, 7, 8, 11, 3, 7, 7, 4, 11, 12, 7, 8, 11, 4, 6, 15, 10, 4, 6, 17, 10, 2, 14, 0, 6, 9, 16, 0, 2, 9, 4, 0, 6, 9, 6, 0, 2, 9, 11, 2, 4, 15, 11, 7, 4, 5, 0, 0, 20, 3, 0, 1, 20, 1, 13, 18, 10, 6, 13, 20, 10, 2, 2, 7, 6, 11, 5, 7, 3, 11, 10, 14, 10, 9, 10, 17, 10, 3, 8, 2, 4, 9, 10, 2, 2, 9, 14, 3, 10, 4, 14, 3, 5, 4, 6, 6, 12, 6, 6, 6, 6, 3, 12, 9, 6, 3, 8, 8, 8, 10, 12, 8, 4, 5, 8, 13, 4, 5, 7, 4, 4, 16, 7, 12, 4, 8, 8, 8, 9, 4, 8, 10, 9, 2, 5, 2, 14, 9, 5, 5, 14, 3, 3, 16, 19, 8, 3, 20, 19, 4, 0, 0, 10, 8, 5, 0, 5, 8, 5, 2, 16, 18, 5, 2, 8, 18, 0, 11, 24, 11, 8, 11, 8, 11, 3, 3, 18, 5, 3, 3, 9, 5, 1, 16, 18, 3, 1, 17, 18, 1, 5, 17, 18, 3, 5, 18, 18, 1, 1, 13, 9, 6, 1, 15, 9, 2, 1, 9, 23, 10, 1, 14, 23, 5, 3, 7, 18, 3, 3, 8, 18, 1, 6, 8, 12, 3, 6, 8, 6, 3, 6, 2, 3, 22, 7, 2, 1, 22, 14, 17, 10, 6, 14, 19, 10, 2, 1, 18, 10, 6, 1, 20, 10, 2, 11, 3, 6, 12, 13, 3, 2, 12, 10, 6, 4, 9, 12, 6, 2, 9, 11, 0, 6, 9, 13, 0, 2, 9, 7, 0, 6, 9, 9, 0, 2, 9, 12, 10, 9, 6, 15, 10, 3, 6, 2, 11, 6, 9, 5, 11, 3, 9, 14, 5, 3, 19, 15, 5, 1, 19, 6, 6, 9, 6, 6, 8, 9, 2, 14, 5, 3, 19, 15, 5, 1, 19, 0, 3, 6, 9, 0, 6, 6, 3, 5, 21, 18, 3, 5, 22, 18, 1, 1, 10, 18, 4, 7, 10, 6, 4, 13, 4, 8, 10, 17, 4, 4, 5, 13, 9, 4, 5, 7, 8, 9, 6, 10, 8, 3, 6, 12, 9, 9, 8, 15, 9, 3, 8, 0, 6, 5, 12, 0, 10, 5, 4, 7, 6, 14, 6, 14, 6, 7, 3, 7, 9, 7, 3, 7, 5, 3, 19, 8, 5, 1, 19, 8, 4, 15, 20, 13, 4, 5, 20, 1, 4, 15, 20, 6, 4, 5, 20, 13, 10, 6, 6, 13, 10, 3, 6, 5, 10, 6, 6, 8, 10, 3, 6, 14, 2, 6, 14, 17, 2, 3, 7, 14, 9, 3, 7, 4, 2, 6, 14, 4, 2, 3, 7, 7, 9, 3, 7, 12, 4, 6, 7, 12, 4, 3, 7, 9, 4, 6, 9, 11, 4, 2, 9, 11, 4, 8, 10, 11, 4, 4, 10, 5, 4, 8, 10, 9, 4, 4, 10, 8, 18, 10, 6, 8, 20, 10, 2, 1, 18, 21, 6, 1, 20, 21, 2, 9, 2, 12, 6, 9, 2, 6, 6, 3, 2, 12, 6, 9, 2, 6, 6, 12, 5, 12, 6, 18, 5, 6, 3, 12, 8, 6, 3, 8, 8, 6, 9, 8, 11, 6, 3, 2, 7, 20, 6, 2, 9, 20, 2, 0, 5, 12, 6, 0, 5, 6, 3, 6, 8, 6, 3, 14, 14, 8, 10, 18, 14, 4, 5, 14, 19, 4, 5, 2, 14, 8, 10, 2, 14, 4, 5, 6, 19, 4, 5, 2, 11, 20, 13, 2, 11, 10, 13, 6, 9, 12, 5, 12, 9, 6, 5, 5, 6, 16, 6, 13, 6, 8, 3, 5, 9, 8, 3, 1, 19, 9, 4, 1, 21, 9, 2, 7, 5, 12, 5, 11, 5, 4, 5, 3, 5, 14, 12, 3, 5, 7, 6, 10, 11, 7, 6, 9, 4, 9, 6, 12, 4, 3, 6, 2, 6, 19, 3, 2, 7, 19, 1, 18, 10, 6, 9, 18, 13, 6, 3, 3, 7, 18, 2, 3, 8, 18, 1, 20, 2, 4, 18, 22, 2, 2, 9, 20, 11, 2, 9, 2, 18, 20, 3, 2, 19, 20, 1, 1, 9, 22, 3, 1, 10, 22, 1, 0, 2, 4, 18, 0, 2, 2, 9, 2, 11, 2, 9, 19, 0, 4, 23, 19, 0, 2, 23, 0, 3, 6, 19, 3, 3, 3, 19, 18, 2, 6, 9, 20, 2, 2, 9, 0, 5, 10, 6, 0, 7, 10, 2, 7, 0, 12, 12, 13, 0, 6, 6, 7, 6, 6, 6, 0, 3, 24, 6, 0, 3, 12, 3, 12, 6, 12, 3, 10, 14, 4, 10, 10, 19, 4, 5, 8, 9, 4, 15, 8, 14, 4, 5, 4, 11, 17, 6, 4, 14, 17, 3, 2, 5, 18, 8, 2, 5, 9, 4, 11, 9, 9, 4, 7, 6, 14, 6, 14, 6, 7, 3, 7, 9, 7, 3, 3, 6, 14, 6, 3, 6, 7, 3, 10, 9, 7, 3, 16, 5, 3, 18, 17, 5, 1, 18, 5, 5, 3, 18, 6, 5, 1, 18, 10, 10, 14, 4, 10, 12, 14, 2, 4, 10, 9, 4, 4, 12, 9, 2, 2, 0, 18, 9, 2, 3, 18, 3, 6, 3, 12, 8, 10, 3, 4, 8, 1, 1, 8, 5, 5, 1, 4, 5, 12, 7, 7, 8, 12, 11, 7, 4, 0, 12, 22, 4, 0, 14, 22, 2, 15, 6, 4, 15, 15, 11, 4, 5, 5, 7, 7, 8, 5, 11, 7, 4, 8, 18, 9, 4, 8, 20, 9, 2, 1, 2, 22, 4, 1, 4, 22, 2, 17, 3, 6, 17, 19, 3, 2, 17, 8, 2, 8, 18, 8, 11, 8, 9, 17, 0, 6, 12, 20, 0, 3, 6, 17, 6, 3, 6, 7, 0, 6, 9, 9, 0, 2, 9, 15, 5, 9, 12, 15, 11, 9, 6, 2, 22, 18, 2, 2, 23, 18, 1, 10, 10, 12, 6, 16, 10, 6, 3, 10, 13, 6, 3, 0, 1, 4, 11, 2, 1, 2, 11, 20, 0, 4, 10, 20, 0, 2, 10, 1, 3, 6, 17, 3, 3, 2, 17, 15, 15, 9, 6, 15, 17, 9, 2, 0, 13, 8, 9, 0, 16, 8, 3, 16, 8, 6, 12, 16, 12, 6, 4, 2, 8, 6, 12, 2, 12, 6, 4, 10, 2, 4, 15, 10, 7, 4, 5, 1, 5, 19, 3, 1, 6, 19, 1, 11, 8, 9, 7, 14, 8, 3, 7, 3, 8, 12, 9, 3, 11, 12, 3, 3, 6, 18, 3, 3, 7, 18, 1, 10, 0, 4, 12, 10, 6, 4, 6, 3, 9, 18, 14, 3, 9, 9, 14, 0, 0, 4, 9, 2, 0, 2, 9, 12, 5, 4, 18, 12, 5, 2, 18, 8, 5, 4, 18, 10, 5, 2, 18, 10, 5, 6, 10, 12, 5, 2, 10, 9, 4, 4, 11, 11, 4, 2, 11, 4, 16, 18, 3, 4, 17, 18, 1, 0, 16, 20, 3, 0, 17, 20, 1, 9, 9, 6, 12, 9, 13, 6, 4, 8, 13, 8, 8, 8, 17, 8, 4, 13, 10, 3, 12, 13, 16, 3, 6, 5, 9, 14, 14, 5, 9, 7, 7, 12, 16, 7, 7, 0, 0, 24, 10, 12, 0, 12, 5, 0, 5, 12, 5, 1, 11, 18, 2, 1, 12, 18, 1, 19, 5, 5, 12, 19, 9, 5, 4, 0, 5, 5, 12, 0, 9, 5, 4, 16, 6, 8, 18, 20, 6, 4, 9, 16, 15, 4, 9, 0, 6, 8, 18, 0, 6, 4, 9, 4, 15, 4, 9, 12, 5, 12, 12, 18, 5, 6, 6, 12, 11, 6, 6, 7, 6, 6, 9, 9, 6, 2, 9, 9, 13, 6, 11, 11, 13, 2, 11, 0, 5, 12, 12, 0, 5, 6, 6, 6, 11, 6, 6, 1, 2, 23, 3, 1, 3, 23, 1, 1, 15, 19, 3, 1, 16, 19, 1, 13, 17, 11, 4, 13, 19, 11, 2, 0, 13, 8, 5, 4, 13, 4, 5, 12, 10, 10, 4, 12, 10, 5, 4, 4, 6, 9, 9, 4, 9, 9, 3, 15, 14, 9, 6, 15, 16, 9, 2, 1, 12, 9, 6, 1, 14, 9, 2, 3, 10, 20, 8, 13, 10, 10, 4, 3, 14, 10, 4, 2, 0, 9, 18, 5, 0, 3, 18, 13, 11, 9, 10, 16, 11, 3, 10, 1, 2, 8, 5, 5, 2, 4, 5, 3, 4, 21, 6, 10, 4, 7, 6, 7, 0, 10, 14, 7, 0, 5, 7, 12, 7, 5, 7, 12, 17, 12, 4, 12, 19, 12, 2, 0, 6, 23, 4, 0, 8, 23, 2, 13, 10, 8, 10, 17, 10, 4, 5, 13, 15, 4, 5, 0, 16, 18, 3, 0, 17, 18, 1, 15, 16, 9, 4, 15, 18, 9, 2, 0, 16, 9, 4, 0, 18, 9, 2, 13, 11, 6, 6, 13, 11, 3, 6, 5, 11, 6, 6, 8, 11, 3, 6, 0, 3, 24, 6, 12, 3, 12, 3, 0, 6, 12, 3, 2, 4, 18, 3, 2, 5, 18, 1, 0, 0, 24, 4, 12, 0, 12, 2, 0, 2, 12, 2, 1, 16, 18, 3, 1, 17, 18, 1, 15, 15, 9, 6, 15, 17, 9, 2, 0, 15, 9, 6, 0, 17, 9, 2, 6, 17, 18, 3, 6, 18, 18, 1, 8, 8, 6, 10, 10, 8, 2, 10, 10, 6, 6, 9, 12, 6, 2, 9, 8, 8, 5, 8, 8, 12, 5, 4, 12, 8, 6, 8, 12, 12, 6, 4, 6, 5, 6, 11, 8, 5, 2, 11, 13, 6, 8, 9, 13, 9, 8, 3, 1, 7, 21, 6, 1, 9, 21, 2, 15, 5, 3, 12, 15, 11, 3, 6, 6, 9, 11, 12, 6, 13, 11, 4, 13, 8, 10, 8, 18, 8, 5, 4, 13, 12, 5, 4, 5, 8, 12, 3, 11, 8, 6, 3, 6, 11, 18, 4, 12, 11, 6, 4, 0, 0, 22, 22, 0, 11, 22, 11, 11, 2, 6, 8, 11, 6, 6, 4, 9, 0, 6, 9, 11, 0, 2, 9, 10, 0, 6, 9, 12, 0, 2, 9, 8, 3, 6, 14, 8, 3, 3, 7, 11, 10, 3, 7, 3, 10, 18, 8, 9, 10, 6, 8, 10, 0, 3, 14, 10, 7, 3, 7, 4, 3, 16, 20, 4, 13, 16, 10, 9, 4, 6, 10, 11, 4, 2, 10, 5, 0, 16, 4, 5, 2, 16, 2, 2, 5, 18, 4, 8, 5, 6, 4, 13, 0, 6, 9, 15, 0, 2, 9, 8, 4, 8, 5, 12, 4, 4, 5, 12, 10, 10, 4, 12, 10, 5, 4, 2, 10, 10, 4, 7, 10, 5, 4, 7, 11, 12, 5, 11, 11, 4, 5, 3, 10, 8, 10, 3, 10, 4, 5, 7, 15, 4, 5, 11, 12, 9, 8, 14, 12, 3, 8, 0, 21, 24, 3, 8, 21, 8, 3, 3, 20, 18, 4, 9, 20, 6, 4, 1, 15, 9, 6, 1, 17, 9, 2, 11, 17, 10, 4, 11, 19, 10, 2, 9, 12, 4, 12, 9, 18, 4, 6, 9, 6, 9, 6, 12, 6, 3, 6, 1, 13, 6, 9, 1, 16, 6, 3, 6, 16, 12, 4, 6, 18, 12, 2, 1, 5, 20, 3, 1, 6, 20, 1, 8, 1, 9, 9, 8, 4, 9, 3, 2, 19, 9, 4, 2, 21, 9, 2, 11, 1, 4, 18, 11, 7, 4, 6, 7, 2, 8, 12, 7, 2, 4, 6, 11, 8, 4, 6, 11, 10, 9, 8, 14, 10, 3, 8, 5, 11, 12, 5, 9, 11, 4, 5, 11, 9, 9, 6, 14, 9, 3, 6, 5, 10, 6, 9, 7, 10, 2, 9, 4, 7, 5, 12, 4, 11, 5, 4, 2, 0, 21, 6, 9, 0, 7, 6, 7, 6, 10, 6, 7, 8, 10, 2, 9, 0, 6, 15, 11, 0, 2, 15, 2, 2, 18, 2, 2, 3, 18, 1, 8, 17, 8, 6, 8, 20, 8, 3, 3, 0, 18, 2, 3, 1, 18, 1, 8, 0, 9, 6, 11, 0, 3, 6, 0, 17, 18, 3, 0, 18, 18, 1, 6, 7, 12, 5, 10, 7, 4, 5, 0, 3, 6, 9, 2, 3, 2, 9, 20, 2, 4, 9, 20, 2, 2, 9, 0, 2, 4, 9, 2, 2, 2, 9, 0, 1, 24, 4, 12, 1, 12, 2, 0, 3, 12, 2, 0, 16, 9, 6, 0, 18, 9, 2, 14, 13, 9, 6, 14, 15, 9, 2, 0, 15, 19, 3, 0, 16, 19, 1, 1, 5, 22, 12, 12, 5, 11, 6, 1, 11, 11, 6, 5, 13, 6, 6, 8, 13, 3, 6, 4, 2, 20, 3, 4, 3, 20, 1, 8, 14, 6, 10, 10, 14, 2, 10, 6, 12, 16, 6, 14, 12, 8, 3, 6, 15, 8, 3, 2, 13, 8, 9, 2, 16, 8, 3, 11, 8, 6, 14, 14, 8, 3, 7, 11, 15, 3, 7, 2, 12, 16, 6, 2, 12, 8, 3, 10, 15, 8, 3, 5, 16, 16, 8, 5, 20, 16, 4, 9, 1, 4, 12, 9, 7, 4, 6, 8, 2, 8, 10, 12, 2, 4, 5, 8, 7, 4, 5, 6, 6, 12, 6, 6, 6, 6, 3, 12, 9, 6, 3, 10, 7, 6, 9, 12, 7, 2, 9, 0, 0, 8, 12, 0, 0, 4, 6, 4, 6, 4, 6, 18, 8, 6, 9, 18, 11, 6, 3, 2, 12, 6, 6, 5, 12, 3, 6, 3, 21, 21, 3, 10, 21, 7, 3, 2, 0, 16, 6, 2, 3, 16, 3, 13, 6, 7, 6, 13, 9, 7, 3, 6, 4, 4, 14, 6, 11, 4, 7, 9, 7, 6, 9, 11, 7, 2, 9, 7, 8, 6, 14, 7, 8, 3, 7, 10, 15, 3, 7, 18, 8, 4, 16, 18, 16, 4, 8, 9, 14, 6, 10, 11, 14, 2, 10, 6, 11, 12, 5, 10, 11, 4, 5, 0, 12, 23, 3, 0, 13, 23, 1, 13, 0, 6, 12, 15, 0, 2, 12, 0, 10, 12, 5, 4, 10, 4, 5, 13, 2, 10, 4, 13, 4, 10, 2, 5, 0, 6, 12, 7, 0, 2, 12, 11, 6, 9, 6, 14, 6, 3, 6, 4, 6, 9, 6, 7, 6, 3, 6, 6, 11, 18, 13, 12, 11, 6, 13, 0, 11, 18, 13, 6, 11, 6, 13, 12, 16, 12, 6, 16, 16, 4, 6, 0, 6, 21, 3, 0, 7, 21, 1, 12, 16, 12, 6, 16, 16, 4, 6, 5, 7, 6, 14, 5, 14, 6, 7, 5, 10, 19, 2, 5, 11, 19, 1, 5, 4, 14, 4, 5, 6, 14, 2, 3, 18, 18, 4, 9, 18, 6, 4, 7, 0, 4, 9, 9, 0, 2, 9, 13, 3, 11, 4, 13, 5, 11, 2, 2, 0, 9, 6, 5, 0, 3, 6, 19, 1, 4, 23, 19, 1, 2, 23, 1, 1, 4, 23, 3, 1, 2, 23, 5, 16, 18, 3, 5, 17, 18, 1, 0, 3, 11, 4, 0, 5, 11, 2, 2, 16, 20, 3, 2, 17, 20, 1, 5, 3, 13, 4, 5, 5, 13, 2, 1, 9, 22, 15, 1, 9, 11, 15, 3, 4, 14, 3, 10, 4, 7, 3, 8, 7, 10, 4, 8, 7, 5, 4, 6, 7, 10, 4, 11, 7, 5, 4, 10, 4, 6, 9, 12, 4, 2, 9, 1, 12, 9, 6, 4, 12, 3, 6, 8, 3, 8, 10, 12, 3, 4, 5, 8, 8, 4, 5, 3, 6, 16, 6, 3, 6, 8, 3, 11, 9, 8, 3, 5, 6, 14, 6, 5, 9, 14, 3, 4, 3, 9, 6, 4, 5, 9, 2, 6, 3, 18, 2, 6, 4, 18, 1, 7, 6, 9, 6, 10, 6, 3, 6, 0, 1, 24, 3, 0, 2, 24, 1, 0, 17, 10, 6, 0, 19, 10, 2, 3, 18, 18, 3, 3, 19, 18, 1, 2, 5, 6, 16, 2, 5, 3, 8, 5, 13, 3, 8, 7, 6, 11, 6, 7, 8, 11, 2, 5, 2, 12, 22, 5, 13, 12, 11, 10, 7, 4, 10, 10, 12, 4, 5, 9, 0, 4, 18, 9, 6, 4, 6, 18, 8, 6, 9, 18, 11, 6, 3, 4, 7, 15, 10, 9, 7, 5, 10, 10, 5, 6, 9, 12, 5, 2, 9, 9, 9, 6, 10, 11, 9, 2, 10, 11, 14, 6, 10, 13, 14, 2, 10, 7, 14, 6, 10, 9, 14, 2, 10, 4, 8, 16, 9, 4, 11, 16, 3, 2, 11, 20, 3, 2, 12, 20, 1, 13, 0, 4, 13, 13, 0, 2, 13, 7, 0, 4, 13, 9, 0, 2, 13, 3, 1, 18, 7, 9, 1, 6, 7, 1, 11, 6, 9, 1, 14, 6, 3, 8, 18, 9, 6, 8, 20, 9, 2, 3, 9, 15, 6, 3, 11, 15, 2, 5, 10, 19, 2, 5, 11, 19, 1, 8, 6, 7, 16, 8, 14, 7, 8, 9, 14, 9, 6, 9, 16, 9, 2, 0, 7, 8, 12, 0, 11, 8, 4, 6, 4, 18, 3, 6, 5, 18, 1, 0, 16, 12, 6, 4, 16, 4, 6, 13, 13, 9, 4, 13, 15, 9, 2, 5, 8, 14, 14, 5, 8, 7, 7, 12, 15, 7, 7, 1, 16, 22, 6, 12, 16, 11, 3, 1, 19, 11, 3, 9, 0, 6, 9, 11, 0, 2, 9, 9, 5, 10, 10, 14, 5, 5, 5, 9, 10, 5, 5, 5, 5, 10, 10, 5, 5, 5, 5, 10, 10, 5, 5, 4, 6, 16, 6, 12, 6, 8, 3, 4, 9, 8, 3, 0, 7, 6, 9, 0, 10, 6, 3, 16, 10, 8, 14, 20, 10, 4, 7, 16, 17, 4, 7, 9, 12, 6, 12, 9, 18, 6, 6, 8, 10, 8, 12, 12, 10, 4, 6, 8, 16, 4, 6, 8, 0, 4, 9, 10, 0, 2, 9, 10, 4, 8, 16, 14, 4, 4, 8, 10, 12, 4, 8, 7, 10, 10, 6, 7, 12, 10, 2, 5, 6, 14, 14, 12, 6, 7, 7, 5, 13, 7, 7, 2, 11, 20, 2, 2, 12, 20, 1, 18, 8, 4, 16, 18, 16, 4, 8, 1, 11, 12, 10, 1, 11, 6, 5, 7, 16, 6, 5, 6, 9, 12, 4, 6, 11, 12, 2, 9, 12, 6, 7, 12, 12, 3, 7, 10, 4, 8, 16, 14, 4, 4, 8, 10, 12, 4, 8, 6, 4, 8, 16, 6, 4, 4, 8, 10, 12, 4, 8, 8, 9, 9, 6, 11, 9, 3, 6, 1, 5, 16, 12, 1, 5, 8, 6, 9, 11, 8, 6, 9, 9, 6, 8, 9, 9, 3, 8, 6, 0, 3, 18, 7, 0, 1, 18, 17, 9, 5, 14, 17, 16, 5, 7, 2, 9, 5, 14, 2, 16, 5, 7, 7, 4, 10, 6, 7, 7, 10, 3, 1, 3, 23, 18, 1, 9, 23, 6, 1, 1, 21, 3, 8, 1, 7, 3, 9, 6, 6, 9, 11, 6, 2, 9, 3, 18, 12, 6, 3, 18, 6, 3, 9, 21, 6, 3, 16, 8, 8, 16, 20, 8, 4, 8, 16, 16, 4, 8, 0, 19, 24, 4, 8, 19, 8, 4, 16, 8, 8, 16, 20, 8, 4, 8, 16, 16, 4, 8, 0, 8, 8, 16, 0, 8, 4, 8, 4, 16, 4, 8, 8, 12, 8, 10, 8, 17, 8, 5, 5, 7, 5, 8, 5, 11, 5, 4, 4, 1, 19, 2, 4, 2, 19, 1, 0, 12, 24, 9, 8, 12, 8, 9, 6, 0, 13, 8, 6, 4, 13, 4, 0, 0, 24, 3, 0, 1, 24, 1, 20, 3, 4, 11, 20, 3, 2, 11, 8, 6, 6, 9, 10, 6, 2, 9, 6, 11, 12, 8, 12, 11, 6, 4, 6, 15, 6, 4, 0, 8, 12, 6, 0, 8, 6, 3, 6, 11, 6, 3, 6, 17, 18, 3, 6, 18, 18, 1, 0, 14, 9, 6, 0, 16, 9, 2, 20, 3, 4, 9, 20, 3, 2, 9, 0, 3, 4, 9, 2, 3, 2, 9, 15, 0, 9, 19, 18, 0, 3, 19, 0, 0, 9, 19, 3, 0, 3, 19, 13, 11, 6, 8, 13, 11, 3, 8, 5, 11, 6, 8, 8, 11, 3, 8, 5, 11, 19, 3, 5, 12, 19, 1, 3, 20, 18, 4, 9, 20, 6, 4, 6, 6, 16, 6, 6, 8, 16, 2, 6, 0, 9, 6, 9, 0, 3, 6, 10, 3, 4, 14, 10, 10, 4, 7, 1, 5, 15, 12, 1, 11, 15, 6, 11, 12, 8, 5, 11, 12, 4, 5, 5, 0, 6, 9, 7, 0, 2, 9, 12, 0, 6, 9, 14, 0, 2, 9, 5, 5, 12, 8, 5, 5, 6, 4, 11, 9, 6, 4, 13, 12, 11, 6, 13, 14, 11, 2, 0, 13, 21, 3, 0, 14, 21, 1, 8, 1, 8, 12, 12, 1, 4, 6, 8, 7, 4, 6, 1, 0, 6, 12, 1, 0, 3, 6, 4, 6, 3, 6, 2, 2, 21, 2, 2, 3, 21, 1, 2, 2, 19, 3, 2, 3, 19, 1, 17, 10, 6, 14, 20, 10, 3, 7, 17, 17, 3, 7, 1, 10, 6, 14, 1, 10, 3, 7, 4, 17, 3, 7, 7, 6, 14, 14, 14, 6, 7, 7, 7, 13, 7, 7, 0, 12, 9, 6, 0, 14, 9, 2, 15, 14, 8, 9, 15, 17, 8, 3, 1, 1, 22, 4, 1, 1, 11, 2, 12, 3, 11, 2, 9, 11, 9, 6, 9, 13, 9, 2, 0, 15, 18, 3, 0, 16, 18, 1, 16, 14, 7, 9, 16, 17, 7, 3, 4, 3, 16, 4, 12, 3, 8, 4, 7, 6, 12, 5, 7, 6, 6, 5, 9, 6, 4, 9, 11, 6, 2, 9, 12, 1, 4, 10, 12, 1, 2, 10, 8, 1, 4, 10, 10, 1, 2, 10, 15, 15, 6, 9, 15, 18, 6, 3, 3, 15, 6, 9, 3, 18, 6, 3, 15, 1, 3, 19, 16, 1, 1, 19, 1, 3, 6, 9, 3, 3, 2, 9, 15, 0, 3, 19, 16, 0, 1, 19, 6, 3, 12, 4, 12, 3, 6, 4, 10, 5, 4, 9, 10, 5, 2, 9, 6, 0, 3, 19, 7, 0, 1, 19, 11, 1, 3, 12, 11, 7, 3, 6, 6, 7, 10, 5, 11, 7, 5, 5, 11, 3, 3, 18, 12, 3, 1, 18, 9, 3, 6, 12, 11, 3, 2, 12, 3, 7, 19, 3, 3, 8, 19, 1, 2, 7, 18, 3, 2, 8, 18, 1, 3, 13, 18, 4, 12, 13, 9, 2, 3, 15, 9, 2, 3, 5, 6, 9, 5, 5, 2, 9, 4, 1, 20, 4, 14, 1, 10, 2, 4, 3, 10, 2, 0, 1, 20, 4, 0, 1, 10, 2, 10, 3, 10, 2, 10, 15, 6, 6, 10, 15, 3, 6, 0, 2, 24, 8, 8, 2, 8, 8, 5, 5, 18, 3, 5, 6, 18, 1, 8, 15, 6, 6, 11, 15, 3, 6, 11, 12, 8, 5, 11, 12, 4, 5, 5, 12, 8, 5, 9, 12, 4, 5, 5, 0, 14, 6, 5, 2, 14, 2, 10, 2, 4, 15, 10, 7, 4, 5, 10, 7, 5, 12, 10, 11, 5, 4, 7, 9, 8, 14, 7, 9, 4, 7, 11, 16, 4, 7, 1, 5, 22, 6, 12, 5, 11, 3, 1, 8, 11, 3, 0, 5, 6, 6, 0, 8, 6, 3, 12, 17, 9, 4, 12, 19, 9, 2, 2, 18, 19, 3, 2, 19, 19, 1, 12, 17, 9, 4, 12, 19, 9, 2, 1, 17, 18, 3, 1, 18, 18, 1, 12, 17, 9, 4, 12, 19, 9, 2, 0, 0, 24, 3, 0, 1, 24, 1, 5, 0, 14, 4, 5, 2, 14, 2, 6, 14, 9, 6, 6, 16, 9, 2, 14, 13, 6, 9, 14, 16, 6, 3, 5, 20, 13, 4, 5, 22, 13, 2, 9, 9, 6, 12, 9, 13, 6, 4, 1, 10, 21, 3, 8, 10, 7, 3, 8, 8, 9, 6, 11, 8, 3, 6, 3, 10, 9, 7, 6, 10, 3, 7, 12, 10, 10, 8, 17, 10, 5, 4, 12, 14, 5, 4, 0, 15, 24, 3, 8, 15, 8, 3, 8, 5, 9, 6, 8, 7, 9, 2, 4, 13, 6, 9, 4, 16, 6, 3, 12, 17, 9, 4, 12, 19, 9, 2, 9, 12, 6, 6, 9, 15, 6, 3, 9, 9, 14, 10, 16, 9, 7, 5, 9, 14, 7, 5, 1, 9, 14, 10, 1, 9, 7, 5, 8, 14, 7, 5, 8, 7, 9, 17, 11, 7, 3, 17, 3, 4, 6, 20, 3, 4, 3, 10, 6, 14, 3, 10, 7, 8, 10, 4, 7, 8, 5, 4, 10, 7, 4, 9, 12, 7, 2, 9, 10, 15, 6, 9, 12, 15, 2, 9, 3, 8, 6, 16, 3, 8, 3, 8, 6, 16, 3, 8, 12, 17, 9, 4, 12, 19, 9, 2, 3, 17, 9, 4, 3, 19, 9, 2, 10, 1, 9, 6, 13, 1, 3, 6, 5, 7, 4, 10, 5, 12, 4, 5, 7, 5, 12, 6, 11, 5, 4, 6, 6, 4, 9, 8, 9, 4, 3, 8, 12, 16, 10, 8, 17, 16, 5, 4, 12, 20, 5, 4, 2, 16, 10, 8, 2, 16, 5, 4, 7, 20, 5, 4, 0, 0, 24, 4, 12, 0, 12, 2, 0, 2, 12, 2, 0, 6, 9, 6, 0, 8, 9, 2, 0, 4, 24, 6, 12, 4, 12, 3, 0, 7, 12, 3, 5, 0, 11, 4, 5, 2, 11, 2, 1, 1, 22, 4, 12, 1, 11, 2, 1, 3, 11, 2, 9, 6, 6, 18, 9, 15, 6, 9, 2, 9, 20, 4, 2, 11, 20, 2, 5, 2, 14, 14, 5, 9, 14, 7, 4, 2, 16, 6, 4, 5, 16, 3, 2, 3, 19, 3, 2, 4, 19, 1, 7, 1, 10, 4, 7, 3, 10, 2, 0, 9, 4, 15, 0, 14, 4, 5, 2, 10, 21, 3, 2, 11, 21, 1, 3, 0, 6, 6, 6, 0, 3, 6, 6, 4, 14, 9, 6, 7, 14, 3, 9, 1, 6, 9, 11, 1, 2, 9, 15, 8, 9, 9, 15, 11, 9, 3, 8, 0, 4, 21, 8, 7, 4, 7, 3, 22, 19, 2, 3, 23, 19, 1, 2, 15, 20, 3, 2, 16, 20, 1, 19, 0, 4, 13, 19, 0, 2, 13, 1, 7, 8, 8, 1, 11, 8, 4, 14, 14, 6, 9, 14, 17, 6, 3, 4, 14, 6, 9, 4, 17, 6, 3, 14, 5, 4, 10, 14, 5, 2, 10, 6, 5, 4, 10, 8, 5, 2, 10, 14, 5, 6, 6, 14, 8, 6, 3, 4, 5, 6, 6, 4, 8, 6, 3, 0, 2, 24, 21, 8, 2, 8, 21, 1, 2, 6, 13, 3, 2, 2, 13, 20, 0, 4, 21, 20, 0, 2, 21, 0, 4, 4, 20, 2, 4, 2, 20, 8, 16, 9, 6, 8, 18, 9, 2, 7, 0, 6, 9, 9, 0, 2, 9, 16, 12, 7, 9, 16, 15, 7, 3, 5, 21, 14, 3, 12, 21, 7, 3, 11, 5, 6, 9, 11, 5, 3, 9, 10, 5, 4, 10, 12, 5, 2, 10, 10, 6, 6, 9, 12, 6, 2, 9, 7, 5, 6, 9, 10, 5, 3, 9, 14, 14, 10, 4, 14, 16, 10, 2, 5, 5, 14, 14, 5, 5, 7, 7, 12, 12, 7, 7, 12, 8, 12, 6, 18, 8, 6, 3, 12, 11, 6, 3, 6, 6, 12, 12, 6, 6, 6, 6, 12, 12, 6, 6, 11, 13, 6, 10, 13, 13, 2, 10, 1, 10, 20, 8, 1, 10, 10, 4, 11, 14, 10, 4, 15, 13, 9, 6, 15, 15, 9, 2, 9, 0, 6, 9, 9, 3, 6, 3, 10, 1, 5, 14, 10, 8, 5, 7, 3, 4, 16, 6, 3, 6, 16, 2, 16, 3, 8, 9, 16, 6, 8, 3, 7, 13, 6, 10, 9, 13, 2, 10, 15, 13, 9, 6, 15, 15, 9, 2, 0, 13, 9, 6, 0, 15, 9, 2, 13, 16, 9, 6, 13, 18, 9, 2, 2, 16, 9, 6, 2, 18, 9, 2, 5, 16, 18, 3, 5, 17, 18, 1, 1, 16, 18, 3, 1, 17, 18, 1, 5, 0, 18, 3, 5, 1, 18, 1, 1, 1, 19, 2, 1, 2, 19, 1, 14, 2, 6, 11, 16, 2, 2, 11, 4, 15, 15, 6, 9, 15, 5, 6, 14, 2, 6, 11, 16, 2, 2, 11, 4, 2, 6, 11, 6, 2, 2, 11, 18, 2, 6, 9, 18, 5, 6, 3, 1, 2, 22, 4, 1, 2, 11, 2, 12, 4, 11, 2, 2, 0, 21, 12, 9, 0, 7, 12, 0, 12, 18, 3, 0, 13, 18, 1, 12, 2, 6, 9, 14, 2, 2, 9, 3, 10, 18, 3, 3, 11, 18, 1, 16, 3, 8, 9, 16, 6, 8, 3, 3, 7, 18, 3, 3, 8, 18, 1, 9, 11, 6, 9, 11, 11, 2, 9, 9, 8, 6, 9, 11, 8, 2, 9, 15, 0, 2, 18, 15, 0, 1, 18, 7, 0, 2, 18, 8, 0, 1, 18, 17, 3, 7, 9, 17, 6, 7, 3, 3, 18, 9, 6, 3, 20, 9, 2, 3, 18, 21, 3, 3, 19, 21, 1, 0, 3, 7, 9, 0, 6, 7, 3, 2, 7, 22, 3, 2, 8, 22, 1, 0, 3, 24, 16, 0, 3, 12, 8, 12, 11, 12, 8, 13, 17, 9, 4, 13, 19, 9, 2, 5, 5, 12, 8, 5, 5, 6, 4, 11, 9, 6, 4, 5, 6, 14, 6, 12, 6, 7, 3, 5, 9, 7, 3, 5, 16, 14, 6, 5, 16, 7, 3, 12, 19, 7, 3, 18, 2, 6, 9, 18, 5, 6, 3, 0, 2, 6, 9, 0, 5, 6, 3, 3, 4, 20, 10, 13, 4, 10, 5, 3, 9, 10, 5, 2, 13, 9, 8, 5, 13, 3, 8, 2, 1, 21, 15, 9, 1, 7, 15, 5, 12, 14, 8, 12, 12, 7, 8, 6, 7, 12, 4, 6, 7, 6, 4, 6, 5, 9, 6, 9, 5, 3, 6, 13, 11, 6, 6, 13, 11, 3, 6, 5, 11, 6, 6, 8, 11, 3, 6, 6, 4, 18, 2, 6, 5, 18, 1, 0, 2, 6, 11, 2, 2, 2, 11, 18, 0, 6, 15, 20, 0, 2, 15, 0, 0, 6, 13, 2, 0, 2, 13, 12, 0, 6, 9, 14, 0, 2, 9, 6, 0, 6, 9, 8, 0, 2, 9, 0, 2, 24, 4, 8, 2, 8, 4, 3, 13, 18, 4, 12, 13, 9, 4, 9, 7, 10, 4, 9, 7, 5, 4, 5, 8, 12, 3, 11, 8, 6, 3, 4, 14, 19, 3, 4, 15, 19, 1, 10, 0, 4, 20, 10, 10, 4, 10, 8, 15, 9, 6, 8, 17, 9, 2, 2, 9, 15, 4, 7, 9, 5, 4, 8, 4, 12, 7, 12, 4, 4, 7, 0, 10, 6, 9, 0, 13, 6, 3, 18, 5, 6, 9, 18, 8, 6, 3, 0, 18, 16, 6, 0, 18, 8, 3, 8, 21, 8, 3, 9, 18, 14, 6, 16, 18, 7, 3, 9, 21, 7, 3, 1, 20, 20, 4, 1, 20, 10, 2, 11, 22, 10, 2, 2, 8, 20, 6, 12, 8, 10, 3, 2, 11, 10, 3, 7, 8, 6, 9, 9, 8, 2, 9, 8, 5, 12, 8, 12, 5, 4, 8, 4, 5, 12, 8, 8, 5, 4, 8, 10, 6, 6, 9, 12, 6, 2, 9, 2, 0, 6, 16, 4, 0, 2, 16, 15, 4, 6, 12, 15, 8, 6, 4, 3, 4, 6, 12, 3, 8, 6, 4, 15, 12, 9, 6, 15, 14, 9, 2, 4, 0, 15, 22, 4, 11, 15, 11, 15, 12, 9, 6, 15, 14, 9, 2, 0, 12, 9, 6, 0, 14, 9, 2, 15, 15, 9, 6, 15, 17, 9, 2, 0, 15, 9, 6, 0, 17, 9, 2, 10, 0, 8, 10, 14, 0, 4, 5, 10, 5, 4, 5, 1, 0, 4, 16, 3, 0, 2, 16, 7, 6, 10, 6, 7, 8, 10, 2, 10, 12, 4, 10, 10, 17, 4, 5, 8, 4, 10, 6, 8, 6, 10, 2, 3, 22, 18, 2, 12, 22, 9, 2, 7, 7, 11, 6, 7, 9, 11, 2, 0, 0, 12, 10, 0, 0, 6, 5, 6, 5, 6, 5, 10, 1, 12, 6, 16, 1, 6, 3, 10, 4, 6, 3, 7, 16, 9, 4, 7, 18, 9, 2, 5, 7, 15, 16, 10, 7, 5, 16, 5, 10, 12, 13, 11, 10, 6, 13, 6, 2, 12, 6, 12, 2, 6, 3, 6, 5, 6, 3, 3, 9, 12, 9, 3, 12, 12, 3, 16, 2, 8, 6, 16, 5, 8, 3, 0, 2, 8, 6, 0, 5, 8, 3, 0, 3, 24, 11, 0, 3, 12, 11, 0, 13, 8, 10, 0, 13, 4, 5, 4, 18, 4, 5, 10, 14, 4, 10, 10, 19, 4, 5, 10, 2, 4, 21, 10, 9, 4, 7, 4, 4, 15, 9, 4, 7, 15, 3, 0, 1, 24, 6, 8, 1, 8, 6, 9, 6, 5, 16, 9, 14, 5, 8, 3, 21, 18, 3, 9, 21, 6, 3, 6, 5, 3, 12, 6, 11, 3, 6, 11, 6, 4, 9, 11, 6, 2, 9, 5, 6, 9, 8, 8, 6, 3, 8, 4, 3, 20, 2, 4, 4, 20, 1, 2, 10, 18, 3, 8, 10, 6, 3, 7, 15, 10, 6, 7, 17, 10, 2, 1, 4, 4, 18, 1, 4, 2, 9, 3, 13, 2, 9, 13, 0, 6, 9, 15, 0, 2, 9, 5, 0, 6, 9, 7, 0, 2, 9, 11, 0, 6, 9, 13, 0, 2, 9, 6, 7, 9, 6, 9, 7, 3, 6, 3, 0, 18, 2, 3, 1, 18, 1, 0, 10, 20, 4, 0, 10, 10, 2, 10, 12, 10, 2, 10, 2, 4, 12, 10, 8, 4, 6, 6, 5, 6, 12, 6, 5, 3, 6, 9, 11, 3, 6, 6, 0, 18, 22, 15, 0, 9, 11, 6, 11, 9, 11, 0, 0, 18, 22, 0, 0, 9, 11, 9, 11, 9, 11, 18, 2, 6, 11, 20, 2, 2, 11, 0, 2, 6, 11, 2, 2, 2, 11, 11, 0, 6, 9, 13, 0, 2, 9, 0, 0, 20, 3, 0, 1, 20, 1, 2, 2, 20, 2, 2, 3, 20, 1, 1, 10, 18, 2, 1, 11, 18, 1, 18, 7, 6, 9, 18, 10, 6, 3, 0, 0, 22, 9, 0, 3, 22, 3, 17, 3, 6, 9, 17, 6, 6, 3, 0, 7, 6, 9, 0, 10, 6, 3, 0, 6, 24, 6, 0, 8, 24, 2, 0, 2, 6, 10, 2, 2, 2, 10, 10, 6, 6, 9, 12, 6, 2, 9, 7, 0, 6, 9, 9, 0, 2, 9, 15, 0, 6, 9, 17, 0, 2, 9, 3, 0, 6, 9, 5, 0, 2, 9, 15, 17, 9, 6, 15, 19, 9, 2, 0, 17, 18, 3, 0, 18, 18, 1, 15, 14, 9, 6, 15, 16, 9, 2, 0, 15, 23, 6, 0, 17, 23, 2, 5, 15, 18, 3, 5, 16, 18, 1, 0, 14, 9, 6, 0, 16, 9, 2, 9, 8, 8, 10, 13, 8, 4, 5, 9, 13, 4, 5, 3, 7, 15, 6, 8, 7, 5, 6, 9, 8, 8, 10, 13, 8, 4, 5, 9, 13, 4, 5, 5, 0, 6, 12, 8, 0, 3, 12, 9, 8, 8, 10, 13, 8, 4, 5, 9, 13, 4, 5, 8, 5, 6, 9, 10, 5, 2, 9, 10, 6, 4, 18, 12, 6, 2, 9, 10, 15, 2, 9, 5, 7, 12, 4, 11, 7, 6, 4, 9, 8, 8, 10, 13, 8, 4, 5, 9, 13, 4, 5, 7, 8, 8, 10, 7, 8, 4, 5, 11, 13, 4, 5, 11, 10, 6, 14, 14, 10, 3, 7, 11, 17, 3, 7, 9, 5, 6, 19, 12, 5, 3, 19, 6, 12, 12, 6, 12, 12, 6, 3, 6, 15, 6, 3, 1, 9, 18, 6, 1, 9, 9, 3, 10, 12, 9, 3, 16, 14, 8, 10, 20, 14, 4, 5, 16, 19, 4, 5, 0, 9, 22, 8, 0, 9, 11, 4, 11, 13, 11, 4, 8, 18, 12, 6, 14, 18, 6, 3, 8, 21, 6, 3, 0, 6, 20, 18, 0, 6, 10, 9, 10, 15, 10, 9, 3, 6, 20, 12, 13, 6, 10, 6, 3, 12, 10, 6, 0, 16, 10, 8, 0, 16, 5, 4, 5, 20, 5, 4, 6, 16, 18, 3, 6, 17, 18, 1, 0, 11, 19, 3, 0, 12, 19, 1, 14, 6, 6, 9, 14, 9, 6, 3, 1, 7, 22, 4, 1, 7, 11, 2, 12, 9, 11, 2, 13, 6, 7, 12, 13, 10, 7, 4, 4, 7, 11, 9, 4, 10, 11, 3, 12, 10, 10, 8, 17, 10, 5, 4, 12, 14, 5, 4, 2, 12, 9, 7, 5, 12, 3, 7, 16, 14, 6, 9, 16, 17, 6, 3, 3, 12, 6, 12, 3, 16, 6, 4, 14, 13, 6, 6, 14, 16, 6, 3, 8, 0, 6, 9, 10, 0, 2, 9, 9, 1, 6, 23, 11, 1, 2, 23, 0, 16, 9, 6, 0, 18, 9, 2, 4, 17, 18, 3, 4, 18, 18, 1, 5, 2, 13, 14, 5, 9, 13, 7, 15, 0, 8, 12, 19, 0, 4, 6, 15, 6, 4, 6, 0, 0, 8, 12, 0, 0, 4, 6, 4, 6, 4, 6, 8, 2, 8, 7, 8, 2, 4, 7, 1, 1, 6, 9, 3, 1, 2, 9, 14, 8, 6, 12, 17, 8, 3, 6, 14, 14, 3, 6, 4, 8, 6, 12, 4, 8, 3, 6, 7, 14, 3, 6, 16, 5, 5, 15, 16, 10, 5, 5, 3, 5, 5, 15, 3, 10, 5, 5, 18, 4, 6, 9, 18, 7, 6, 3, 1, 7, 6, 15, 1, 12, 6, 5, 11, 15, 12, 8, 17, 15, 6, 4, 11, 19, 6, 4, 0, 2, 24, 4, 0, 2, 12, 2, 12, 4, 12, 2, 15, 1, 2, 19, 15, 1, 1, 19, 7, 1, 2, 19, 8, 1, 1, 19, 22, 1, 2, 20, 22, 1, 1, 20, 0, 1, 2, 20, 1, 1, 1, 20, 18, 11, 6, 12, 20, 11, 2, 12, 0, 11, 6, 12, 2, 11, 2, 12, 3, 6, 18, 14, 3, 13, 18, 7, 6, 10, 7, 8, 6, 14, 7, 4, 7, 9, 12, 12, 7, 13, 12, 4, 2, 18, 18, 5, 11, 18, 9, 5, 4, 21, 20, 3, 4, 22, 20, 1, 9, 12, 6, 12, 9, 12, 3, 6, 12, 18, 3, 6, 4, 6, 18, 3, 4, 7, 18, 1, 3, 6, 18, 3, 3, 7, 18, 1, 18, 4, 6, 9, 18, 7, 6, 3, 2, 12, 9, 6, 2, 14, 9, 2, 4, 14, 18, 4, 13, 14, 9, 2, 4, 16, 9, 2, 7, 7, 6, 14, 7, 7, 3, 7, 10, 14, 3, 7, 7, 13, 12, 6, 13, 13, 6, 3, 7, 16, 6, 3, 6, 7, 12, 9, 10, 7, 4, 9, 12, 12, 6, 6, 12, 12, 3, 6, 0, 2, 4, 10, 0, 7, 4, 5, 8, 0, 9, 6, 11, 0, 3, 6, 2, 9, 12, 6, 2, 12, 12, 3, 13, 10, 6, 9, 13, 13, 6, 3, 5, 10, 6, 9, 5, 13, 6, 3, 9, 15, 9, 6, 9, 17, 9, 2, 5, 16, 12, 6, 5, 19, 12, 3, 3, 2, 20, 3, 3, 3, 20, 1, 2, 5, 12, 6, 6, 5, 4, 6, 11, 0, 3, 24, 12, 0, 1, 24, 3, 16, 15, 4, 8, 16, 5, 4, 9, 12, 6, 12, 9, 18, 6, 6, 1, 15, 12, 8, 1, 15, 6, 4, 7, 19, 6, 4, 15, 10, 8, 14, 19, 10, 4, 7, 15, 17, 4, 7, 1, 9, 8, 14, 1, 9, 4, 7, 5, 16, 4, 7, 9, 11, 9, 10, 9, 16, 9, 5, 6, 7, 12, 6, 6, 9, 12, 2, 10, 15, 6, 9, 12, 15, 2, 9, 7, 8, 9, 7, 10, 8, 3, 7, 10, 4, 8, 10, 14, 4, 4, 5, 10, 9, 4, 5, 4, 6, 6, 9, 4, 9, 6, 3, 0, 6, 24, 12, 8, 6, 8, 12, 3, 7, 6, 14, 6, 7, 3, 14, 19, 8, 5, 8, 19, 12, 5, 4, 0, 8, 5, 8, 0, 12, 5, 4, 17, 3, 6, 6, 17, 6, 6, 3, 1, 3, 6, 6, 1, 6, 6, 3, 18, 2, 6, 9, 18, 5, 6, 3, 0, 2, 6, 9, 0, 5, 6, 3, 3, 3, 18, 6, 3, 5, 18, 2, 2, 3, 9, 6, 2, 5, 9, 2, 9, 3, 10, 8, 14, 3, 5, 4, 9, 7, 5, 4, 5, 3, 10, 8, 5, 3, 5, 4, 10, 7, 5, 4, 10, 11, 6, 12, 10, 11, 3, 12, 8, 11, 6, 11, 11, 11, 3, 11, 7, 8, 10, 4, 7, 8, 5, 4, 9, 6, 6, 7, 12, 6, 3, 7, 5, 18, 18, 3, 5, 19, 18, 1, 8, 4, 6, 9, 10, 4, 2, 9, 8, 1, 9, 7, 11, 1, 3, 7, 6, 11, 6, 6, 9, 11, 3, 6, 14, 12, 4, 11, 14, 12, 2, 11, 6, 12, 4, 11, 8, 12, 2, 11, 8, 0, 12, 18, 12, 0, 4, 18, 2, 12, 10, 5, 7, 12, 5, 5, 2, 20, 22, 3, 2, 21, 22, 1, 0, 4, 2, 20, 1, 4, 1, 20, 0, 2, 24, 4, 8, 2, 8, 4, 7, 8, 10, 4, 7, 10, 10, 2, 6, 7, 8, 10, 6, 7, 4, 5, 10, 12, 4, 5, 14, 0, 6, 14, 17, 0, 3, 7, 14, 7, 3, 7, 4, 11, 5, 8, 4, 15, 5, 4, 2, 0, 20, 9, 2, 3, 20, 3, 6, 7, 12, 8, 6, 7, 6, 4, 12, 11, 6, 4, 9, 17, 6, 6, 9, 20, 6, 3, 7, 10, 10, 4, 7, 12, 10, 2, 6, 5, 12, 9, 10, 5, 4, 9, 5, 11, 6, 8, 8, 11, 3, 8, 18, 4, 4, 17, 18, 4, 2, 17, 0, 0, 6, 6, 3, 0, 3, 6, 18, 4, 4, 17, 18, 4, 2, 17, 2, 4, 4, 17, 4, 4, 2, 17, 5, 18, 19, 3, 5, 19, 19, 1, 11, 0, 2, 18, 11, 9, 2, 9, 15, 4, 2, 18, 15, 13, 2, 9, 7, 4, 2, 18, 7, 13, 2, 9, 7, 11, 10, 8, 12, 11, 5, 4, 7, 15, 5, 4, 10, 6, 4, 9, 12, 6, 2, 9, 10, 0, 6, 9, 12, 0, 2, 9, 2, 9, 16, 8, 2, 9, 8, 4, 10, 13, 8, 4, 14, 15, 6, 9, 14, 18, 6, 3, 8, 7, 6, 9, 10, 7, 2, 9, 14, 15, 6, 9, 14, 18, 6, 3, 3, 12, 12, 6, 3, 14, 12, 2, 14, 12, 9, 6, 14, 14, 9, 2, 1, 12, 9, 6, 1, 14, 9, 2, 3, 7, 18, 3, 3, 8, 18, 1, 1, 7, 22, 6, 1, 9, 22, 2, 18, 4, 6, 6, 18, 7, 6, 3, 0, 4, 6, 6, 0, 7, 6, 3, 5, 11, 16, 6, 5, 14, 16, 3, 6, 16, 9, 4, 6, 18, 9, 2, 14, 15, 6, 9, 14, 18, 6, 3, 4, 15, 6, 9, 4, 18, 6, 3, 15, 1, 6, 23, 17, 1, 2, 23, 0, 21, 24, 3, 8, 21, 8, 3, 0, 20, 24, 4, 8, 20, 8, 4, 3, 1, 6, 23, 5, 1, 2, 23, 3, 17, 18, 3, 3, 18, 18, 1, 0, 16, 18, 3, 0, 17, 18, 1, 1, 16, 22, 4, 12, 16, 11, 2, 1, 18, 11, 2, 0, 16, 9, 6, 0, 18, 9, 2, 2, 10, 21, 3, 9, 10, 7, 3, 2, 18, 12, 6, 2, 18, 6, 3, 8, 21, 6, 3, 0, 5, 24, 4, 0, 7, 24, 2, 10, 2, 4, 15, 10, 7, 4, 5, 10, 7, 6, 12, 10, 13, 6, 6, 6, 6, 6, 9, 8, 6, 2, 9, 11, 0, 6, 9, 13, 0, 2, 9, 9, 7, 6, 9, 11, 7, 2, 9, 2, 1, 20, 3, 2, 2, 20, 1, 1, 18, 12, 6, 1, 18, 6, 3, 7, 21, 6, 3, 13, 2, 4, 13, 13, 2, 2, 13, 6, 7, 12, 4, 12, 7, 6, 4, 10, 1, 4, 13, 10, 1, 2, 13, 6, 0, 3, 18, 7, 0, 1, 18, 14, 3, 10, 5, 14, 3, 5, 5, 6, 15, 12, 8, 10, 15, 4, 8, 9, 10, 6, 9, 11, 10, 2, 9, 8, 3, 4, 9, 10, 3, 2, 9, 17, 0, 6, 14, 20, 0, 3, 7, 17, 7, 3, 7, 1, 0, 6, 14, 1, 0, 3, 7, 4, 7, 3, 7, 14, 0, 6, 16, 17, 0, 3, 8, 14, 8, 3, 8, 7, 4, 4, 10, 9, 4, 2, 10, 3, 17, 18, 6, 12, 17, 9, 3, 3, 20, 9, 3, 1, 20, 22, 4, 12, 20, 11, 4, 14, 3, 10, 5, 14, 3, 5, 5, 0, 3, 10, 5, 5, 3, 5, 5, 12, 6, 12, 16, 16, 6, 4, 16, 0, 6, 12, 16, 4, 6, 4, 16, 10, 9, 5, 15, 10, 14, 5, 5, 1, 18, 21, 2, 1, 19, 21, 1, 15, 0, 9, 6, 15, 2, 9, 2, 6, 1, 12, 4, 12, 1, 6, 4, 6, 0, 12, 12, 12, 0, 6, 6, 6, 6, 6, 6, 8, 10, 8, 12, 8, 10, 4, 6, 12, 16, 4, 6, 14, 16, 10, 8, 19, 16, 5, 4, 14, 20, 5, 4, 0, 16, 10, 8, 0, 16, 5, 4, 5, 20, 5, 4, 10, 12, 12, 5, 14, 12, 4, 5, 6, 16, 10, 8, 6, 16, 5, 4, 11, 20, 5, 4, 7, 6, 12, 6, 13, 6, 6, 3, 7, 9, 6, 3, 9, 6, 4, 18, 9, 6, 2, 9, 11, 15, 2, 9, 10, 9, 6, 14, 13, 9, 3, 7, 10, 16, 3, 7, 8, 9, 6, 14, 8, 9, 3, 7, 11, 16, 3, 7, 7, 4, 11, 12, 7, 10, 11, 6, 4, 8, 6, 16, 4, 8, 3, 8, 7, 16, 3, 8, 17, 3, 4, 21, 17, 10, 4, 7, 3, 3, 4, 21, 3, 10, 4, 7, 10, 1, 8, 18, 14, 1, 4, 9, 10, 10, 4, 9, 2, 5, 16, 8, 2, 5, 8, 4, 10, 9, 8, 4, 3, 6, 18, 12, 3, 10, 18, 4, 4, 10, 16, 12, 4, 14, 16, 4, 15, 4, 8, 20, 19, 4, 4, 10, 15, 14, 4, 10, 7, 2, 9, 6, 10, 2, 3, 6, 15, 4, 8, 20, 19, 4, 4, 10, 15, 14, 4, 10, 1, 4, 8, 20, 1, 4, 4, 10, 5, 14, 4, 10, 11, 8, 8, 14, 15, 8, 4, 7, 11, 15, 4, 7, 5, 8, 8, 14, 5, 8, 4, 7, 9, 15, 4, 7, 10, 13, 5, 8, 10, 17, 5, 4, 4, 13, 7, 9, 4, 16, 7, 3, 0, 13, 24, 10, 0, 18, 24, 5, 4, 2, 8, 11, 8, 2, 4, 11, 10, 2, 8, 16, 14, 2, 4, 8, 10, 10, 4, 8, 0, 2, 24, 6, 0, 2, 12, 3, 12, 5, 12, 3, 6, 0, 12, 9, 6, 3, 12, 3, 1, 2, 12, 12, 1, 2, 6, 6, 7, 8, 6, 6, 18, 5, 6, 9, 18, 8, 6, 3, 4, 3, 8, 10, 4, 3, 4, 5, 8, 8, 4, 5, 6, 21, 18, 3, 6, 22, 18, 1, 1, 10, 18, 2, 1, 11, 18, 1, 1, 10, 22, 3, 1, 11, 22, 1, 2, 8, 12, 9, 2, 11, 12, 3, 12, 8, 12, 6, 18, 8, 6, 3, 12, 11, 6, 3, 0, 8, 12, 6, 0, 8, 6, 3, 6, 11, 6, 3, 10, 15, 6, 9, 12, 15, 2, 9, 7, 13, 9, 6, 7, 15, 9, 2, 9, 8, 7, 12, 9, 14, 7, 6, 4, 13, 9, 6, 7, 13, 3, 6, 6, 15, 18, 4, 12, 15, 6, 4, 5, 4, 4, 16, 7, 4, 2, 16, 10, 15, 6, 9, 12, 15, 2, 9, 8, 15, 6, 9, 10, 15, 2, 9, 9, 11, 12, 10, 15, 11, 6, 5, 9, 16, 6, 5, 3, 6, 14, 6, 3, 8, 14, 2, 4, 2, 17, 8, 4, 6, 17, 4, 6, 2, 12, 21, 6, 9, 12, 7, 8, 1, 9, 9, 8, 4, 9, 3, 0, 7, 24, 3, 12, 7, 12, 3, 11, 6, 9, 10, 11, 11, 9, 5, 2, 11, 18, 3, 2, 12, 18, 1, 8, 16, 9, 4, 8, 18, 9, 2, 0, 0, 9, 6, 0, 2, 9, 2, 0, 11, 24, 6, 0, 13, 24, 2, 2, 9, 20, 6, 2, 12, 20, 3, 4, 5, 16, 12, 12, 5, 8, 6, 4, 11, 8, 6, 10, 2, 4, 15, 10, 7, 4, 5, 7, 3, 10, 4, 7, 5, 10, 2, 9, 15, 6, 8, 9, 19, 6, 4, 17, 0, 7, 10, 17, 5, 7, 5, 0, 0, 7, 10, 0, 5, 7, 5, 16, 1, 6, 12, 19, 1, 3, 6, 16, 7, 3, 6, 1, 0, 19, 8, 1, 4, 19, 4, 12, 2, 9, 4, 12, 4, 9, 2, 3, 2, 9, 4, 3, 4, 9, 2, 12, 2, 10, 6, 12, 4, 10, 2, 3, 4, 18, 2, 12, 4, 9, 2, 12, 1, 4, 9, 12, 1, 2, 9, 8, 1, 4, 9, 10, 1, 2, 9, 10, 5, 8, 10, 14, 5, 4, 5, 10, 10, 4, 5, 6, 4, 12, 13, 10, 4, 4, 13, 13, 5, 6, 6, 13, 5, 3, 6, 1, 5, 12, 3, 7, 5, 6, 3, 7, 5, 10, 6, 7, 7, 10, 2, 2, 0, 21, 5, 9, 0, 7, 5, 0, 8, 9, 9, 0, 11, 9, 3, 9, 6, 6, 9, 11, 6, 2, 9, 0, 3, 6, 7, 3, 3, 3, 7, 9, 18, 12, 6, 15, 18, 6, 3, 9, 21, 6, 3, 2, 8, 20, 6, 2, 8, 10, 3, 12, 11, 10, 3, 13, 2, 10, 4, 13, 4, 10, 2, 4, 5, 5, 18, 4, 11, 5, 6, 20, 4, 4, 9, 20, 4, 2, 9, 8, 6, 8, 14, 8, 13, 8, 7, 0, 1, 24, 6, 12, 1, 12, 3, 0, 4, 12, 3, 0, 4, 4, 9, 2, 4, 2, 9, 3, 6, 18, 3, 3, 7, 18, 1, 3, 17, 16, 6, 3, 19, 16, 2, 13, 6, 6, 9, 13, 9, 6, 3, 5, 6, 14, 6, 5, 6, 7, 3, 12, 9, 7, 3, 13, 5, 8, 10, 17, 5, 4, 5, 13, 10, 4, 5, 2, 2, 20, 3, 2, 3, 20, 1, 9, 2, 9, 6, 12, 2, 3, 6, 8, 6, 6, 9, 10, 6, 2, 9, 12, 3, 4, 11, 12, 3, 2, 11, 8, 3, 4, 11, 10, 3, 2, 11, 8, 3, 8, 10, 12, 3, 4, 5, 8, 8, 4, 5, 11, 1, 2, 18, 12, 1, 1, 18, 9, 2, 9, 6, 12, 2, 3, 6, 0, 2, 19, 3, 0, 3, 19, 1, 9, 14, 9, 6, 9, 16, 9, 2, 1, 8, 18, 5, 7, 8, 6, 5, 12, 0, 6, 9, 14, 0, 2, 9, 6, 0, 6, 9, 8, 0, 2, 9, 13, 6, 4, 15, 13, 11, 4, 5, 1, 5, 18, 3, 1, 6, 18, 1, 9, 7, 14, 6, 9, 9, 14, 2, 2, 16, 18, 3, 2, 17, 18, 1, 15, 17, 9, 6, 15, 19, 9, 2, 0, 8, 12, 6, 0, 8, 6, 3, 6, 11, 6, 3, 9, 13, 7, 8, 9, 17, 7, 4, 2, 17, 20, 3, 2, 18, 20, 1, 15, 17, 9, 6, 15, 19, 9, 2, 4, 0, 15, 4, 4, 2, 15, 2, 17, 2, 6, 6, 17, 5, 6, 3, 0, 3, 6, 9, 0, 6, 6, 3, 15, 17, 9, 6, 15, 19, 9, 2, 0, 17, 9, 6, 0, 19, 9, 2, 9, 18, 12, 6, 15, 18, 6, 3, 9, 21, 6, 3, 3, 15, 6, 9, 3, 18, 6, 3, 16, 13, 8, 10, 20, 13, 4, 5, 16, 18, 4, 5, 0, 14, 24, 4, 8, 14, 8, 4, 13, 18, 6, 6, 13, 18, 3, 6, 0, 13, 8, 10, 0, 13, 4, 5, 4, 18, 4, 5, 0, 14, 24, 6, 0, 17, 24, 3, 5, 2, 12, 8, 5, 2, 6, 4, 11, 6, 6, 4, 8, 9, 9, 6, 11, 9, 3, 6, 4, 3, 16, 4, 4, 5, 16, 2, 10, 2, 4, 10, 10, 7, 4, 5, 8, 4, 5, 8, 8, 8, 5, 4, 11, 5, 9, 12, 11, 9, 9, 4, 4, 5, 9, 12, 4, 9, 9, 4, 14, 6, 6, 9, 14, 9, 6, 3, 2, 4, 20, 12, 2, 8, 20, 4, 4, 4, 17, 16, 4, 12, 17, 8, 8, 7, 7, 6, 8, 10, 7, 3, 1, 9, 23, 2, 1, 10, 23, 1, 7, 0, 6, 9, 9, 0, 2, 9, 13, 3, 4, 9, 13, 3, 2, 9, 8, 1, 6, 13, 10, 1, 2, 13, 4, 22, 18, 2, 4, 23, 18, 1, 3, 10, 9, 6, 6, 10, 3, 6, 14, 0, 2, 24, 14, 0, 1, 24, 8, 0, 2, 24, 9, 0, 1, 24, 3, 2, 18, 10, 9, 2, 6, 10, 4, 13, 15, 6, 9, 13, 5, 6, 3, 21, 18, 3, 9, 21, 6, 3, 9, 1, 4, 11, 11, 1, 2, 11, 9, 7, 10, 4, 9, 7, 5, 4, 7, 0, 10, 18, 12, 0, 5, 18, 12, 1, 6, 16, 14, 1, 2, 16, 6, 1, 6, 16, 8, 1, 2, 16, 18, 2, 6, 6, 18, 5, 6, 3, 3, 5, 18, 2, 3, 6, 18, 1, 18, 2, 6, 6, 18, 5, 6, 3, 0, 2, 6, 6, 0, 5, 6, 3, 13, 11, 11, 6, 13, 13, 11, 2, 5, 7, 10, 4, 10, 7, 5, 4, 11, 9, 10, 7, 11, 9, 5, 7, 3, 9, 10, 7, 8, 9, 5, 7, 16, 4, 6, 6, 16, 4, 3, 6, 5, 6, 10, 8, 5, 6, 5, 4, 10, 10, 5, 4, 7, 21, 16, 3, 7, 21, 8, 3, 1, 21, 16, 3, 9, 21, 8, 3, 2, 5, 22, 14, 13, 5, 11, 7, 2, 12, 11, 7, 3, 10, 8, 10, 3, 10, 4, 5, 7, 15, 4, 5, 17, 0, 6, 12, 20, 0, 3, 6, 17, 6, 3, 6, 5, 2, 6, 18, 7, 2, 2, 18, 13, 0, 6, 9, 15, 0, 2, 9, 0, 12, 7, 9, 0, 15, 7, 3, 15, 13, 8, 10, 19, 13, 4, 5, 15, 18, 4, 5, 1, 0, 6, 12, 1, 0, 3, 6, 4, 6, 3, 6, 12, 1, 3, 12, 12, 7, 3, 6, 1, 13, 8, 10, 1, 13, 4, 5, 5, 18, 4, 5, 3, 21, 19, 2, 3, 22, 19, 1, 6, 3, 4, 13, 8, 3, 2, 13, 5, 10, 18, 3, 5, 11, 18, 1, 9, 3, 5, 12, 9, 7, 5, 4, 11, 2, 4, 15, 11, 7, 4, 5, 4, 1, 16, 4, 4, 3, 16, 2, 6, 0, 18, 3, 6, 1, 18, 1, 5, 1, 10, 8, 5, 1, 5, 4, 10, 5, 5, 4, 11, 18, 12, 6, 17, 18, 6, 3, 11, 21, 6, 3, 5, 15, 12, 3, 11, 15, 6, 3, 1, 10, 22, 4, 1, 10, 11, 4, 7, 9, 9, 6, 10, 9, 3, 6, 6, 11, 12, 5, 10, 11, 4, 5, 6, 7, 10, 7, 11, 7, 5, 7, 11, 2, 8, 10, 11, 2, 4, 10, 5, 2, 8, 10, 9, 2, 4, 10, 6, 4, 18, 6, 15, 4, 9, 3, 6, 7, 9, 3, 0, 5, 10, 9, 0, 8, 10, 3, 2, 7, 21, 6, 2, 9, 21, 2, 0, 4, 22, 16, 0, 4, 11, 8, 11, 12, 11, 8, 9, 0, 6, 22, 9, 11, 6, 11, 9, 1, 3, 12, 9, 7, 3, 6, 12, 0, 12, 18, 18, 0, 6, 9, 12, 9, 6, 9, 0, 0, 12, 18, 0, 0, 6, 9, 6, 9, 6, 9, 1, 1, 22, 4, 12, 1, 11, 2, 1, 3, 11, 2, 3, 0, 18, 4, 3, 2, 18, 2, 2, 5, 22, 6, 2, 7, 22, 2, 5, 0, 6, 9, 5, 3, 6, 3, 10, 14, 6, 9, 12, 14, 2, 9, 8, 14, 6, 9, 10, 14, 2, 9, 5, 18, 18, 3, 5, 19, 18, 1, 6, 0, 6, 13, 9, 0, 3, 13, 7, 4, 12, 4, 7, 4, 6, 4, 5, 2, 12, 6, 9, 2, 4, 6, 4, 1, 18, 3, 4, 2, 18, 1, 0, 8, 6, 12, 0, 12, 6, 4, 9, 15, 6, 9, 11, 15, 2, 9, 9, 10, 6, 13, 11, 10, 2, 13, 6, 17, 18, 2, 6, 18, 18, 1, 9, 4, 6, 9, 11, 4, 2, 9, 10, 0, 6, 9, 12, 0, 2, 9, 5, 6, 10, 8, 5, 6, 5, 4, 10, 10, 5, 4, 14, 9, 5, 8, 14, 13, 5, 4, 5, 9, 5, 8, 5, 13, 5, 4, 14, 11, 9, 6, 14, 13, 9, 2, 0, 2, 23, 15, 0, 7, 23, 5, 16, 0, 8, 12, 16, 6, 8, 6, 4, 15, 6, 9, 4, 18, 6, 3, 8, 18, 9, 4, 8, 20, 9, 2, 0, 17, 18, 3, 0, 18, 18, 1, 13, 11, 11, 6, 13, 13, 11, 2, 0, 11, 11, 6, 0, 13, 11, 2, 0, 9, 24, 6, 12, 9, 12, 3, 0, 12, 12, 3, 6, 16, 8, 8, 6, 20, 8, 4, 10, 16, 14, 6, 10, 18, 14, 2, 1, 1, 21, 3, 1, 2, 21, 1, 0, 2, 24, 3, 0, 2, 12, 3, 2, 15, 8, 5, 6, 15, 4, 5, 2, 11, 21, 3, 9, 11, 7, 3, 1, 18, 12, 6, 1, 18, 6, 3, 7, 21, 6, 3, 10, 14, 4, 10, 10, 19, 4, 5, 7, 7, 4, 10, 7, 12, 4, 5, 9, 8, 6, 12, 9, 12, 6, 4, 7, 1, 9, 6, 10, 1, 3, 6, 3, 14, 19, 2, 3, 15, 19, 1, 7, 7, 10, 10, 7, 7, 5, 5, 12, 12, 5, 5, 3, 12, 18, 12, 3, 12, 9, 12, 8, 0, 6, 12, 10, 0, 2, 12, 3, 0, 17, 9, 3, 3, 17, 3, 6, 0, 12, 11, 10, 0, 4, 11, 1, 0, 6, 13, 4, 0, 3, 13, 5, 8, 16, 6, 5, 11, 16, 3, 8, 8, 5, 12, 8, 14, 5, 6, 3, 21, 18, 3, 9, 21, 6, 3, 0, 0, 6, 6, 3, 0, 3, 6, 2, 0, 20, 3, 2, 1, 20, 1, 4, 6, 15, 10, 9, 6, 5, 10, 9, 6, 6, 9, 11, 6, 2, 9, 9, 0, 6, 9, 11, 0, 2, 9, 14, 0, 6, 9, 16, 0, 2, 9, 7, 16, 9, 6, 7, 18, 9, 2, 14, 0, 6, 9, 16, 0, 2, 9, 4, 0, 6, 9, 6, 0, 2, 9, 17, 1, 6, 16, 19, 1, 2, 16, 1, 1, 6, 16, 3, 1, 2, 16, 14, 13, 6, 9, 14, 16, 6, 3, 0, 0, 6, 9, 0, 3, 6, 3, 9, 5, 6, 6, 9, 5, 3, 6, 3, 10, 9, 6, 6, 10, 3, 6, 14, 7, 3, 16, 14, 15, 3, 8, 4, 10, 14, 12, 4, 10, 7, 6, 11, 16, 7, 6, 7, 6, 12, 6, 7, 8, 12, 2, 7, 2, 4, 20, 9, 2, 2, 20, 14, 13, 6, 9, 14, 16, 6, 3, 10, 6, 4, 9, 12, 6, 2, 9, 14, 13, 6, 9, 14, 16, 6, 3, 5, 20, 14, 4, 5, 22, 14, 2, 4, 4, 16, 12, 4, 10, 16, 6, 9, 6, 6, 9, 11, 6, 2, 9, 3, 0, 21, 4, 3, 2, 21, 2, 4, 13, 6, 9, 4, 16, 6, 3, 16, 16, 5, 8, 16, 20, 5, 4, 4, 0, 16, 16, 4, 0, 8, 8, 12, 8, 8, 8, 6, 6, 14, 6, 13, 6, 7, 3, 6, 9, 7, 3, 10, 5, 4, 15, 10, 10, 4, 5, 9, 15, 12, 8, 15, 15, 6, 4, 9, 19, 6, 4, 6, 7, 12, 4, 12, 7, 6, 4, 5, 6, 14, 6, 12, 6, 7, 3, 5, 9, 7, 3, 3, 6, 18, 10, 3, 6, 9, 5, 12, 11, 9, 5, 6, 0, 18, 21, 12, 0, 6, 21, 0, 0, 24, 21, 8, 0, 8, 21, 6, 18, 18, 3, 6, 19, 18, 1, 0, 15, 9, 6, 0, 17, 9, 2, 4, 3, 19, 2, 4, 4, 19, 1, 0, 3, 24, 2, 0, 4, 24, 1, 15, 14, 9, 4, 15, 16, 9, 2, 0, 14, 9, 4, 0, 16, 9, 2, 6, 15, 18, 2, 6, 16, 18, 1, 3, 17, 18, 3, 3, 18, 18, 1, 12, 0, 3, 23, 13, 0, 1, 23, 6, 0, 8, 6, 6, 3, 8, 3, 6, 16, 18, 3, 6, 17, 18, 1, 9, 0, 3, 23, 10, 0, 1, 23, 10, 7, 4, 10, 10, 12, 4, 5, 7, 8, 10, 12, 7, 12, 10, 4, 14, 9, 6, 14, 17, 9, 3, 7, 14, 16, 3, 7, 2, 0, 10, 9, 2, 3, 10, 3, 11, 1, 5, 12, 11, 7, 5, 6, 1, 4, 12, 10, 1, 4, 6, 5, 7, 9, 6, 5, 15, 1, 9, 4, 15, 3, 9, 2, 1, 2, 8, 10, 1, 2, 4, 5, 5, 7, 4, 5, 10, 1, 5, 12, 10, 5, 5, 4, 4, 0, 14, 24, 11, 0, 7, 24, 7, 17, 10, 4, 7, 19, 10, 2, 10, 14, 4, 10, 10, 19, 4, 5, 13, 15, 6, 9, 15, 15, 2, 9, 3, 21, 18, 3, 3, 22, 18, 1, 13, 15, 6, 9, 15, 15, 2, 9, 5, 15, 6, 9, 7, 15, 2, 9, 10, 6, 4, 18, 12, 6, 2, 9, 10, 15, 2, 9, 7, 3, 6, 11, 9, 3, 2, 11, 15, 1, 9, 4, 15, 3, 9, 2, 5, 4, 14, 8, 5, 8, 14, 4, 8, 1, 15, 9, 8, 4, 15, 3, 7, 2, 8, 10, 7, 2, 4, 5, 11, 7, 4, 5, 12, 2, 6, 12, 12, 2, 3, 12, 6, 2, 6, 12, 9, 2, 3, 12, 7, 7, 12, 4, 7, 7, 6, 4, 6, 3, 12, 10, 10, 3, 4, 10, 5, 6, 16, 6, 13, 6, 8, 3, 5, 9, 8, 3, 3, 1, 18, 9, 9, 1, 6, 9, 3, 8, 18, 5, 9, 8, 6, 5, 0, 0, 24, 22, 0, 0, 12, 11, 12, 11, 12, 11, 14, 16, 9, 6, 14, 18, 9, 2, 0, 16, 24, 8, 0, 20, 24, 4, 1, 19, 22, 4, 12, 19, 11, 2, 1, 21, 11, 2, 1, 16, 9, 6, 1, 18, 9, 2, 7, 8, 10, 4, 7, 8, 5, 4, 9, 15, 6, 9, 11, 15, 2, 9, 10, 18, 12, 6, 16, 18, 6, 3, 10, 21, 6, 3, 2, 18, 12, 6, 2, 18, 6, 3, 8, 21, 6, 3, 8, 3, 16, 9, 8, 6, 16, 3, 0, 5, 10, 6, 0, 7, 10, 2, 5, 5, 18, 3, 5, 6, 18, 1, 2, 6, 9, 6, 2, 9, 9, 3, 14, 2, 10, 9, 14, 5, 10, 3, 3, 6, 18, 3, 3, 7, 18, 1, 9, 2, 15, 6, 9, 4, 15, 2, 4, 8, 15, 6, 4, 10, 15, 2, 0, 5, 24, 4, 12, 5, 12, 2, 0, 7, 12, 2, 7, 8, 6, 12, 9, 8, 2, 12, 11, 0, 6, 9, 13, 0, 2, 9, 0, 12, 6, 12, 0, 12, 3, 6, 3, 18, 3, 6, 14, 12, 10, 6, 14, 14, 10, 2, 2, 7, 18, 9, 2, 10, 18, 3, 11, 14, 10, 9, 11, 17, 10, 3, 7, 6, 10, 8, 7, 6, 5, 4, 12, 10, 5, 4, 6, 6, 14, 6, 13, 6, 7, 3, 6, 9, 7, 3, 4, 13, 9, 7, 7, 13, 3, 7, 14, 10, 6, 12, 17, 10, 3, 6, 14, 16, 3, 6, 4, 10, 6, 12, 4, 10, 3, 6, 7, 16, 3, 6, 13, 9, 8, 6, 13, 9, 4, 6, 8, 3, 4, 14, 10, 3, 2, 14, 17, 0, 3, 18, 18, 0, 1, 18, 4, 12, 16, 12, 12, 12, 8, 12, 15, 0, 6, 14, 17, 0, 2, 14, 3, 0, 6, 14, 5, 0, 2, 14, 12, 2, 12, 20, 16, 2, 4, 20, 0, 2, 12, 20, 4, 2, 4, 20, 16, 0, 6, 17, 18, 0, 2, 17, 2, 0, 6, 17, 4, 0, 2, 17, 15, 6, 9, 6, 15, 8, 9, 2, 0, 6, 9, 6, 0, 8, 9, 2, 18, 1, 6, 13, 20, 1, 2, 13, 0, 1, 6, 13, 2, 1, 2, 13, 16, 0, 4, 9, 16, 0, 2, 9, 5, 10, 12, 7, 9, 10, 4, 7, 12, 9, 12, 6, 12, 11, 12, 2, 0, 9, 12, 6, 0, 11, 12, 2, 5, 7, 14, 9, 5, 10, 14, 3, 0, 15, 20, 3, 0, 16, 20, 1, 8, 10, 8, 10, 12, 10, 4, 5, 8, 15, 4, 5, 5, 4, 13, 9, 5, 7, 13, 3, 10, 2, 6, 18, 10, 8, 6, 6, 6, 0, 6, 9, 8, 0, 2, 9, 6, 9, 12, 4, 6, 11, 12, 2, 3, 2, 15, 12, 3, 6, 15, 4, 12, 0, 12, 5, 16, 0, 4, 5, 0, 15, 18, 3, 6, 15, 6, 3, 0, 14, 24, 5, 8, 14, 8, 5, 5, 1, 3, 18, 6, 1, 1, 18, 10, 0, 4, 14, 10, 0, 2, 14, 9, 3, 4, 9, 11, 3, 2, 9, 8, 2, 12, 6, 14, 2, 6, 3, 8, 5, 6, 3, 0, 4, 17, 4, 0, 6, 17, 2, 16, 16, 5, 8, 16, 20, 5, 4, 3, 16, 5, 8, 3, 20, 5, 4, 6, 18, 18, 2, 6, 19, 18, 1, 0, 0, 12, 5, 4, 0, 4, 5, 14, 3, 6, 12, 17, 3, 3, 6, 14, 9, 3, 6, 0, 12, 6, 12, 2, 12, 2, 12, 2, 3, 21, 3, 2, 4, 21, 1, 4, 3, 6, 12, 4, 3, 3, 6, 7, 9, 3, 6, 12, 8, 12, 6, 18, 8, 6, 3, 12, 11, 6, 3, 0, 15, 16, 9, 8, 15, 8, 9, 6, 13, 18, 5, 6, 13, 9, 5, 1, 6, 15, 6, 6, 6, 5, 6, 11, 9, 9, 6, 14, 9, 3, 6, 3, 0, 15, 11, 8, 0, 5, 11, 15, 3, 3, 18, 15, 9, 3, 6, 6, 3, 3, 18, 6, 9, 3, 6, 9, 5, 10, 8, 14, 5, 5, 4, 9, 9, 5, 4, 4, 4, 16, 8, 4, 4, 8, 4, 12, 8, 8, 4, 7, 7, 12, 3, 7, 7, 6, 3, 5, 0, 9, 13, 8, 0, 3, 13, 11, 0, 6, 9, 13, 0, 2, 9, 7, 0, 6, 9, 9, 0, 2, 9, 8, 1, 10, 9, 8, 4, 10, 3, 0, 2, 18, 2, 0, 3, 18, 1, 10, 13, 14, 6, 17, 13, 7, 3, 10, 16, 7, 3, 0, 13, 14, 6, 0, 13, 7, 3, 7, 16, 7, 3, 20, 2, 3, 21, 21, 2, 1, 21, 0, 9, 5, 12, 0, 13, 5, 4, 12, 6, 12, 6, 12, 8, 12, 2, 1, 8, 20, 3, 1, 9, 20, 1, 5, 7, 19, 3, 5, 8, 19, 1, 1, 12, 9, 6, 1, 14, 9, 2, 6, 10, 14, 12, 6, 14, 14, 4, 5, 6, 14, 18, 5, 12, 14, 6, 11, 12, 9, 7, 14, 12, 3, 7, 1, 15, 18, 4, 1, 17, 18, 2, 11, 14, 6, 9, 11, 17, 6, 3, 0, 8, 18, 4, 0, 8, 9, 2, 9, 10, 9, 2, 3, 10, 20, 6, 13, 10, 10, 3, 3, 13, 10, 3, 1, 10, 20, 6, 1, 10, 10, 3, 11, 13, 10, 3, 0, 9, 24, 2, 0, 9, 12, 2, 1, 12, 20, 8, 1, 12, 10, 4, 11, 16, 10, 4, 11, 12, 9, 7, 14, 12, 3, 7, 4, 12, 9, 7, 7, 12, 3, 7, 12, 12, 8, 5, 12, 12, 4, 5, 4, 12, 8, 5, 8, 12, 4, 5, 13, 10, 4, 10, 13, 10, 2, 10, 1, 15, 20, 2, 11, 15, 10, 2, 9, 10, 6, 6, 9, 10, 3, 6, 0, 1, 21, 3, 7, 1, 7, 3, 6, 4, 13, 9, 6, 7, 13, 3, 6, 5, 12, 5, 10, 5, 4, 5, 10, 10, 10, 6, 10, 12, 10, 2, 6, 12, 5, 8, 6, 16, 5, 4, 13, 0, 6, 9, 15, 0, 2, 9, 2, 10, 18, 6, 8, 10, 6, 6, 11, 2, 9, 4, 11, 4, 9, 2, 1, 20, 21, 3, 8, 20, 7, 3, 1, 10, 22, 2, 1, 11, 22, 1, 0, 17, 18, 3, 0, 18, 18, 1, 13, 0, 6, 9, 15, 0, 2, 9, 5, 0, 6, 9, 7, 0, 2, 9, 18, 2, 6, 20, 20, 2, 2, 20, 0, 2, 6, 20, 2, 2, 2, 20, 11, 7, 6, 14, 14, 7, 3, 7, 11, 14, 3, 7, 0, 1, 4, 9, 2, 1, 2, 9, 12, 14, 9, 4, 12, 16, 9, 2, 1, 13, 9, 4, 1, 15, 9, 2, 7, 6, 15, 6, 7, 8, 15, 2, 8, 2, 3, 18, 8, 8, 3, 6, 6, 6, 12, 6, 12, 6, 6, 3, 6, 9, 6, 3, 2, 19, 20, 4, 2, 19, 10, 2, 12, 21, 10, 2, 14, 15, 6, 9, 14, 18, 6, 3, 3, 5, 18, 14, 3, 5, 9, 7, 12, 12, 9, 7, 15, 6, 4, 18, 17, 6, 2, 9, 15, 15, 2, 9, 5, 6, 4, 18, 5, 6, 2, 9, 7, 15, 2, 9, 11, 0, 6, 9, 13, 0, 2, 9, 7, 0, 6, 9, 9, 0, 2, 9, 11, 5, 6, 9, 13, 5, 2, 9, 9, 5, 6, 6, 12, 5, 3, 6, 4, 1, 16, 6, 12, 1, 8, 3, 4, 4, 8, 3, 9, 13, 6, 11, 11, 13, 2, 11, 17, 1, 6, 12, 20, 1, 3, 6, 17, 7, 3, 6, 1, 17, 18, 3, 1, 18, 18, 1, 7, 13, 10, 8, 7, 17, 10, 4, 6, 18, 10, 6, 6, 20, 10, 2, 9, 14, 9, 4, 9, 16, 9, 2, 1, 1, 6, 12, 1, 1, 3, 6, 4, 7, 3, 6, 19, 4, 5, 12, 19, 8, 5, 4, 0, 0, 8, 8, 4, 0, 4, 8, 3, 5, 19, 3, 3, 6, 19, 1, 1, 5, 12, 6, 1, 5, 6, 3, 7, 8, 6, 3, 2, 1, 21, 8, 9, 1, 7, 8, 4, 1, 16, 8, 4, 5, 16, 4, 6, 0, 18, 3, 6, 1, 18, 1, 4, 4, 10, 14, 4, 11, 10, 7, 15, 6, 4, 10, 15, 11, 4, 5, 3, 18, 18, 3, 9, 18, 6, 3, 8, 18, 12, 6, 12, 18, 4, 6, 3, 15, 6, 9, 6, 15, 3, 9, 15, 7, 6, 8, 15, 11, 6, 4, 3, 7, 6, 8, 3, 11, 6, 4, 5, 9, 18, 6, 14, 9, 9, 3, 5, 12, 9, 3, 1, 13, 12, 6, 1, 15, 12, 2, 14, 15, 10, 6, 14, 17, 10, 2, 0, 15, 10, 6, 0, 17, 10, 2, 15, 13, 6, 9, 15, 16, 6, 3, 3, 13, 6, 9, 3, 16, 6, 3, 9, 5, 8, 8, 9, 5, 4, 8, 1, 18, 12, 6, 1, 18, 6, 3, 7, 21, 6, 3, 13, 19, 10, 4, 13, 21, 10, 2, 1, 19, 10, 4, 1, 21, 10, 2, 6, 19, 18, 3, 6, 20, 18, 1, 8, 14, 4, 10, 8, 19, 4, 5, 0, 0, 24, 6, 0, 2, 24, 2, 0, 1, 6, 9, 0, 4, 6, 3, 4, 9, 20, 6, 14, 9, 10, 3, 4, 12, 10, 3, 1, 15, 19, 8, 1, 19, 19, 4, 14, 0, 10, 6, 14, 2, 10, 2, 1, 10, 21, 14, 8, 10, 7, 14, 10, 10, 8, 8, 10, 10, 4, 8, 6, 8, 10, 4, 11, 8, 5, 4, 10, 5, 4, 9, 10, 5, 2, 9, 7, 5, 6, 10, 9, 5, 2, 10, 14, 4, 4, 13, 14, 4, 2, 13, 6, 4, 4, 13, 8, 4, 2, 13, 8, 7, 9, 6, 11, 7, 3, 6, 3, 6, 16, 6, 3, 6, 8, 3, 11, 9, 8, 3, 5, 4, 16, 14, 13, 4, 8, 7, 5, 11, 8, 7, 0, 0, 24, 4, 0, 0, 12, 2, 12, 2, 12, 2, 9, 1, 9, 6, 12, 1, 3, 6, 4, 1, 14, 4, 11, 1, 7, 4, 10, 14, 7, 9, 10, 17, 7, 3, 8, 3, 8, 10, 8, 3, 4, 5, 12, 8, 4, 5, 7, 3, 12, 5, 11, 3, 4, 5, 8, 2, 4, 13, 10, 2, 2, 13, 11, 2, 3, 19, 12, 2, 1, 19, 7, 7, 9, 6, 10, 7, 3, 6, 4, 22, 20, 2, 4, 22, 10, 2, 0, 16, 24, 4, 0, 16, 12, 2, 12, 18, 12, 2, 7, 3, 12, 5, 11, 3, 4, 5, 1, 10, 8, 14, 1, 10, 4, 7, 5, 17, 4, 7, 11, 16, 6, 6, 11, 19, 6, 3, 6, 0, 10, 24, 6, 0, 5, 12, 11, 12, 5, 12, 7, 5, 14, 14, 14, 5, 7, 7, 7, 12, 7, 7, 7, 8, 10, 8, 7, 8, 5, 4, 12, 12, 5, 4, 9, 1, 9, 6, 12, 1, 3, 6, 0, 6, 24, 3, 12, 6, 12, 3, 7, 3, 12, 5, 11, 3, 4, 5, 1, 13, 22, 4, 1, 13, 11, 2, 12, 15, 11, 2, 9, 12, 12, 6, 9, 14, 12, 2, 0, 5, 9, 6, 0, 7, 9, 2, 1, 5, 23, 6, 1, 7, 23, 2, 1, 6, 19, 12, 1, 10, 19, 4, 9, 1, 6, 21, 9, 8, 6, 7, 3, 19, 18, 3, 9, 19, 6, 3, 9, 14, 6, 9, 11, 14, 2, 9, 9, 6, 4, 12, 11, 6, 2, 12, 16, 0, 6, 9, 18, 0, 2, 9, 2, 0, 6, 9, 4, 0, 2, 9, 13, 1, 4, 22, 15, 1, 2, 11, 13, 12, 2, 11, 1, 8, 8, 12, 1, 14, 8, 6, 14, 7, 7, 9, 14, 10, 7, 3, 3, 12, 18, 4, 3, 12, 9, 2, 12, 14, 9, 2, 13, 1, 4, 22, 15, 1, 2, 11, 13, 12, 2, 11, 7, 1, 4, 22, 7, 1, 2, 11, 9, 12, 2, 11, 4, 7, 20, 4, 14, 7, 10, 2, 4, 9, 10, 2, 9, 10, 6, 7, 12, 10, 3, 7, 7, 7, 10, 4, 7, 7, 5, 4, 0, 3, 4, 15, 0, 8, 4, 5, 15, 0, 8, 12, 19, 0, 4, 6, 15, 6, 4, 6, 1, 0, 8, 12, 1, 0, 4, 6, 5, 6, 4, 6, 14, 5, 6, 16, 16, 5, 2, 16, 4, 5, 6, 16, 6, 5, 2, 16, 15, 0, 6, 16, 17, 0, 2, 16, 3, 0, 6, 16, 5, 0, 2, 16, 0, 2, 24, 3, 0, 3, 24, 1, 7, 1, 10, 4, 7, 3, 10, 2, 1, 0, 23, 8, 1, 4, 23, 4, 1, 17, 19, 3, 1, 18, 19, 1, 6, 18, 18, 2, 6, 19, 18, 1, 1, 17, 9, 6, 1, 19, 9, 2, 15, 15, 6, 9, 15, 18, 6, 3, 3, 15, 6, 9, 3, 18, 6, 3, 4, 14, 20, 6, 4, 17, 20, 3, 0, 10, 6, 14, 0, 10, 3, 7, 3, 17, 3, 7, 6, 18, 18, 3, 6, 19, 18, 1, 4, 12, 9, 7, 7, 12, 3, 7, 6, 10, 18, 5, 12, 10, 6, 5, 0, 10, 18, 5, 6, 10, 6, 5, 3, 2, 18, 9, 9, 2, 6, 9, 4, 6, 10, 10, 4, 6, 5, 5, 9, 11, 5, 5, 20, 14, 4, 9, 20, 14, 2, 9, 0, 14, 4, 9, 2, 14, 2, 9, 11, 1, 4, 20, 13, 1, 2, 10, 11, 11, 2, 10, 6, 21, 12, 3, 12, 21, 6, 3, 11, 1, 4, 20, 13, 1, 2, 10, 11, 11, 2, 10, 1, 16, 10, 8, 1, 16, 5, 4, 6, 20, 5, 4, 11, 1, 4, 20, 13, 1, 2, 10, 11, 11, 2, 10, 1, 0, 3, 19, 2, 0, 1, 19, 11, 1, 4, 20, 13, 1, 2, 10, 11, 11, 2, 10, 0, 1, 6, 9, 2, 1, 2, 9, 3, 7, 19, 4, 3, 9, 19, 2, 7, 14, 9, 6, 7, 16, 9, 2, 17, 1, 7, 6, 17, 4, 7, 3, 5, 0, 14, 8, 5, 4, 14, 4, 16, 1, 8, 6, 16, 4, 8, 3, 0, 1, 8, 6, 0, 4, 8, 3, 6, 0, 18, 4, 15, 0, 9, 2, 6, 2, 9, 2, 0, 14, 9, 6, 0, 16, 9, 2, 3, 7, 18, 8, 9, 7, 6, 8, 2, 11, 6, 9, 4, 11, 2, 9, 10, 5, 6, 9, 12, 5, 2, 9, 10, 6, 4, 18, 10, 6, 2, 9, 12, 15, 2, 9, 11, 1, 4, 20, 13, 1, 2, 10, 11, 11, 2, 10, 9, 1, 4, 20, 9, 1, 2, 10, 11, 11, 2, 10, 5, 9, 18, 6, 14, 9, 9, 3, 5, 12, 9, 3, 6, 4, 6, 9, 8, 4, 2, 9, 10, 16, 8, 6, 10, 16, 4, 6, 0, 0, 18, 8, 0, 0, 9, 4, 9, 4, 9, 4, 6, 5, 14, 12, 13, 5, 7, 6, 6, 11, 7, 6, 4, 3, 15, 7, 9, 3, 5, 7, 14, 12, 10, 6, 14, 14, 10, 2, 0, 11, 4, 10, 0, 16, 4, 5, 1, 10, 22, 3, 1, 11, 22, 1, 8, 9, 6, 10, 10, 9, 2, 10, 13, 2, 6, 12, 16, 2, 3, 6, 13, 8, 3, 6, 10, 6, 4, 18, 10, 6, 2, 9, 12, 15, 2, 9, 7, 8, 10, 16, 12, 8, 5, 8, 7, 16, 5, 8, 8, 1, 8, 12, 8, 1, 4, 6, 12, 7, 4, 6, 7, 1, 12, 14, 13, 1, 6, 7, 7, 8, 6, 7, 2, 14, 12, 6, 2, 16, 12, 2, 11, 16, 6, 6, 11, 19, 6, 3, 7, 16, 6, 6, 7, 19, 6, 3, 13, 4, 4, 10, 13, 4, 2, 10, 0, 19, 19, 3, 0, 20, 19, 1, 12, 8, 6, 8, 12, 12, 6, 4, 8, 1, 8, 22, 8, 12, 8, 11, 12, 8, 6, 8, 12, 12, 6, 4, 6, 8, 6, 8, 6, 12, 6, 4, 14, 5, 6, 9, 14, 8, 6, 3, 0, 6, 24, 4, 0, 8, 24, 2, 14, 12, 10, 6, 14, 14, 10, 2, 0, 12, 10, 6, 0, 14, 10, 2, 4, 6, 19, 3, 4, 7, 19, 1, 1, 6, 19, 3, 1, 7, 19, 1, 4, 0, 16, 9, 4, 3, 16, 3, 0, 1, 24, 5, 8, 1, 8, 5, 3, 6, 6, 15, 3, 11, 6, 5, 9, 6, 6, 9, 11, 6, 2, 9, 0, 17, 18, 3, 0, 18, 18, 1, 6, 22, 18, 2, 6, 23, 18, 1, 2, 12, 6, 9, 2, 15, 6, 3, 18, 12, 6, 9, 18, 15, 6, 3, 0, 12, 6, 9, 0, 15, 6, 3, 11, 14, 4, 10, 11, 19, 4, 5, 9, 6, 6, 16, 9, 14, 6, 8, 7, 7, 10, 10, 7, 12, 10, 5, 1, 3, 6, 13, 3, 3, 2, 13, 18, 1, 6, 13, 18, 1, 3, 13, 5, 1, 6, 9, 7, 1, 2, 9, 18, 2, 6, 11, 18, 2, 3, 11, 0, 2, 6, 11, 3, 2, 3, 11, 9, 12, 15, 6, 9, 14, 15, 2, 2, 2, 20, 3, 2, 3, 20, 1, 10, 6, 4, 9, 10, 6, 2, 9, 5, 6, 12, 14, 5, 6, 6, 7, 11, 13, 6, 7, 9, 0, 6, 9, 11, 0, 2, 9, 7, 0, 9, 6, 10, 0, 3, 6, 10, 6, 6, 9, 12, 6, 2, 9, 4, 1, 12, 20, 4, 1, 6, 10, 10, 11, 6, 10, 6, 7, 18, 3, 6, 7, 9, 3, 0, 7, 18, 3, 9, 7, 9, 3, 3, 20, 18, 3, 9, 20, 6, 3, 9, 6, 6, 9, 11, 6, 2, 9, 6, 2, 12, 15, 10, 2, 4, 15, 2, 3, 18, 3, 2, 4, 18, 1, 19, 4, 4, 18, 21, 4, 2, 9, 19, 13, 2, 9, 0, 1, 19, 3, 0, 2, 19, 1, 5, 0, 15, 4, 5, 2, 15, 2, 5, 2, 14, 5, 12, 2, 7, 5, 1, 2, 22, 14, 1, 2, 11, 14, 8, 15, 6, 9, 10, 15, 2, 9, 6, 17, 18, 3, 6, 18, 18, 1, 9, 6, 3, 18, 9, 12, 3, 6, 2, 0, 20, 3, 2, 1, 20, 1, 5, 4, 5, 12, 5, 8, 5, 4, 8, 6, 12, 5, 12, 6, 4, 5, 9, 12, 6, 12, 9, 12, 3, 6, 12, 18, 3, 6, 14, 14, 8, 10, 18, 14, 4, 5, 14, 19, 4, 5, 2, 14, 8, 10, 2, 14, 4, 5, 6, 19, 4, 5, 10, 18, 12, 6, 16, 18, 6, 3, 10, 21, 6, 3, 1, 3, 6, 9, 1, 6, 6, 3, 11, 3, 3, 20, 12, 3, 1, 20, 4, 6, 14, 6, 4, 6, 7, 3, 11, 9, 7, 3, 6, 5, 12, 13, 10, 5, 4, 13, 5, 4, 4, 15, 5, 9, 4, 5, 9, 16, 15, 4, 14, 16, 5, 4, 7, 8, 6, 14, 7, 8, 3, 7, 10, 15, 3, 7, 7, 6, 10, 6, 7, 8, 10, 2, 2, 5, 18, 3, 2, 6, 18, 1, 5, 1, 15, 8, 5, 5, 15, 4, 7, 1, 8, 18, 7, 10, 8, 9, 0, 10, 24, 3, 0, 11, 24, 1, 0, 2, 6, 13, 2, 2, 2, 13, 16, 0, 8, 10, 20, 0, 4, 5, 16, 5, 4, 5, 5, 1, 10, 9, 5, 4, 10, 3, 5, 6, 18, 3, 5, 7, 18, 1, 0, 1, 24, 3, 0, 2, 24, 1, 11, 4, 6, 11, 13, 4, 2, 11, 0, 0, 8, 10, 0, 0, 4, 5, 4, 5, 4, 5, 4, 16, 18, 3, 4, 17, 18, 1, 2, 16, 18, 3, 2, 17, 18, 1, 3, 0, 18, 10, 12, 0, 9, 5, 3, 5, 9, 5, 2, 3, 20, 21, 12, 3, 10, 21, 6, 7, 14, 3, 6, 7, 7, 3, 0, 9, 12, 6, 0, 9, 6, 3, 6, 12, 6, 3, 3, 14, 21, 4, 10, 14, 7, 4, 0, 14, 21, 4, 7, 14, 7, 4, 5, 21, 18, 3, 11, 21, 6, 3, 1, 21, 18, 3, 7, 21, 6, 3, 19, 4, 4, 18, 21, 4, 2, 9, 19, 13, 2, 9, 3, 7, 18, 3, 3, 8, 18, 1, 19, 4, 4, 18, 21, 4, 2, 9, 19, 13, 2, 9, 7, 15, 10, 6, 7, 17, 10, 2, 9, 13, 11, 9, 9, 16, 11, 3, 0, 6, 4, 10, 0, 11, 4, 5, 15, 16, 9, 6, 15, 18, 9, 2, 1, 5, 4, 18, 1, 5, 2, 9, 3, 14, 2, 9, 9, 8, 8, 10, 13, 8, 4, 5, 9, 13, 4, 5, 7, 8, 8, 10, 7, 8, 4, 5, 11, 13, 4, 5, 9, 8, 12, 5, 13, 8, 4, 5, 7, 8, 9, 7, 10, 8, 3, 7, 9, 8, 12, 5, 13, 8, 4, 5, 7, 6, 9, 7, 10, 6, 3, 7, 9, 8, 12, 5, 13, 8, 4, 5, 10, 5, 4, 18, 10, 11, 4, 6, 5, 5, 14, 12, 5, 11, 14, 6, 0, 1, 11, 4, 0, 3, 11, 2, 9, 10, 6, 10, 11, 10, 2, 10, 2, 17, 11, 6, 2, 19, 11, 2, 15, 16, 9, 6, 15, 18, 9, 2, 1, 10, 18, 2, 1, 11, 18, 1, 6, 4, 12, 13, 10, 4, 4, 13, 0, 18, 18, 3, 0, 19, 18, 1, 6, 18, 18, 3, 6, 19, 18, 1, 0, 16, 9, 6, 0, 18, 9, 2, 13, 15, 9, 6, 13, 17, 9, 2, 2, 15, 9, 6, 2, 17, 9, 2, 13, 1, 6, 16, 13, 1, 3, 16, 5, 1, 6, 16, 8, 1, 3, 16, 11, 5, 6, 10, 13, 5, 2, 10, 7, 5, 6, 10, 9, 5, 2, 10, 10, 0, 6, 24, 12, 0, 2, 24, 3, 4, 4, 20, 3, 4, 2, 10, 5, 14, 2, 10, 14, 0, 6, 9, 16, 0, 2, 9, 4, 0, 6, 9, 6, 0, 2, 9, 4, 5, 18, 5, 10, 5, 6, 5, 5, 6, 6, 9, 7, 6, 2, 9, 7, 2, 15, 8, 12, 2, 5, 8, 2, 2, 15, 8, 7, 2, 5, 8, 10, 0, 4, 9, 10, 0, 2, 9, 3, 4, 6, 12, 3, 4, 3, 6, 6, 10, 3, 6, 16, 0, 8, 18, 16, 0, 4, 18, 0, 0, 8, 18, 4, 0, 4, 18, 0, 7, 24, 6, 0, 9, 24, 2, 4, 7, 14, 3, 11, 7, 7, 3, 10, 8, 8, 15, 10, 8, 4, 15, 7, 0, 10, 14, 12, 0, 5, 14, 13, 10, 8, 10, 17, 10, 4, 5, 13, 15, 4, 5, 3, 0, 4, 9, 5, 0, 2, 9, 16, 1, 6, 8, 16, 1, 3, 8, 2, 1, 6, 8, 5, 1, 3, 8, 3, 6, 18, 12, 3, 10, 18, 4, 4, 12, 16, 4, 4, 14, 16, 2, 4, 9, 16, 15, 4, 14, 16, 5, 3, 10, 8, 10, 3, 10, 4, 5, 7, 15, 4, 5, 8, 18, 16, 6, 16, 18, 8, 3, 8, 21, 8, 3, 2, 16, 12, 5, 6, 16, 4, 5, 14, 14, 9, 4, 14, 16, 9, 2, 7, 14, 9, 6, 7, 16, 9, 2, 4, 10, 16, 12, 4, 14, 16, 4, 0, 13, 19, 6, 0, 15, 19, 2, 10, 13, 9, 6, 10, 15, 9, 2, 5, 0, 3, 23, 6, 0, 1, 23, 0, 8, 24, 6, 0, 10, 24, 2, 0, 5, 5, 12, 0, 9, 5, 4, 3, 0, 19, 18, 3, 9, 19, 9, 9, 11, 6, 12, 9, 11, 3, 6, 12, 17, 3, 6, 0, 5, 24, 8, 12, 5, 12, 4, 0, 9, 12, 4, 6, 18, 9, 4, 6, 20, 9, 2, 8, 8, 10, 6, 8, 10, 10, 2, 2, 7, 20, 3, 2, 8, 20, 1, 12, 0, 7, 20, 12, 10, 7, 10, 5, 0, 7, 20, 5, 10, 7, 10, 14, 2, 2, 18, 14, 11, 2, 9, 5, 8, 10, 12, 10, 8, 5, 12, 6, 9, 12, 8, 12, 9, 6, 4, 6, 13, 6, 4, 7, 7, 3, 14, 7, 14, 3, 7, 11, 2, 12, 16, 17, 2, 6, 8, 11, 10, 6, 8, 7, 0, 6, 9, 9, 0, 2, 9, 13, 14, 9, 4, 13, 16, 9, 2, 0, 12, 22, 4, 0, 12, 11, 2, 11, 14, 11, 2, 1, 12, 22, 6, 12, 12, 11, 3, 1, 15, 11, 3, 6, 6, 9, 6, 9, 6, 3, 6, 10, 0, 4, 9, 10, 0, 2, 9, 3, 8, 18, 7, 9, 8, 6, 7, 0, 6, 24, 6, 0, 8, 24, 2, 0, 11, 24, 10, 8, 11, 8, 10, 3, 3, 18, 21, 9, 3, 6, 21, 7, 12, 4, 10, 9, 12, 2, 10, 10, 16, 10, 8, 15, 16, 5, 4, 10, 20, 5, 4, 8, 6, 6, 9, 10, 6, 2, 9, 12, 10, 6, 12, 15, 10, 3, 6, 12, 16, 3, 6, 6, 10, 6, 12, 6, 10, 3, 6, 9, 16, 3, 6, 16, 12, 6, 12, 19, 12, 3, 6, 16, 18, 3, 6, 2, 12, 6, 12, 2, 12, 3, 6, 5, 18, 3, 6, 10, 15, 6, 9, 12, 15, 2, 9, 8, 15, 6, 9, 10, 15, 2, 9, 14, 20, 10, 4, 14, 20, 5, 4, 0, 20, 10, 4, 5, 20, 5, 4, 11, 17, 9, 6, 11, 19, 9, 2, 3, 2, 14, 4, 3, 4, 14, 2, 10, 1, 10, 4, 10, 3, 10, 2, 0, 15, 10, 4, 5, 15, 5, 4, 19, 2, 3, 19, 20, 2, 1, 19, 4, 12, 9, 8, 7, 12, 3, 8, 4, 7, 5, 12, 4, 11, 5, 4, 0, 1, 24, 3, 8, 1, 8, 3, 6, 8, 12, 4, 6, 10, 12, 2, 19, 3, 4, 10, 19, 3, 2, 10, 0, 6, 9, 6, 3, 6, 3, 6, 18, 0, 6, 22, 20, 0, 2, 22, 0, 0, 6, 22, 2, 0, 2, 22, 5, 15, 19, 3, 5, 16, 19, 1, 10, 7, 4, 15, 10, 12, 4, 5, 9, 6, 6, 9, 11, 6, 2, 9, 0, 21, 18, 3, 0, 22, 18, 1, 7, 3, 10, 15, 7, 8, 10, 5, 1, 7, 18, 3, 1, 8, 18, 1, 8, 2, 9, 6, 11, 2, 3, 6, 0, 10, 24, 14, 0, 17, 24, 7, 13, 9, 8, 10, 17, 9, 4, 5, 13, 14, 4, 5, 10, 5, 4, 9, 12, 5, 2, 9, 13, 9, 8, 10, 17, 9, 4, 5, 13, 14, 4, 5, 7, 11, 10, 10, 7, 11, 5, 5, 12, 16, 5, 5, 4, 13, 18, 4, 13, 13, 9, 2, 4, 15, 9, 2, 0, 0, 19, 2, 0, 1, 19, 1, 0, 18, 24, 6, 8, 18, 8, 6, 6, 4, 8, 16, 6, 12, 8, 8, 7, 8, 10, 4, 7, 10, 10, 2, 0, 3, 6, 9, 0, 6, 6, 3, 13, 15, 7, 9, 13, 18, 7, 3, 3, 18, 12, 6, 3, 18, 6, 3, 9, 21, 6, 3, 12, 14, 6, 9, 12, 17, 6, 3, 2, 15, 15, 8, 2, 19, 15, 4, 9, 6, 6, 16, 9, 14, 6, 8, 6, 6, 7, 12, 6, 10, 7, 4, 14, 6, 6, 9, 14, 9, 6, 3, 5, 14, 6, 9, 5, 17, 6, 3, 10, 8, 6, 9, 12, 8, 2, 9, 6, 6, 4, 18, 6, 6, 2, 9, 8, 15, 2, 9, 14, 9, 6, 12, 17, 9, 3, 6, 14, 15, 3, 6, 4, 9, 6, 12, 4, 9, 3, 6, 7, 15, 3, 6, 14, 15, 9, 6, 14, 17, 9, 2, 0, 20, 18, 4, 0, 20, 9, 2, 9, 22, 9, 2, 13, 18, 9, 6, 13, 20, 9, 2, 2, 18, 9, 6, 2, 20, 9, 2, 6, 16, 18, 3, 6, 17, 18, 1, 0, 16, 18, 3, 0, 17, 18, 1, 19, 2, 4, 22, 21, 2, 2, 11, 19, 13, 2, 11, 1, 2, 4, 22, 1, 2, 2, 11, 3, 13, 2, 11, 15, 0, 2, 24, 15, 0, 1, 24, 3, 20, 16, 4, 11, 20, 8, 4, 11, 6, 4, 18, 13, 6, 2, 9, 11, 15, 2, 9, 7, 9, 10, 14, 7, 9, 5, 7, 12, 16, 5, 7, 14, 6, 6, 9, 14, 9, 6, 3, 3, 6, 7, 9, 3, 9, 7, 3, 20, 4, 4, 20, 22, 4, 2, 10, 20, 14, 2, 10, 7, 6, 6, 9, 7, 9, 6, 3, 7, 0, 10, 14, 12, 0, 5, 7, 7, 7, 5, 7, 2, 1, 18, 6, 11, 1, 9, 6, 15, 0, 2, 24, 15, 0, 1, 24, 7, 0, 2, 24, 8, 0, 1, 24, 13, 12, 6, 7, 13, 12, 3, 7, 5, 12, 6, 7, 8, 12, 3, 7, 3, 5, 18, 19, 9, 5, 6, 19, 5, 6, 9, 6, 8, 6, 3, 6, 9, 5, 9, 6, 12, 5, 3, 6, 3, 16, 10, 8, 3, 16, 5, 4, 8, 20, 5, 4, 19, 8, 5, 15, 19, 13, 5, 5, 0, 8, 5, 15, 0, 13, 5, 5, 20, 4, 4, 20, 22, 4, 2, 10, 20, 14, 2, 10, 0, 4, 4, 20, 0, 4, 2, 10, 2, 14, 2, 10, 7, 7, 10, 4, 7, 7, 5, 4, 4, 19, 14, 4, 11, 19, 7, 4, 10, 11, 12, 3, 10, 11, 6, 3, 0, 1, 24, 3, 0, 2, 24, 1, 7, 2, 14, 20, 14, 2, 7, 10, 7, 12, 7, 10, 0, 13, 6, 9, 2, 13, 2, 9, 13, 0, 4, 19, 13, 0, 2, 19, 1, 11, 14, 3, 8, 11, 7, 3, 7, 1, 16, 20, 15, 1, 8, 10, 7, 11, 8, 10, 0, 10, 21, 9, 7, 10, 7, 9, 6, 19, 15, 5, 11, 19, 5, 5, 8, 10, 6, 6, 11, 10, 3, 6, 7, 1, 16, 20, 15, 1, 8, 10, 7, 11, 8, 10, 1, 1, 16, 20, 1, 1, 8, 10, 9, 11, 8, 10, 16, 4, 3, 12, 16, 10, 3, 6, 5, 4, 3, 12, 5, 10, 3, 6, 7, 6, 10, 8, 12, 6, 5, 4, 7, 10, 5, 4, 4, 9, 6, 6, 4, 12, 6, 3, 6, 5, 12, 4, 6, 7, 12, 2, 9, 2, 5, 15, 9, 7, 5, 5, 15, 0, 9, 6, 15, 2, 9, 2, 6, 0, 11, 10, 6, 5, 11, 5, 12, 7, 4, 12, 12, 13, 4, 6, 7, 2, 9, 4, 7, 4, 9, 2, 6, 0, 13, 6, 6, 2, 13, 2, 10, 6, 4, 18, 10, 6, 2, 9, 12, 15, 2, 9, 10, 8, 6, 9, 12, 8, 2, 9, 3, 18, 10, 6, 3, 20, 10, 2, 4, 14, 20, 3, 4, 15, 20, 1, 2, 15, 9, 6, 2, 17, 9, 2, 13, 0, 4, 19, 13, 0, 2, 19, 7, 0, 4, 19, 9, 0, 2, 19, 1, 4, 22, 2, 1, 5, 22, 1, 0, 0, 9, 6, 0, 2, 9, 2, 0, 0, 24, 18, 0, 9, 24, 9, 3, 2, 16, 8, 3, 6, 16, 4, 3, 6, 18, 6, 3, 8, 18, 2, 3, 1, 6, 10, 5, 1, 2, 10, 13, 0, 9, 6, 16, 0, 3, 6, 2, 0, 9, 6, 5, 0, 3, 6, 10, 2, 4, 15, 10, 7, 4, 5, 6, 0, 7, 10, 6, 5, 7, 5, 2, 2, 20, 4, 12, 2, 10, 2, 2, 4, 10, 2, 2, 11, 19, 3, 2, 12, 19, 1, 10, 8, 6, 9, 12, 8, 2, 9, 8, 8, 6, 9, 10, 8, 2, 9, 13, 8, 4, 9, 13, 8, 2, 9, 3, 11, 9, 9, 6, 11, 3, 9, 3, 9, 18, 5, 9, 9, 6, 5, 2, 4, 2, 20, 2, 14, 2, 10, 14, 17, 8, 6, 14, 20, 8, 3, 3, 21, 18, 2, 3, 22, 18, 1, 5, 4, 15, 6, 10, 4, 5, 6, 2, 15, 12, 6, 2, 17, 12, 2, 17, 8, 6, 9, 17, 11, 6, 3, 2, 12, 20, 4, 2, 12, 10, 2, 12, 14, 10, 2, 0, 17, 24, 6, 0, 19, 24, 2, 7, 16, 9, 4, 7, 18, 9, 2, 15, 1, 4, 22, 17, 1, 2, 11, 15, 12, 2, 11, 5, 1, 4, 22, 5, 1, 2, 11, 7, 12, 2, 11, 11, 13, 8, 9, 11, 16, 8, 3, 6, 1, 6, 9, 8, 1, 2, 9, 11, 4, 3, 18, 11, 10, 3, 6, 5, 8, 12, 6, 5, 8, 6, 3, 11, 11, 6, 3, 15, 7, 5, 8, 15, 11, 5, 4, 4, 7, 5, 8, 4, 11, 5, 4, 12, 6, 6, 12, 15, 6, 3, 6, 12, 12, 3, 6, 6, 6, 6, 12, 6, 6, 3, 6, 9, 12, 3, 6, 5, 9, 14, 8, 12, 9, 7, 4, 5, 13, 7, 4, 9, 1, 3, 14, 9, 8, 3, 7, 12, 6, 6, 12, 12, 10, 6, 4, 4, 5, 4, 18, 4, 5, 2, 9, 6, 14, 2, 9, 4, 6, 16, 18, 4, 12, 16, 6, 5, 4, 7, 20, 5, 14, 7, 10, 14, 8, 8, 12, 14, 14, 8, 6, 9, 10, 6, 14, 9, 10, 3, 7, 12, 17, 3, 7, 9, 5, 9, 6, 12, 5, 3, 6, 9, 4, 3, 18, 10, 4, 1, 18, 1, 4, 22, 14, 12, 4, 11, 7, 1, 11, 11, 7, 2, 7, 18, 2, 2, 8, 18, 1, 12, 6, 6, 12, 12, 10, 6, 4, 6, 5, 9, 7, 9, 5, 3, 7, 12, 7, 4, 12, 12, 13, 4, 6, 8, 7, 4, 12, 8, 13, 4, 6, 7, 2, 10, 22, 7, 13, 10, 11, 0, 1, 3, 20, 1, 1, 1, 20, 4, 13, 18, 4, 13, 13, 9, 2, 4, 15, 9, 2, 2, 13, 18, 4, 2, 13, 9, 2, 11, 15, 9, 2, 15, 15, 9, 6, 15, 17, 9, 2, 0, 15, 9, 6, 0, 17, 9, 2, 6, 0, 18, 24, 15, 0, 9, 12, 6, 12, 9, 12, 6, 6, 6, 12, 6, 10, 6, 4, 8, 7, 10, 4, 8, 9, 10, 2, 1, 9, 18, 6, 1, 9, 9, 3, 10, 12, 9, 3, 6, 6, 18, 3, 6, 7, 18, 1, 7, 7, 9, 8, 10, 7, 3, 8, 10, 12, 6, 12, 12, 12, 2, 12, 3, 14, 18, 3, 3, 15, 18, 1, 15, 17, 9, 7, 18, 17, 3, 7, 1, 12, 10, 6, 1, 14, 10, 2, 15, 17, 9, 7, 18, 17, 3, 7, 10, 3, 3, 19, 11, 3, 1, 19, 15, 17, 9, 7, 18, 17, 3, 7, 6, 1, 11, 9, 6, 4, 11, 3, 15, 17, 9, 7, 18, 17, 3, 7, 6, 5, 11, 6, 6, 8, 11, 3, 16, 7, 8, 5, 16, 7, 4, 5, 2, 4, 20, 19, 12, 4, 10, 19, 2, 1, 21, 6, 9, 1, 7, 6, 6, 5, 12, 14, 6, 5, 6, 7, 12, 12, 6, 7, 9, 0, 6, 9, 11, 0, 2, 9, 2, 11, 8, 5, 6, 11, 4, 5, 16, 7, 8, 5, 16, 7, 4, 5, 0, 7, 8, 5, 4, 7, 4, 5, 15, 17, 9, 7, 18, 17, 3, 7, 8, 6, 8, 10, 8, 6, 4, 5, 12, 11, 4, 5, 15, 15, 9, 9, 18, 15, 3, 9, 0, 15, 9, 9, 3, 15, 3, 9, 12, 10, 9, 7, 15, 10, 3, 7, 3, 10, 9, 7, 6, 10, 3, 7, 13, 15, 10, 8, 18, 15, 5, 4, 13, 19, 5, 4, 0, 1, 6, 12, 0, 1, 3, 6, 3, 7, 3, 6, 10, 0, 6, 12, 13, 0, 3, 6, 10, 6, 3, 6, 7, 0, 10, 12, 7, 0, 5, 6, 12, 6, 5, 6, 4, 1, 16, 8, 4, 1, 8, 8, 0, 21, 19, 3, 0, 22, 19, 1, 6, 9, 18, 4, 15, 9, 9, 2, 6, 11, 9, 2, 3, 4, 9, 6, 3, 6, 9, 2, 9, 1, 6, 15, 9, 6, 6, 5, 5, 9, 6, 6, 8, 9, 3, 6, 5, 1, 14, 9, 5, 4, 14, 3, 3, 0, 8, 20, 3, 0, 4, 10, 7, 10, 4, 10, 5, 0, 7, 9, 5, 3, 7, 3, 6, 6, 12, 5, 10, 6, 4, 5, 0, 1, 8, 14, 4, 1, 4, 14, 2, 12, 22, 4, 2, 14, 22, 2, 8, 17, 6, 6, 8, 20, 6, 3, 18, 1, 6, 7, 18, 1, 3, 7, 0, 0, 6, 6, 3, 0, 3, 6, 4, 6, 17, 18, 4, 12, 17, 6, 6, 0, 12, 6, 6, 0, 6, 3, 12, 3, 6, 3, 4, 7, 18, 4, 13, 7, 9, 2, 4, 9, 9, 2, 4, 12, 10, 6, 4, 14, 10, 2, 7, 9, 10, 12, 12, 9, 5, 6, 7, 15, 5, 6, 0, 1, 24, 3, 8, 1, 8, 3, 13, 11, 6, 6, 13, 11, 3, 6, 5, 11, 6, 6, 8, 11, 3, 6, 3, 10, 19, 3, 3, 11, 19, 1, 0, 2, 6, 9, 0, 5, 6, 3, 14, 16, 10, 6, 14, 18, 10, 2, 0, 16, 10, 6, 0, 18, 10, 2, 14, 13, 9, 6, 14, 15, 9, 2, 0, 16, 18, 3, 0, 17, 18, 1, 6, 16, 18, 3, 6, 17, 18, 1, 0, 18, 9, 6, 0, 20, 9, 2, 14, 13, 9, 6, 14, 15, 9, 2, 6, 2, 6, 9, 8, 2, 2, 9, 15, 8, 4, 12, 15, 8, 2, 12, 8, 13, 8, 8, 8, 17, 8, 4, 4, 20, 18, 3, 10, 20, 6, 3, 5, 8, 4, 12, 7, 8, 2, 12, 7, 7, 12, 3, 7, 7, 6, 3, 10, 6, 4, 9, 12, 6, 2, 9, 5, 20, 18, 3, 11, 20, 6, 3, 1, 20, 18, 3, 7, 20, 6, 3, 18, 1, 6, 20, 21, 1, 3, 10, 18, 11, 3, 10, 0, 1, 6, 20, 0, 1, 3, 10, 3, 11, 3, 10, 13, 3, 4, 18, 15, 3, 2, 9, 13, 12, 2, 9, 0, 2, 6, 12, 0, 6, 6, 4, 12, 9, 12, 6, 18, 9, 6, 3, 12, 12, 6, 3, 7, 3, 4, 18, 7, 3, 2, 9, 9, 12, 2, 9, 14, 0, 6, 9, 16, 0, 2, 9, 0, 9, 12, 6, 0, 9, 6, 3, 6, 12, 6, 3, 14, 4, 8, 20, 18, 4, 4, 10, 14, 14, 4, 10, 2, 4, 8, 20, 2, 4, 4, 10, 6, 14, 4, 10, 14, 13, 9, 6, 14, 15, 9, 2, 1, 13, 9, 6, 1, 15, 9, 2, 3, 15, 18, 3, 9, 15, 6, 3, 5, 13, 9, 6, 5, 15, 9, 2, 5, 0, 18, 3, 5, 1, 18, 1, 8, 2, 6, 7, 11, 2, 3, 7, 9, 1, 9, 6, 12, 1, 3, 6, 6, 1, 9, 6, 9, 1, 3, 6, 5, 6, 14, 6, 12, 6, 7, 3, 5, 9, 7, 3, 8, 2, 6, 13, 10, 2, 2, 13, 6, 11, 12, 6, 12, 11, 6, 3, 6, 14, 6, 3, 3, 1, 18, 15, 9, 1, 6, 15, 13, 0, 6, 7, 13, 0, 3, 7, 3, 3, 16, 6, 3, 6, 16, 3, 12, 1, 3, 12, 12, 7, 3, 6, 7, 7, 6, 9, 9, 7, 2, 9, 13, 0, 4, 24, 13, 0, 2, 24, 7, 0, 4, 24, 9, 0, 2, 24, 11, 9, 5, 12, 11, 13, 5, 4, 7, 15, 9, 6, 7, 17, 9, 2, 5, 7, 18, 6, 5, 9, 18, 2, 8, 9, 5, 12, 8, 13, 5, 4, 4, 17, 17, 6, 4, 19, 17, 2, 0, 3, 18, 14, 0, 3, 9, 7, 9, 10, 9, 7, 0, 1, 24, 2, 0, 2, 24, 1, 0, 15, 18, 3, 0, 16, 18, 1, 9, 0, 6, 9, 11, 0, 2, 9, 3, 3, 14, 12, 3, 9, 14, 6, 12, 1, 3, 12, 12, 7, 3, 6, 8, 0, 6, 9, 10, 0, 2, 9, 10, 6, 6, 10, 12, 6, 2, 10, 5, 0, 6, 9, 7, 0, 2, 9, 2, 0, 21, 7, 9, 0, 7, 7, 6, 11, 12, 5, 10, 11, 4, 5, 8, 7, 9, 8, 11, 7, 3, 8, 9, 6, 6, 18, 9, 6, 3, 9, 12, 15, 3, 9, 15, 14, 8, 10, 19, 14, 4, 5, 15, 19, 4, 5, 1, 14, 8, 10, 1, 14, 4, 5, 5, 19, 4, 5, 11, 0, 8, 10, 15, 0, 4, 5, 11, 5, 4, 5, 5, 0, 8, 10, 5, 0, 4, 5, 9, 5, 4, 5, 6, 1, 12, 5, 6, 1, 6, 5, 1, 12, 18, 2, 10, 12, 9, 2, 2, 8, 20, 6, 12, 8, 10, 3, 2, 11, 10, 3, 7, 6, 9, 7, 10, 6, 3, 7, 10, 5, 8, 16, 14, 5, 4, 8, 10, 13, 4, 8, 3, 9, 16, 8, 3, 9, 8, 4, 11, 13, 8, 4, 7, 8, 10, 4, 7, 8, 5, 4, 7, 12, 10, 8, 7, 12, 5, 4, 12, 16, 5, 4, 9, 19, 15, 4, 14, 19, 5, 4, 1, 0, 18, 9, 7, 0, 6, 9, 13, 4, 10, 8, 18, 4, 5, 4, 13, 8, 5, 4, 3, 16, 18, 4, 9, 16, 6, 4, 8, 7, 10, 12, 13, 7, 5, 6, 8, 13, 5, 6, 6, 7, 10, 12, 6, 7, 5, 6, 11, 13, 5, 6, 4, 6, 18, 7, 10, 6, 6, 7, 0, 17, 18, 3, 0, 18, 18, 1, 3, 17, 18, 3, 3, 18, 18, 1, 2, 4, 6, 10, 4, 4, 2, 10, 16, 0, 8, 24, 16, 0, 4, 24, 4, 0, 8, 15, 8, 0, 4, 15, 16, 0, 8, 24, 16, 0, 4, 24, 1, 4, 18, 9, 7, 4, 6, 9, 15, 12, 9, 6, 15, 14, 9, 2, 3, 9, 18, 6, 3, 9, 9, 3, 12, 12, 9, 3, 18, 5, 6, 9, 18, 8, 6, 3, 0, 5, 6, 9, 0, 8, 6, 3, 4, 7, 18, 4, 13, 7, 9, 2, 4, 9, 9, 2, 2, 1, 12, 20, 2, 1, 6, 10, 8, 11, 6, 10, 17, 0, 6, 23, 17, 0, 3, 23, 1, 6, 2, 18, 1, 15, 2, 9, 8, 8, 10, 6, 8, 10, 10, 2, 0, 6, 20, 6, 0, 6, 10, 3, 10, 9, 10, 3, 11, 12, 12, 5, 15, 12, 4, 5, 0, 4, 3, 19, 1, 4, 1, 19, 19, 1, 3, 18, 20, 1, 1, 18, 2, 1, 3, 18, 3, 1, 1, 18, 3, 10, 18, 3, 9, 10, 6, 3, 4, 4, 10, 9, 9, 4, 5, 9, 7, 13, 14, 7, 7, 13, 7, 7, 3, 13, 14, 7, 10, 13, 7, 7, 8, 15, 9, 6, 11, 15, 3, 6, 4, 14, 8, 10, 4, 14, 4, 5, 8, 19, 4, 5, 10, 14, 4, 10, 10, 19, 4, 5, 3, 8, 5, 16, 3, 16, 5, 8, 15, 10, 9, 6, 15, 12, 9, 2, 0, 10, 9, 6, 0, 12, 9, 2, 6, 7, 12, 9, 6, 10, 12, 3, 9, 10, 5, 8, 9, 14, 5, 4, 12, 1, 3, 12, 12, 7, 3, 6, 8, 15, 6, 9, 10, 15, 2, 9, 16, 6, 7, 6, 16, 9, 7, 3, 8, 1, 4, 22, 10, 1, 2, 22, 6, 6, 14, 3, 6, 6, 7, 3, 0, 18, 19, 3, 0, 19, 19, 1, 17, 0, 6, 24, 17, 0, 3, 24, 0, 13, 15, 6, 5, 13, 5, 6, 9, 6, 10, 14, 14, 6, 5, 7, 9, 13, 5, 7, 1, 6, 8, 10, 1, 6, 4, 5, 5, 11, 4, 5, 7, 6, 12, 5, 7, 6, 6, 5, 7, 7, 9, 6, 10, 7, 3, 6, 7, 8, 14, 14, 14, 8, 7, 7, 7, 15, 7, 7, 3, 8, 14, 14, 3, 8, 7, 7, 10, 15, 7, 7, 9, 8, 13, 4, 9, 10, 13, 2, 3, 2, 6, 12, 3, 2, 3, 6, 6, 8, 3, 6, 6, 10, 17, 6, 6, 13, 17, 3, 1, 10, 17, 6, 1, 13, 17, 3, 16, 7, 8, 9, 16, 10, 8, 3, 0, 7, 8, 9, 0, 10, 8, 3, 0, 9, 24, 10, 12, 9, 12, 5, 0, 14, 12, 5, 3, 2, 15, 8, 8, 2, 5, 8, 4, 2, 18, 8, 10, 2, 6, 8, 0, 1, 18, 4, 0, 1, 9, 2, 9, 3, 9, 2, 20, 2, 3, 18, 21, 2, 1, 18, 1, 3, 3, 19, 2, 3, 1, 19, 18, 8, 6, 16, 20, 8, 2, 16, 0, 8, 6, 16, 2, 8, 2, 16, 8, 18, 11, 6, 8, 20, 11, 2, 4, 6, 12, 5, 8, 6, 4, 5, 7, 6, 12, 5, 11, 6, 4, 5, 6, 3, 9, 6, 9, 3, 3, 6, 7, 6, 12, 5, 7, 6, 6, 5, 9, 8, 6, 7, 12, 8, 3, 7, 8, 2, 9, 6, 11, 2, 3, 6, 8, 14, 6, 9, 8, 17, 6, 3, 8, 2, 9, 6, 11, 2, 3, 6, 4, 3, 16, 20, 4, 3, 8, 10, 12, 13, 8, 10, 7, 6, 10, 12, 12, 6, 5, 6, 7, 12, 5, 6, 0, 2, 7, 12, 0, 6, 7, 4, 12, 17, 11, 6, 12, 19, 11, 2, 4, 7, 12, 8, 4, 7, 6, 4, 10, 11, 6, 4, 8, 11, 8, 10, 12, 11, 4, 5, 8, 16, 4, 5, 9, 1, 4, 9, 11, 1, 2, 9, 14, 0, 3, 22, 15, 0, 1, 22, 7, 0, 3, 22, 8, 0, 1, 22, 4, 7, 18, 4, 13, 7, 9, 2, 4, 9, 9, 2, 10, 2, 4, 15, 10, 7, 4, 5, 12, 1, 3, 12, 12, 7, 3, 6, 0, 0, 18, 13, 9, 0, 9, 13, 16, 0, 3, 24, 17, 0, 1, 24, 5, 0, 3, 24, 6, 0, 1, 24, 10, 15, 5, 8, 10, 19, 5, 4, 2, 18, 18, 2, 2, 19, 18, 1, 2, 8, 20, 3, 2, 9, 20, 1, 7, 6, 9, 6, 7, 8, 9, 2, 3, 2, 19, 10, 3, 7, 19, 5, 2, 7, 19, 3, 2, 8, 19, 1, 15, 6, 9, 4, 15, 8, 9, 2, 2, 2, 18, 8, 8, 2, 6, 8, 10, 9, 14, 4, 10, 9, 7, 4, 4, 4, 6, 16, 7, 4, 3, 16, 15, 8, 9, 16, 18, 8, 3, 16, 0, 8, 9, 16, 3, 8, 3, 16, 18, 0, 6, 14, 20, 0, 2, 14, 0, 0, 6, 14, 2, 0, 2, 14, 15, 0, 6, 22, 17, 0, 2, 22, 3, 0, 6, 22, 5, 0, 2, 22, 12, 2, 12, 20, 16, 2, 4, 20, 0, 2, 12, 20, 4, 2, 4, 20, 11, 6, 4, 9, 11, 6, 2, 9, 9, 0, 6, 16, 12, 0, 3, 16, 12, 1, 3, 12, 12, 7, 3, 6, 3, 4, 18, 6, 3, 4, 9, 3, 12, 7, 9, 3, 5, 5, 16, 8, 13, 5, 8, 4, 5, 9, 8, 4, 0, 13, 10, 6, 0, 15, 10, 2, 8, 14, 9, 6, 8, 16, 9, 2, 6, 2, 9, 6, 9, 2, 3, 6, 14, 1, 10, 8, 19, 1, 5, 4, 14, 5, 5, 4, 9, 1, 3, 12, 9, 7, 3, 6, 6, 4, 12, 9, 6, 7, 12, 3, 6, 5, 12, 6, 10, 5, 4, 6, 1, 1, 8, 5, 5, 1, 4, 5, 12, 12, 6, 8, 12, 16, 6, 4, 3, 12, 12, 6, 3, 14, 12, 2, 9, 18, 12, 6, 15, 18, 6, 3, 9, 21, 6, 3, 4, 13, 6, 6, 4, 16, 6, 3, 11, 3, 7, 18, 11, 12, 7, 9, 3, 9, 18, 3, 9, 9, 6, 3, 5, 3, 19, 2, 5, 4, 19, 1, 4, 2, 12, 6, 4, 2, 6, 3, 10, 5, 6, 3, 9, 6, 6, 9, 11, 6, 2, 9, 8, 6, 6, 9, 10, 6, 2, 9, 16, 9, 5, 15, 16, 14, 5, 5, 3, 9, 5, 15, 3, 14, 5, 5, 6, 6, 14, 6, 13, 6, 7, 3, 6, 9, 7, 3, 8, 6, 3, 14, 8, 13, 3, 7, 0, 16, 24, 5, 8, 16, 8, 5, 0, 20, 20, 3, 10, 20, 10, 3, 5, 10, 18, 2, 5, 11, 18, 1, 0, 6, 6, 10, 2, 6, 2, 10, 2, 1, 20, 3, 2, 2, 20, 1, 9, 13, 6, 11, 11, 13, 2, 11, 9, 15, 6, 8, 9, 19, 6, 4, 9, 12, 6, 9, 9, 15, 6, 3, 5, 11, 18, 2, 5, 12, 18, 1, 2, 6, 15, 6, 2, 8, 15, 2, 6, 0, 18, 3, 6, 1, 18, 1, 5, 0, 3, 18, 6, 0, 1, 18, 18, 3, 6, 10, 20, 3, 2, 10, 0, 3, 6, 10, 2, 3, 2, 10, 10, 5, 8, 9, 10, 5, 4, 9, 6, 5, 8, 9, 10, 5, 4, 9, 3, 2, 20, 3, 3, 3, 20, 1, 5, 2, 13, 4, 5, 4, 13, 2, 17, 0, 7, 14, 17, 7, 7, 7, 0, 0, 7, 14, 0, 7, 7, 7, 9, 11, 10, 6, 9, 11, 5, 6, 5, 11, 10, 6, 10, 11, 5, 6, 11, 6, 3, 18, 11, 12, 3, 6, 0, 16, 18, 3, 0, 17, 18, 1, 6, 16, 18, 3, 6, 17, 18, 1, 4, 6, 9, 10, 4, 11, 9, 5, 9, 7, 15, 4, 9, 9, 15, 2, 5, 6, 12, 6, 5, 6, 6, 3, 11, 9, 6, 3, 6, 1, 12, 9, 6, 4, 12, 3, 7, 9, 6, 12, 7, 9, 3, 6, 10, 15, 3, 6, 11, 5, 13, 6, 11, 7, 13, 2, 1, 11, 22, 13, 12, 11, 11, 13, 18, 8, 6, 6, 18, 11, 6, 3, 0, 8, 6, 6, 0, 11, 6, 3, 0, 6, 24, 3, 0, 7, 24, 1, 0, 5, 10, 6, 0, 7, 10, 2, 6, 7, 18, 3, 6, 8, 18, 1, 0, 0, 10, 6, 0, 2, 10, 2, 19, 0, 3, 19, 20, 0, 1, 19, 4, 6, 12, 16, 4, 6, 6, 8, 10, 14, 6, 8, 19, 6, 4, 18, 21, 6, 2, 9, 19, 15, 2, 9, 1, 6, 4, 18, 1, 6, 2, 9, 3, 15, 2, 9, 3, 21, 18, 3, 3, 22, 18, 1, 0, 19, 9, 4, 0, 21, 9, 2, 12, 18, 12, 6, 18, 18, 6, 3, 12, 21, 6, 3, 7, 18, 9, 4, 7, 20, 9, 2, 12, 16, 10, 8, 17, 16, 5, 4, 12, 20, 5, 4, 2, 16, 10, 8, 2, 16, 5, 4, 7, 20, 5, 4, 14, 0, 10, 12, 19, 0, 5, 6, 14, 6, 5, 6, 0, 0, 10, 12, 0, 0, 5, 6, 5, 6, 5, 6, 15, 14, 9, 6, 15, 16, 9, 2, 0, 14, 9, 6, 0, 16, 9, 2, 14, 14, 10, 6, 14, 16, 10, 2, 0, 14, 10, 6, 0, 16, 10, 2, 5, 18, 18, 2, 5, 19, 18, 1, 0, 18, 18, 3, 0, 19, 18, 1, 3, 5, 18, 12, 12, 5, 9, 6, 3, 11, 9, 6, 5, 3, 7, 9, 5, 6, 7, 3, 4, 0, 19, 15, 4, 5, 19, 5, 3, 0, 16, 4, 3, 2, 16, 2, 4, 12, 16, 12, 4, 12, 8, 12, 4, 3, 12, 15, 10, 3, 6, 15, 16, 4, 2, 19, 16, 4, 1, 19, 6, 4, 2, 19, 7, 4, 1, 19, 13, 14, 8, 10, 17, 14, 4, 5, 13, 19, 4, 5, 3, 14, 8, 10, 3, 14, 4, 5, 7, 19, 4, 5, 12, 6, 3, 18, 12, 12, 3, 6, 5, 11, 12, 6, 5, 11, 6, 3, 11, 14, 6, 3, 10, 5, 8, 10, 14, 5, 4, 5, 10, 10, 4, 5, 6, 4, 12, 10, 6, 4, 6, 5, 12, 9, 6, 5, 6, 8, 18, 10, 15, 8, 9, 5, 6, 13, 9, 5, 0, 8, 18, 10, 0, 8, 9, 5, 9, 13, 9, 5, 12, 6, 3, 18, 12, 12, 3, 6, 0, 14, 18, 3, 0, 15, 18, 1, 12, 6, 3, 18, 12, 12, 3, 6, 9, 6, 3, 18, 9, 12, 3, 6, 6, 14, 18, 3, 6, 15, 18, 1, 0, 5, 18, 3, 0, 6, 18, 1, 2, 5, 22, 3, 2, 6, 22, 1, 0, 0, 21, 10, 7, 0, 7, 10, 6, 3, 18, 17, 12, 3, 6, 17, 0, 3, 18, 17, 6, 3, 6, 17, 0, 12, 24, 11, 8, 12, 8, 11, 4, 10, 16, 6, 4, 13, 16, 3, 12, 8, 6, 8, 12, 12, 6, 4, 6, 14, 8, 7, 10, 14, 4, 7, 15, 10, 6, 14, 18, 10, 3, 7, 15, 17, 3, 7, 3, 10, 6, 14, 3, 10, 3, 7, 6, 17, 3, 7, 6, 12, 18, 2, 6, 13, 18, 1, 5, 8, 10, 6, 5, 10, 10, 2, 12, 11, 9, 4, 12, 13, 9, 2, 0, 11, 9, 6, 0, 13, 9, 2, 11, 2, 3, 18, 12, 2, 1, 18, 10, 2, 3, 18, 11, 2, 1, 18, 9, 12, 6, 10, 11, 12, 2, 10, 1, 10, 6, 9, 1, 13, 6, 3, 6, 9, 16, 6, 14, 9, 8, 3, 6, 12, 8, 3, 1, 8, 9, 6, 1, 10, 9, 2, 7, 7, 16, 6, 7, 9, 16, 2, 0, 0, 18, 3, 0, 1, 18, 1, 10, 0, 6, 9, 12, 0, 2, 9, 9, 5, 6, 6, 12, 5, 3, 6, 10, 6, 4, 18, 12, 6, 2, 9, 10, 15, 2, 9, 8, 0, 6, 9, 10, 0, 2, 9, 9, 1, 6, 9, 9, 4, 6, 3, 1, 0, 18, 9, 1, 3, 18, 3, 0, 3, 24, 3, 0, 4, 24, 1, 6, 14, 9, 4, 6, 16, 9, 2, 8, 9, 8, 10, 12, 9, 4, 5, 8, 14, 4, 5, 5, 2, 13, 9, 5, 5, 13, 3, 4, 4, 16, 9, 4, 7, 16, 3, 4, 4, 14, 9, 4, 7, 14, 3, 8, 5, 9, 6, 8, 7, 9, 2, 1, 7, 16, 6, 1, 9, 16, 2, 10, 5, 13, 9, 10, 8, 13, 3, 1, 5, 13, 9, 1, 8, 13, 3, 0, 4, 24, 6, 12, 4, 12, 3, 0, 7, 12, 3, 1, 14, 10, 9, 1, 17, 10, 3, 5, 17, 18, 3, 5, 18, 18, 1, 0, 16, 18, 3, 0, 17, 18, 1, 9, 17, 9, 6, 9, 19, 9, 2, 1, 20, 22, 4, 1, 20, 11, 2, 12, 22, 11, 2, 8, 14, 8, 6, 8, 17, 8, 3, 8, 6, 8, 15, 8, 11, 8, 5, 5, 4, 18, 3, 5, 5, 18, 1, 9, 3, 5, 10, 9, 8, 5, 5, 6, 8, 12, 3, 6, 8, 6, 3, 2, 6, 18, 6, 2, 6, 9, 3, 11, 9, 9, 3, 10, 6, 4, 18, 12, 6, 2, 9, 10, 15, 2, 9, 7, 5, 6, 6, 10, 5, 3, 6, 14, 5, 2, 18, 14, 14, 2, 9, 8, 5, 2, 18, 8, 14, 2, 9, 9, 2, 10, 6, 9, 2, 5, 6, 3, 1, 18, 12, 12, 1, 9, 12, 5, 2, 17, 22, 5, 13, 17, 11, 4, 0, 12, 6, 4, 2, 12, 2, 6, 9, 16, 6, 14, 9, 8, 3, 6, 12, 8, 3, 9, 0, 5, 18, 9, 9, 5, 9, 12, 0, 6, 9, 14, 0, 2, 9, 6, 0, 6, 9, 8, 0, 2, 9, 9, 1, 6, 12, 11, 1, 2, 12, 5, 9, 13, 4, 5, 11, 13, 2, 5, 8, 19, 3, 5, 9, 19, 1, 9, 9, 6, 8, 9, 13, 6, 4, 11, 9, 4, 15, 11, 14, 4, 5, 2, 0, 6, 14, 2, 0, 3, 7, 5, 7, 3, 7, 15, 1, 6, 14, 18, 1, 3, 7, 15, 8, 3, 7, 3, 1, 6, 14, 3, 1, 3, 7, 6, 8, 3, 7, 3, 20, 18, 4, 12, 20, 9, 2, 3, 22, 9, 2, 5, 0, 4, 20, 5, 0, 2, 10, 7, 10, 2, 10, 16, 8, 8, 12, 20, 8, 4, 6, 16, 14, 4, 6, 0, 8, 8, 12, 0, 8, 4, 6, 4, 14, 4, 6, 13, 13, 10, 8, 18, 13, 5, 4, 13, 17, 5, 4, 1, 13, 10, 8, 1, 13, 5, 4, 6, 17, 5, 4, 15, 8, 4, 15, 15, 13, 4, 5, 5, 8, 4, 15, 5, 13, 4, 5, 6, 11, 16, 12, 6, 15, 16, 4, 2, 11, 16, 12, 2, 15, 16, 4, 14, 12, 7, 9, 14, 15, 7, 3, 10, 1, 3, 21, 10, 8, 3, 7, 13, 11, 9, 4, 13, 13, 9, 2, 3, 10, 17, 9, 3, 13, 17, 3, 13, 8, 8, 15, 13, 13, 8, 5, 3, 8, 8, 15, 3, 13, 8, 5, 11, 14, 10, 8, 16, 14, 5, 4, 11, 18, 5, 4, 0, 18, 22, 6, 0, 18, 11, 3, 11, 21, 11, 3, 0, 16, 24, 4, 0, 16, 12, 4, 6, 20, 12, 3, 12, 20, 6, 3, 18, 12, 6, 12, 21, 12, 3, 6, 18, 18, 3, 6, 0, 12, 6, 12, 0, 12, 3, 6, 3, 18, 3, 6, 15, 17, 9, 6, 15, 19, 9, 2, 1, 6, 22, 10, 1, 6, 11, 5, 12, 11, 11, 5, 15, 17, 9, 6, 15, 19, 9, 2, 0, 18, 18, 2, 0, 19, 18, 1, 3, 15, 19, 3, 3, 16, 19, 1, 0, 13, 18, 3, 0, 14, 18, 1, 15, 17, 9, 6, 15, 19, 9, 2, 0, 17, 9, 6, 0, 19, 9, 2, 12, 17, 9, 6, 12, 19, 9, 2, 3, 17, 9, 6, 3, 19, 9, 2, 16, 2, 3, 20, 17, 2, 1, 20, 0, 13, 24, 8, 0, 17, 24, 4, 9, 1, 6, 22, 12, 1, 3, 11, 9, 12, 3, 11}; +const int eye_window_w=20; +const int eye_window_h=20; +const int eye_n_stages=24; +const uint8_t eye_stages_array[]={6, 12, 9, 16, 23, 27, 28, 36, 47, 48, 55, 32, 30, 44, 53, 51, 44, 72, 66, 69, 59, 88, 58, 93}; +const int16_t eye_stages_thresh_array[]={-372, -321, -351, -329, -311, -330, -296, -313, -329, -286, -292, -288, -300, -265, -268, -284, -320, -286, -278, -266, -270, -250, -259, -250}; +const int16_t eye_tree_thresh_array[]={531, -189, -66, -187, -220, 139, -889, 49, -73, 91, -374, 118, 32, 83, -48, -3, 0, -33, -483, -140, -88, -89, -117, -46, -92, -7, 0, 814, -147, -317, 0, 1, -76, 0, -75, -53, 0, 0, -13, 7, -103, 5, -30, -122, -514, 21, -33, -67, -8, -6, -20, -78, -50, 235, -39, -12, -17, -55, 0, -10, -289, 377, -36, 0, -12, -10, -196, 357, -69, -118, 10, -98, -134, -62, -1, 3, -43, -30, -437, 66, -84, 69, -23, 0, 32, -10, -91, -31, -4, 78, 23, -39, 45, -71, 125, -90, 0, -28, -28, -57, -196, 0, 4, -10, 0, 0, 1076, -96, -16, 0, -32, -6, -1, 0, 53, -53, 12, -24, -9, 64, 0, -114, 528, 99, -10, -14, 0, -66, -18, -89, -11, -18, 116, 340, -50, -19, -3, -80, 4, -106, -5, -204, -12, 19, 0, 0, -54, 9, -51, -11, 81, 19, -28, -53, -5, 11, 1, -59, -545, -41, -32, -58, -24, 44, 0, -23, -91, -20, 0, -2, 6, -49, 43, -126, 46, -50, -24, -4, -30, 11, 0, -20, 306, 82, 181, -3, -10, 0, -150, 12, -10, -57, 3, -29, -78, -20, -11, -101, 82, 7, 5, -44, 0, 15, -418, -212, -174, 1, 99, -97, -28, -2, 21, -54, -27, -17, -44, 1, -10, 5, -20, 108, -13, -12, 125, -39, 0, 20, 1, -35, 3, 4, -97, 0, 100, -583, -82, 0, -11, 0, -28, -153, -65, -12, 4, -6, 17, -44, -17, 30, 33, -136, 80, 90, -17, -3, 3, 37, -16, 0, -12, -90, -30, 3, -19, -62, 20, -7, -50, -53, -5, 24, 2, -7, -2, -24, -13, -249, -396, 16, -37, -19, -17, -39, -71, -27, 1, -9, -30, -21, -19, 6, -19, -5, 9, -1, 20, -37, -28, 4, -154, -27, 2, -17, -282, 25, 43, -257, 14, -32, -14, -38, 0, 7, -22, 30, -63, 117, 0, 214, -354, -172, 0, 21, -3, 0, 20, -6, 11, 9, -3, 8, 46, -4, 13, 0, -16, -6, 57, 672, 3, -7, -6, 7, -553, -16, -11, -5, -14, 170, -37, 25, -4, 47, -36, 2, 5, -44, 5, 9, 11, -27, -197, 62, -24, 27, -32, 15, -4, 14, 26, 0, -15, 15, -100, 193, 27, -5, -20, 14, 8, 120, -3, -443, -73, 25, -6, 3, 0, -85, -25, 44, 0, 0, 5, -3, 4, -4, -4, 31, -572, 8, -11, 21, -5, -15, 24, -21, 20, -2, 21, -60, -169, 3, -229, 4, 11, -30, -6, 18, 9, -347, -20, -8, -42, -55, -345, 10, 64, 265, 1, 5, -23, -605, -67, 12, -149, 0, -15, -16, 81, 3, -4, 64, -4, -449, -37, 60, -5, -17, 14, -15, 15, -8, -9, 4, 7, 24, -113, 192, -32, -52, 96, 65, 32, 23, 9, 18, -6, 10, 9, 23, 12, -355, 21, -81, 15, 42, -82, -21, -8, -1, 0, -1, 26, 45, -4, 9, -45, -1, 94, -9, -3, 6, 3, -84, -84, 88, 26, -36, -38, -8, -7, -55, -79, -13, 1, -31, -34, 113, -224, 10, 0, 20, -427, -2, 13, 21, -10, -11, -97, 82, -88, 11, 11, 13, -14, 7, -1, 0, -1, 82, 150, 5, -62, 19, 0, -14, 4, 0, 560, -48, 16, -1, -19, -1, 42, -3, 5, 70, 10, 5, 19, -25, -13, 13, -23, 0, -16, 5, -4, -13, 101, -31, 32, -44, -14, -127, 4, 13, -114, -94, -16, 12, -44, -74, 2, 44, -1, -14, -80, -44, -17, 6, 64, -45, 36, -3, -12, 25, 84, 408, -26, -249, -12, -40, -338, 20, 25, -13, -10, 3, -156, 0, 6, 5, 40, -6, 8, -14, 1, -2, -1, 28, 52, -6, 497, -205, 128, 7, -23, 0, -809, 0, 0, 41, -29, 993, 2, 14, 0, -2, -27, -108, 40, 132, -11, -45, 25, 356, 4, -3, -56, -310, -11, -16, 71, 90, -11, -41, -28, -15, -13, -32, 0, -13, -3, -2, -1, 5, -211, -62, 282, 5, -10, -6, -6, -27, -57, -9, 81, 10, -2, -71, -8, 10, -229, -6, -46, 95, -9, -118, -59, -21, 20, -17, 0, 1, -3, 0, -15, -164, 0, 0, 298, 41, 9, -14, -5, -39, -39, -39, -17, -39, -37, -35, -36, -58, 106, 0, 0, 28, -50, -15, 11, -31, 1, 13, -68, -26, 1, 61, 1, -1, 1, 33, -93, 0, -52, -4, 4, -183, 33, -47, 345, 58, 6, 40, -37, -1, 807, -29, 35, 36, 234, 15, -5, 4, -7, 44, 0, -22, -12, 26, 48, 55, -10, 6, 10, -31, 20, -11, 12, 12, 914, -391, 0, 0, -26, -4, -121, -53, -5, -14, -6, -25, -136, -93, -353, -8, 160, 14, 2, 2, -89, -13, 9, -1, 14, -89, 0, -6, 31, 15, 24, -7, 67, 19, -127, 2, 48, -30, -220, 63, 6, -1, 11, -269, 5, 3, 97, 112, -1, -5, -38, 31, 0, -6, 17, 0, -32, 700, -6, -43, -23, -76, -3, 43, 144, 3, 378, -12, -354, 0, -149, 10, -18, -21, 18, 59, 0, -24, -24, -45, -240, -18, -38, 5, 20, -31, -1594, -410, 3, -14, 34, 9, 30, 13, -320, 0, -39, -291, 1, -1, -16, 13, -22, -1, -5, -1, -29, -69, 122, 11, -7, 20, 336, -1, -7, -20, -34, -445, -39, -111, -26, -49, -687, -54, 314, 20, 13, -7, -1, 1, -1, 322, -3, 98, -6, -3, 1, 8, 109, -8, 23, 0, 0, 17, -8, 89, -60, -14, 7, -5, -16, -79, 53, -2, -54, 14, 25, 6, 1, -6, 14, -19, -47, -112, -216, -1222, -1, 0, 0, 8, -28, 33, 0, 6, -1, -126, -2, -48, 29, -8, 14, -551, -9, 17, -30, 2, 0, 27, 0, -832, 38, -9, 0, 61, 3, -17, 2, -68, -1, 1, -82, -59, -86, 86, 4, 0, -148, -37, -8, -93, 104, -10, -15, -68, 2, -150, 5, -34, 265, 124, 10, -28, -4, 0, -1, -7, -54, -7, -26, 0, 89, -112, 0, 287, -294, -441, 5, 279, -27, -2, -238, -25, -55, 5, 0, -317, 3, 4, -3, 341, -37, -1, 31, -510, 54, -27, -11, 3, -205, 30, -2, -7, -1, -290, -218, -82, 4, 0, 62, -852, 6, -216, -88, 0, 0, 0, -49, -72, -25, -3, -1, -69, 19, -42, -90, -28, -2, -9, -115, 8, -1, -68, 490, -1, -74, -1, 1, 3, 1764, 2, 0, -226}; +const int16_t eye_alpha1_array[]={-197, 146, 154, 164, 138, -59, 182, -72, 136, -44, 157, -31, 43, -32, 98, -163, -88, -162, 173, 171, 184, 170, 179, 151, 88, -203, -79, -135, 107, 122, -98, -67, 119, 75, 118, 106, -71, -69, 113, -41, 104, 21, 142, 91, 100, -112, 102, 107, -189, -194, 107, 120, 84, -94, -184, 91, 100, -150, -67, -200, 106, -33, 93, -76, 91, -139, 106, -99, 135, 91, 38, 143, 118, -189, -116, 24, 104, 103, 158, -33, -182, 32, -155, -68, -20, 94, -175, 60, -153, -40, -25, -155, -26, 80, -48, 93, 54, 101, 77, -195, 123, -64, 17, 109, -49, 77, -59, 49, 141, -61, 104, -147, 74, -50, -24, 120, -34, -199, 79, 16, -45, 116, -134, -38, 84, 121, -77, 58, 103, -175, -178, 64, 15, 16, -181, 131, 68, -172, 13, 144, -128, 45, 100, -30, -44, -59, 110, 18, 92, 72, 12, -27, -205, 47, 70, 23, -99, 96, 77, 94, 117, 114, 71, -38, -58, 116, -160, 67, -62, -109, -30, 71, 10, -160, 10, 51, 131, -185, 75, 11, 51, 75, -29, -26, -70, 62, -137, -53, 93, -33, -145, -124, -20, 114, -170, 44, -153, 59, -22, 12, -38, -148, -35, -26, 106, 84, 66, -88, -108, 81, 79, -153, -31, 86, -156, 64, 140, 21, 59, 15, -171, -18, -108, 41, -15, -155, -42, 11, -28, 103, 16, -24, -150, -38, -14, 38, 55, -43, -177, 48, 120, -194, 78, 67, -44, 91, 12, -163, 41, 8, 6, 85, -26, -63, 98, -160, 23, -18, 51, -71, 93, 100, 32, 18, 55, 93, -88, -158, -149, -152, -174, -24, 20, 60, -114, 111, 46, 120, 70, -31, 91, -105, -186, 141, -146, -118, -24, 58, -145, 89, -154, -26, 72, 59, 16, -97, -19, 36, 69, -39, -151, 65, 11, 63, -170, 6, -20, 71, -88, 61, -151, 47, -84, 25, 75, -37, -175, -37, -44, -17, 129, 116, -67, 19, -126, -54, -23, 65, -40, -38, 75, 12, -32, 63, 13, -55, 62, 72, 10, -125, -72, -183, -186, -79, 75, -170, -158, 57, -138, -16, -193, 23, 62, -30, -125, -55, -38, 109, 18, 12, -43, -202, -178, -27, 79, 20, -223, -29, 61, -60, -80, -70, -143, 10, 42, -27, -45, -138, 62, 17, -34, -73, -148, 101, 70, 5, -145, -38, -39, -161, 65, -25, 48, -38, 12, 46, -27, 69, -179, -23, 176, 24, 61, -29, -152, -118, -18, -189, -35, -140, -21, 53, -211, 21, 39, -52, -44, -152, 48, 8, -60, 59, 99, 46, -192, -184, -86, -30, -17, -19, 18, -43, 90, 110, 59, -34, -160, 42, -121, 46, -17, 14, -120, -31, 51, -221, 75, 8, 62, 55, 11, 38, -22, 44, 68, 11, -28, -39, 29, -10, -137, 59, -22, -31, -21, 9, -42, -31, 66, -27, -35, -19, -28, -94, -27, -136, -34, 22, 40, -175, 72, -94, -82, -83, -22, 18, 66, -21, 49, -81, -25, 116, -90, 25, 14, 80, 69, 10, -25, -134, 63, 68, 40, 104, 46, 74, -54, -116, 67, -25, 73, -28, -48, -19, 51, 45, 10, -31, 131, -129, 95, -44, -112, -32, -22, -21, -113, -28, -96, 19, -84, -15, -15, -90, 58, -14, -78, 98, -28, -51, -55, 111, -12, -97, 124, -91, 13, 115, -24, -20, -29, 17, -16, -181, 35, -33, 97, -79, 66, -14, 37, 85, -18, 118, -70, 57, 99, 61, -72, 8, 61, 99, 78, -35, 42, -161, -47, -6, -76, 67, -151, 41, -147, 15, -18, 100, 9, 45, -113, -13, 14, -9, 72, -130, -122, 103, -180, 7, -13, 23, 34, -17, 75, -42, -23, 16, 5, 65, 5, 69, -27, -76, -69, -14, 17, 52, -24, 72, -22, -29, 60, 45, 116, -60, 20, -10, 44, 13, -29, -10, 17, 87, 30, 52, -28, -25, 38, 131, -23, 7, -33, -128, -203, 78, 53, -168, -21, 16, 37, 126, 71, -148, -127, 88, 38, 58, -113, 35, -76, -41, -136, -200, -9, -24, 29, 45, 46, 64, 35, 102, -48, 12, -111, 168, 101, 15, -132, 38, -161, -14, 48, -139, -172, -110, -27, 61, -22, 11, -140, -28, 62, -161, 50, -23, 12, -23, 16, 33, 41, 156, -256, 256, 72, -256, 222, 116, 34, 82, -33, -31, 30, -16, 140, 61, -32, 76, 15, 12, 182, -89, 23, -13, 16, 45, 26, -16, 135, 20, -89, 59, 30, -164, 11, 106, 13, 11, 21, -14, 115, 46, -7, -228, -18, 18, 13, 8, 79, -33, 74, 8, 41, -111, 97, -40, -10, -13, 28, 17, 16, 50, 21, -121, -12, -26, -3, -189, -32, -42, 86, 57, 66, 48, 51, 61, -125, 79, -126, 83, -173, 57, 29, -29, -38, 25, -81, -97, -24, 56, 9, 75, 58, -129, 11, -22, -19, -90, -20, 10, 49, -53, 10, 40, -142, -10, 17, 45, 7, -118, -24, 29, -8, -16, -86, 37, -131, 5, 60, 49, -24, -80, -169, 8, 55, 62, -122, -153, 62, -35, -19, -24, -70, 58, 122, -57, 70, -11, 60, 38, -14, 9, -44, 79, -81, 62, -194, -116, 37, -25, 8, 23, -170, -147, 22, 66, -28, 9, -9, -25, -196, -34, 64, -102, 10, -66, 61, -18, 128, 63, 81, -69, 70, -89, 8, 9, 63, -21, -4, -63, 73, 32, 60, -66, -181, -149, -152, 33, -145, 29, -8, -13, -19, 50, -73, 17, 42, 6, 35, 7, 67, 39, 14, -23, 15, 75, -16, -43, 16, -25, 49, 9, -112, 50, 5, 25, 29, 121, -65, 49, 113, -34, -13, -51, 26, 72, -21, 50, -143, 127, 43, -165, 64, -22, 29, -12, -138, 6, -30, -15, -88, 51, -80, 51, -9, -72, -27, 127, 43, 22, 63, 17, -21, 7, -40, -177, -22, 54, 38, -8, 12, 55, -19, -173, 58, 14, 49, -175, 96, 7, -17, 46, -214, 106, -50, 62, -73, 74, 54, 67, 5, 138, -33, -242, -18, -21, -33, -164, 36, 40, -85, 43, -149, 36, 70, 33, -6, -61, -34, -14, -96, -95, 20, -9, 49, 33, -77, 56, -99, -24, -61, 143, 14, -15, 60, 13, -100, -79, -11, -209, 9, -91, 64, -18, -161, -17, -92, 36, 55, 115, -173, -111, -6, 46, -51, 171, -19, 63, -74, -56, -29, 40, 31, -89, 33, 44, -72, 100, -8, -145, -116, 40, -36, 42, 69, 10, 41, -109, -4, 40, 49, 20, 25, -18, -7, 29, -43, 116}; +const int16_t eye_alpha2_array[]={174, -125, -80, -39, -52, 123, -151, 151, -58, 162, -43, 190, -167, 211, -53, 33, 58, 33, -128, -91, -46, -70, -50, -56, -98, 40, 90, 90, -100, -64, 81, 83, -38, -64, -33, -45, 68, 61, -36, 109, -46, -204, -30, -98, -76, 49, -59, -58, 32, 22, -45, -36, -55, 62, 16, -42, -30, 22, 54, 20, -35, 129, -35, 52, -39, 26, -87, 61, -44, -57, -167, -37, -27, 14, 24, -140, -31, -41, -18, 95, 13, -79, 20, 37, 145, -34, 13, -45, 19, 76, 107, 18, 107, -86, 137, -41, -72, -33, -52, 25, -33, 62, -168, -33, 74, -50, 60, -72, -24, 55, -32, 24, -43, 70, 103, -22, 78, 14, -38, -185, 59, -46, 41, 108, -44, -18, 40, -52, -34, 20, 12, -41, -170, -137, 14, -22, -45, 18, -142, -18, 22, -57, -25, 108, 57, 42, -25, -147, -29, -31, -206, 86, 11, -45, -32, -109, 22, -63, -57, -45, -33, -26, -39, 102, 55, -32, 21, -37, 42, 23, 77, -31, -189, 12, -183, -49, -15, 12, -30, -178, -40, -29, 77, 104, 31, -34, 16, 41, -23, 64, 13, 14, 103, -16, 12, -43, 14, -30, 92, -161, 48, 11, 50, 64, -42, -53, -40, 35, 34, -42, -34, 22, 98, -25, 12, -36, -15, -113, -35, -126, 11, 113, 17, -46, 136, 13, 49, -147, 59, -15, -114, 77, 11, 45, 125, -48, -30, 42, 10, -38, -14, 8, -22, -23, 39, -19, -151, 8, -38, -192, -183, -18, 136, 53, -37, 30, -141, 142, -53, 35, -19, -28, -61, -112, -36, -21, 23, 12, 11, 10, 8, 77, -90, -30, 14, -17, -36, -15, -24, 53, -19, 17, 9, -13, 12, 12, 66, -26, 12, -21, 11, 59, -25, -29, -108, 15, 85, -43, -23, 50, 10, -24, -124, -24, 9, -186, 76, -75, 53, -49, 31, -63, 28, -127, -33, 85, 25, 79, 54, 126, -19, -23, 41, -138, 15, 36, 91, -36, 53, 68, -27, -183, 61, -34, -133, 32, -29, -27, -188, 45, 61, 22, 24, 46, -56, 21, 13, -51, 17, 158, 13, -113, -47, 94, 27, 47, 74, -26, -164, -185, 60, 11, 10, 93, -29, -119, 9, 84, -45, 63, 47, 42, 28, -148, -64, 100, 69, 12, -33, -180, 56, 34, 9, -20, -34, -205, 10, 48, 49, 9, -27, 77, -35, 55, -164, -35, 62, -30, 10, 79, -10, -73, -28, 62, 12, 13, 99, 10, 48, 12, 81, -31, 8, -143, -69, 51, 54, 12, -40, -175, 28, -35, -14, -45, 6, 8, 18, 57, 80, 104, -88, 39, -21, -14, -27, 67, 11, -42, 12, -36, 93, -114, 12, 52, -32, 7, -21, -198, -29, -35, -139, -38, 65, -36, -20, -120, 52, 40, -56, 137, 11, -26, 78, 45, 82, -174, 106, 103, -51, 93, 76, 124, 74, 18, 81, 18, 58, -122, -45, 9, -25, 20, 19, 22, 81, -103, -30, 79, -38, 22, 65, -13, 20, -55, -103, -20, -19, -152, 60, 11, -24, -23, -40, -16, -35, -20, 28, 13, -24, 53, -19, 55, 36, 75, -27, -30, -139, 164, -28, 29, -37, 122, 34, 119, 94, 132, 23, 100, 31, -104, 28, 143, 133, 27, -41, 123, 27, -23, 79, 39, 36, -19, 150, 23, -18, 27, -146, -21, 80, 92, 56, -94, 98, 9, -59, 58, -17, 21, -26, 122, -44, -42, 145, -23, 33, -38, -16, -29, 33, -131, -33, -20, -18, 48, -40, 10, 34, 156, 18, -20, 10, -32, 9, -77, 75, -12, -125, -28, 13, 95, -94, 148, -15, 9, 9, -12, 7, -135, 84, -55, -35, 85, -17, 31, 54, -92, -207, -17, -215, -19, 58, 16, 17, 93, -73, -25, 50, -17, 54, 42, -23, -27, -10, 19, -73, 122, -25, -82, 37, 136, -69, -14, -38, -67, 83, 81, -50, -11, 88, -212, 52, 23, -1, -20, -32, 12, 92, -181, -42, -6, -19, 9, 12, -17, -36, -23, 14, -37, 19, 35, 10, 6, 182, 55, -46, -31, -29, -23, -44, -12, 38, -110, 10, -8, -11, -83, 9, -32, 8, 98, -25, 10, 5, 11, 49, -26, 52, -99, 9, 44, -20, 6, -26, 50, -86, 51, -68, -32, -27, -16, 0, 0, -39, 11, 0, -22, -66, -24, 67, 53, -59, 105, -10, -24, 49, -24, -107, -161, -10, 18, -63, 144, -93, -29, -51, 77, -10, -63, 14, -23, -47, 7, -110, -12, -97, -110, -63, 88, -10, -27, 170, 6, 72, -77, -100, -130, -16, 37, -17, -122, -29, 11, -14, 31, 117, 89, -42, -78, -71, -27, -63, 8, 109, 43, 236, 7, 33, 25, -39, -49, -39, -42, -31, -33, 26, -29, 13, -16, 6, -32, -68, 65, 42, -56, 21, 15, 73, -24, -148, -20, -25, 11, -105, 63, 72, 14, 60, -124, -26, 24, -159, -31, 10, 159, -78, -28, -226, 8, 73, -42, 153, 76, 16, -33, 8, -178, -22, -26, 65, 15, 6, -116, -21, -21, 11, 5, -18, 67, 99, 74, 20, -20, -1, 22, -25, 80, -22, -32, 93, -137, 28, -17, 16, -20, 7, 7, -28, 50, -101, -40, 4, 6, -41, -15, 40, -121, 126, 40, 5, 28, -17, 10, -84, 17, -18, 61, -7, -14, -10, 14, -13, 11, -110, -94, -15, 56, 176, 16, -13, -32, -18, 15, 4, 5, 5, -25, 8, -30, 153, 59, 45, -20, 12, -57, -22, -142, -30, -118, -14, -28, -72, 45, -62, -13, 63, 24, -62, 46, -24, -115, 8, -19, -184, -38, -32, -30, 41, -44, -23, 46, 92, 30, -61, -17, 104, -29, 17, -15, -33, 10, -22, 61, -48, 147, 12, -158, 41, 90, 16, -25, 15, -22, 136, 18, 63, -10, -28, -70, -24, -86, 62, -176, 30, 7, 61, -33, -32, 129, -102, -22, 67, 8, -23, -91, -24, 6, -13, -228, 69, -25, 6, -13, 26, -37, 27, -26, -35, -11, -110, -11, 42, 5, 68, 70, 45, 10, -29, -28, 11, -27, 7, -28, -13, -28, 128, 15, 25, 63, 10, 10, -44, 104, -21, -36, 13, -19, 10, 38, 16, -6, -71, 63, -16, -79, 8, 12, 93, 4, -85, 10, -14, 58, 7, 71, 10, -27, -17, -10, 4, 8, 130, -21, 17, -5, 44, -16, 13, 16, 29, -21, -28, 8, -27, -22, 13, -10, 105, 7, 6, -23, 26, -21, -14, -81, -21, 7, 204, -21, -18, -53, -38, 55, 146, -32, 20, -7}; +const int8_t eye_num_rectangles_array[]={2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 3, 2, 3, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 3, 2, 3, 2, 3, 2, 2, 3, 2, 3, 2, 3, 2, 3, 3, 2, 3, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 3, 2, 2, 3, 3, 3, 2, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 2, 2, 3, 3, 2, 3, 2, 2, 2, 3, 3, 3, 2, 3, 3, 2, 2, 2, 3, 2, 3, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 3, 3, 2, 2, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 3, 2, 3, 3, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 2, 2, 3, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}; +const int8_t eye_weights_array[]={-1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, 2, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 2, -1, 2, -1, 3, -1, 2, 2, -1, 2, 2, -1, 2, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, 2, -1, 2, -1, 3, -1, 3, -1, 2, 2, -1, 3, -1, 3, -1, 2, -1, 3, -1, 3, -1, 3, -1, 2, -1, 2, -1, 3, -1, 2, -1, 2}; +const int8_t eye_rectangles_array[]={0, 8, 20, 12, 0, 14, 20, 6, 9, 1, 4, 15, 9, 6, 4, 5, 6, 10, 9, 2, 9, 10, 3, 2, 7, 0, 10, 9, 7, 3, 10, 3, 12, 2, 2, 18, 12, 8, 2, 6, 8, 6, 8, 6, 8, 9, 8, 3, 2, 0, 17, 18, 2, 6, 17, 6, 10, 10, 1, 8, 10, 14, 1, 4, 7, 10, 9, 2, 10, 10, 3, 2, 5, 1, 6, 6, 5, 3, 6, 2, 3, 1, 15, 9, 3, 4, 15, 3, 6, 3, 9, 6, 6, 5, 9, 2, 8, 17, 6, 3, 10, 17, 2, 3, 9, 10, 9, 1, 12, 10, 3, 1, 1, 7, 6, 11, 3, 7, 2, 11, 9, 18, 3, 1, 10, 18, 1, 1, 16, 16, 1, 2, 16, 17, 1, 1, 9, 17, 6, 3, 11, 17, 2, 3, 8, 0, 5, 18, 8, 6, 5, 6, 6, 7, 9, 7, 9, 7, 3, 7, 14, 6, 6, 10, 16, 6, 2, 10, 9, 8, 9, 5, 12, 8, 3, 5, 3, 7, 9, 6, 6, 7, 3, 6, 1, 7, 6, 6, 3, 7, 2, 6, 16, 0, 4, 18, 16, 6, 4, 6, 0, 17, 3, 3, 0, 18, 3, 1, 16, 0, 2, 1, 17, 0, 1, 1, 0, 8, 20, 12, 0, 14, 20, 6, 6, 6, 9, 8, 9, 6, 3, 8, 5, 3, 12, 9, 5, 6, 12, 3, 4, 16, 1, 2, 4, 17, 1, 1, 18, 10, 2, 1, 19, 10, 1, 1, 9, 8, 6, 5, 11, 8, 2, 5, 0, 0, 2, 1, 1, 0, 1, 1, 6, 8, 6, 6, 8, 8, 2, 6, 11, 7, 6, 7, 13, 7, 2, 7, 19, 14, 1, 2, 19, 15, 1, 1, 6, 17, 1, 2, 6, 18, 1, 1, 14, 7, 2, 7, 15, 7, 1, 7, 6, 8, 2, 4, 7, 8, 1, 4, 5, 8, 12, 6, 5, 10, 12, 2, 2, 17, 1, 3, 2, 18, 1, 1, 6, 7, 3, 6, 7, 7, 1, 6, 6, 7, 9, 12, 9, 7, 3, 12, 6, 2, 11, 12, 6, 6, 11, 4, 1, 12, 5, 8, 1, 16, 5, 4, 14, 7, 6, 7, 16, 7, 2, 7, 10, 8, 6, 6, 12, 8, 2, 6, 16, 18, 4, 2, 16, 19, 4, 1, 18, 17, 2, 3, 18, 18, 2, 1, 9, 7, 3, 7, 10, 7, 1, 7, 5, 6, 6, 8, 7, 6, 2, 8, 2, 6, 6, 11, 4, 6, 2, 11, 8, 10, 12, 8, 8, 14, 12, 4, 7, 17, 6, 3, 9, 17, 2, 3, 10, 9, 3, 3, 11, 9, 1, 3, 8, 8, 3, 6, 9, 8, 1, 6, 7, 0, 6, 5, 9, 0, 2, 5, 6, 17, 1, 3, 6, 18, 1, 1, 0, 18, 4, 2, 0, 19, 4, 1, 4, 1, 11, 9, 4, 4, 11, 3, 3, 1, 14, 9, 3, 4, 14, 3, 0, 9, 6, 4, 2, 9, 2, 4, 18, 13, 1, 2, 18, 14, 1, 1, 13, 5, 3, 11, 14, 5, 1, 11, 0, 18, 8, 2, 0, 18, 4, 1, 4, 19, 4, 1, 5, 8, 12, 5, 9, 8, 4, 5, 4, 7, 11, 10, 4, 12, 11, 5, 14, 9, 6, 4, 16, 9, 2, 4, 0, 7, 6, 8, 3, 7, 3, 8, 0, 16, 3, 3, 0, 17, 3, 1, 7, 11, 12, 1, 11, 11, 4, 1, 4, 8, 9, 4, 7, 8, 3, 4, 5, 16, 6, 4, 7, 16, 2, 4, 18, 17, 1, 3, 18, 18, 1, 1, 18, 17, 1, 3, 18, 18, 1, 1, 4, 9, 4, 10, 4, 9, 2, 5, 6, 14, 2, 5, 4, 8, 6, 4, 6, 8, 2, 4, 10, 2, 2, 18, 10, 8, 2, 6, 0, 5, 8, 6, 0, 5, 4, 3, 4, 8, 4, 3, 6, 0, 6, 5, 8, 0, 2, 5, 18, 0, 2, 14, 18, 7, 2, 7, 8, 18, 4, 2, 10, 18, 2, 2, 1, 17, 6, 3, 1, 18, 6, 1, 11, 8, 3, 5, 12, 8, 1, 5, 11, 8, 3, 4, 12, 8, 1, 4, 11, 0, 6, 5, 13, 0, 2, 5, 1, 7, 6, 7, 3, 7, 2, 7, 0, 13, 1, 3, 0, 14, 1, 1, 3, 2, 9, 6, 3, 4, 9, 2, 8, 6, 9, 2, 8, 7, 9, 1, 0, 14, 3, 6, 0, 16, 3, 2, 1, 11, 6, 4, 3, 11, 2, 4, 6, 9, 9, 3, 9, 9, 3, 3, 6, 0, 9, 6, 6, 2, 9, 2, 8, 5, 6, 6, 8, 7, 6, 2, 1, 12, 2, 1, 2, 12, 1, 1, 10, 10, 6, 2, 12, 10, 2, 2, 13, 8, 6, 6, 15, 8, 2, 6, 6, 16, 6, 4, 8, 16, 2, 4, 8, 0, 9, 9, 8, 3, 9, 3, 18, 17, 1, 3, 18, 18, 1, 1, 18, 17, 1, 3, 18, 18, 1, 1, 7, 10, 3, 3, 8, 10, 1, 3, 9, 14, 2, 2, 9, 14, 1, 1, 10, 15, 1, 1, 9, 14, 2, 2, 9, 14, 1, 1, 10, 15, 1, 1, 0, 8, 19, 12, 0, 14, 19, 6, 7, 6, 9, 14, 10, 6, 3, 14, 13, 8, 3, 4, 14, 8, 1, 4, 4, 17, 1, 3, 4, 18, 1, 1, 4, 9, 6, 3, 6, 9, 2, 3, 2, 18, 5, 2, 2, 19, 5, 1, 7, 8, 2, 2, 7, 8, 1, 1, 8, 9, 1, 1, 7, 8, 2, 2, 7, 8, 1, 1, 8, 9, 1, 1, 5, 10, 13, 2, 5, 11, 13, 1, 10, 8, 1, 9, 10, 11, 1, 3, 15, 8, 2, 12, 15, 8, 1, 6, 16, 14, 1, 6, 4, 0, 3, 5, 5, 0, 1, 5, 12, 6, 3, 7, 13, 6, 1, 7, 7, 16, 6, 4, 9, 16, 2, 4, 9, 16, 2, 1, 10, 16, 1, 1, 6, 10, 9, 2, 9, 10, 3, 2, 0, 6, 15, 14, 0, 13, 15, 7, 9, 1, 5, 6, 9, 3, 5, 2, 3, 9, 3, 4, 4, 9, 1, 4, 5, 7, 3, 6, 6, 7, 1, 6, 17, 16, 1, 2, 17, 17, 1, 1, 9, 8, 6, 12, 11, 8, 2, 12, 6, 10, 6, 1, 8, 10, 2, 1, 7, 17, 9, 3, 10, 17, 3, 3, 14, 18, 6, 2, 14, 19, 6, 1, 9, 5, 3, 14, 10, 5, 1, 14, 8, 16, 9, 4, 11, 16, 3, 4, 0, 0, 4, 14, 0, 7, 4, 7, 8, 1, 6, 3, 10, 1, 2, 3, 6, 8, 3, 4, 7, 8, 1, 4, 4, 8, 3, 4, 5, 8, 1, 4, 5, 1, 6, 5, 7, 1, 2, 5, 1, 18, 1, 2, 1, 19, 1, 1, 7, 0, 6, 6, 7, 2, 6, 2, 0, 18, 4, 2, 0, 19, 4, 1, 12, 3, 8, 12, 12, 7, 8, 4, 12, 9, 3, 4, 13, 9, 1, 4, 12, 8, 3, 5, 13, 8, 1, 5, 16, 0, 2, 1, 17, 0, 1, 1, 5, 17, 1, 3, 5, 18, 1, 1, 10, 2, 3, 6, 10, 4, 3, 2, 4, 17, 2, 3, 4, 18, 2, 1, 12, 7, 1, 9, 12, 10, 1, 3, 7, 6, 3, 9, 8, 6, 1, 9, 17, 13, 3, 6, 17, 15, 3, 2, 7, 7, 3, 8, 8, 7, 1, 8, 5, 0, 3, 5, 6, 0, 1, 5, 4, 6, 9, 8, 7, 6, 3, 8, 2, 9, 3, 3, 3, 9, 1, 3, 16, 18, 4, 2, 16, 19, 4, 1, 17, 10, 3, 10, 17, 15, 3, 5, 8, 9, 6, 4, 10, 9, 2, 4, 5, 2, 10, 12, 5, 6, 10, 4, 6, 9, 6, 3, 8, 9, 2, 3, 11, 7, 3, 7, 12, 7, 1, 7, 12, 8, 6, 4, 14, 8, 2, 4, 14, 8, 6, 5, 16, 8, 2, 5, 12, 12, 2, 4, 12, 14, 2, 2, 3, 15, 1, 2, 3, 16, 1, 1, 12, 7, 3, 4, 13, 7, 1, 4, 10, 0, 6, 6, 12, 0, 2, 6, 10, 6, 3, 8, 11, 6, 1, 8, 16, 17, 1, 2, 16, 18, 1, 1, 16, 16, 1, 3, 16, 17, 1, 1, 11, 11, 1, 2, 11, 12, 1, 1, 3, 7, 6, 9, 5, 7, 2, 9, 4, 18, 9, 1, 7, 18, 3, 1, 0, 11, 4, 9, 0, 14, 4, 3, 9, 17, 6, 3, 11, 17, 2, 3, 7, 8, 6, 12, 9, 8, 2, 12, 6, 8, 3, 4, 7, 8, 1, 4, 3, 17, 1, 3, 3, 18, 1, 1, 11, 9, 6, 4, 13, 9, 2, 4, 6, 1, 3, 2, 7, 1, 1, 2, 1, 0, 2, 1, 2, 0, 1, 1, 1, 0, 2, 14, 1, 0, 1, 7, 2, 7, 1, 7, 5, 5, 11, 8, 5, 9, 11, 4, 9, 3, 5, 6, 9, 5, 5, 2, 7, 9, 5, 10, 7, 14, 5, 5, 15, 10, 2, 2, 16, 10, 1, 2, 0, 18, 8, 2, 0, 19, 8, 1, 7, 17, 1, 3, 7, 18, 1, 1, 7, 2, 11, 6, 7, 4, 11, 2, 8, 3, 9, 3, 8, 4, 9, 1, 0, 9, 2, 2, 0, 10, 2, 1, 0, 5, 3, 6, 0, 7, 3, 2, 6, 7, 2, 2, 6, 7, 1, 1, 7, 8, 1, 1, 7, 6, 3, 6, 8, 6, 1, 6, 12, 1, 6, 4, 14, 1, 2, 4, 9, 11, 6, 8, 11, 11, 2, 8, 17, 15, 3, 3, 17, 16, 3, 1, 6, 6, 3, 9, 6, 9, 3, 3, 0, 5, 8, 6, 0, 5, 4, 3, 4, 8, 4, 3, 0, 6, 1, 3, 0, 7, 1, 1, 17, 0, 2, 6, 18, 0, 1, 6, 10, 17, 6, 3, 12, 17, 2, 3, 13, 15, 2, 2, 13, 15, 1, 1, 14, 16, 1, 1, 4, 0, 12, 3, 4, 1, 12, 1, 5, 3, 10, 9, 5, 6, 10, 3, 7, 7, 9, 7, 10, 7, 3, 7, 5, 8, 9, 6, 8, 8, 3, 6, 0, 16, 6, 2, 0, 17, 6, 1, 12, 6, 7, 14, 12, 13, 7, 7, 13, 7, 6, 8, 15, 7, 2, 8, 2, 10, 6, 3, 4, 10, 2, 3, 18, 17, 1, 3, 18, 18, 1, 1, 7, 1, 6, 2, 7, 2, 6, 1, 6, 0, 6, 4, 6, 2, 6, 2, 8, 18, 6, 2, 10, 18, 2, 2, 7, 6, 5, 2, 7, 7, 5, 1, 6, 7, 3, 6, 7, 7, 1, 6, 18, 18, 2, 2, 18, 18, 1, 1, 19, 19, 1, 1, 16, 8, 3, 7, 17, 8, 1, 7, 0, 16, 2, 3, 0, 17, 2, 1, 5, 19, 6, 1, 7, 19, 2, 1, 9, 5, 6, 6, 9, 7, 6, 2, 0, 10, 2, 4, 0, 12, 2, 2, 0, 9, 4, 3, 2, 9, 2, 3, 1, 10, 6, 9, 3, 10, 2, 9, 9, 0, 6, 2, 11, 0, 2, 2, 14, 1, 2, 1, 15, 1, 1, 1, 0, 8, 1, 4, 0, 10, 1, 2, 15, 6, 2, 2, 15, 6, 1, 1, 16, 7, 1, 1, 7, 5, 3, 6, 8, 5, 1, 6, 19, 17, 1, 3, 19, 18, 1, 1, 7, 10, 3, 1, 8, 10, 1, 1, 12, 1, 6, 6, 14, 1, 2, 6, 15, 5, 2, 1, 16, 5, 1, 1, 8, 2, 7, 4, 8, 4, 7, 2, 4, 0, 14, 15, 4, 5, 14, 5, 7, 8, 6, 6, 9, 8, 2, 6, 11, 17, 1, 3, 11, 18, 1, 1, 12, 16, 2, 4, 12, 16, 1, 2, 13, 18, 1, 2, 10, 13, 2, 1, 11, 13, 1, 1, 11, 8, 3, 3, 12, 8, 1, 3, 2, 0, 6, 8, 4, 0, 2, 8, 3, 5, 6, 6, 3, 5, 3, 3, 6, 8, 3, 3, 10, 8, 3, 3, 11, 8, 1, 3, 5, 17, 4, 2, 5, 18, 4, 1, 8, 16, 5, 2, 8, 17, 5, 1, 0, 4, 3, 3, 0, 5, 3, 1, 6, 3, 6, 2, 8, 3, 2, 2, 4, 4, 9, 3, 7, 4, 3, 3, 0, 13, 1, 4, 0, 15, 1, 2, 0, 17, 8, 3, 0, 18, 8, 1, 6, 1, 11, 6, 6, 3, 11, 2, 4, 10, 6, 2, 6, 10, 2, 2, 10, 8, 1, 12, 10, 14, 1, 6, 5, 8, 3, 4, 6, 8, 1, 4, 0, 17, 1, 3, 0, 18, 1, 1, 0, 17, 1, 3, 0, 18, 1, 1, 13, 8, 3, 4, 14, 8, 1, 4, 1, 5, 5, 4, 1, 7, 5, 2, 18, 14, 1, 2, 18, 15, 1, 1, 13, 8, 2, 4, 14, 8, 1, 4, 10, 6, 6, 8, 12, 6, 2, 8, 8, 6, 6, 10, 10, 6, 2, 10, 17, 16, 1, 3, 17, 17, 1, 1, 1, 7, 2, 10, 2, 7, 1, 10, 5, 9, 6, 3, 7, 9, 2, 3, 0, 8, 5, 12, 0, 14, 5, 6, 0, 11, 1, 3, 0, 12, 1, 1, 6, 16, 6, 4, 8, 16, 2, 4, 0, 6, 2, 6, 0, 8, 2, 2, 11, 18, 2, 1, 12, 18, 1, 1, 5, 1, 9, 2, 5, 2, 9, 1, 0, 0, 1, 2, 0, 1, 1, 1, 15, 9, 3, 3, 16, 9, 1, 3, 18, 16, 1, 3, 18, 17, 1, 1, 11, 10, 6, 1, 13, 10, 2, 1, 1, 3, 4, 4, 3, 3, 2, 4, 11, 2, 1, 18, 11, 8, 1, 6, 9, 1, 5, 12, 9, 5, 5, 4, 12, 0, 8, 1, 16, 0, 4, 1, 8, 6, 3, 10, 9, 6, 1, 10, 19, 2, 1, 6, 19, 4, 1, 2, 18, 6, 2, 2, 18, 7, 2, 1, 7, 7, 3, 4, 8, 7, 1, 4, 5, 0, 6, 5, 7, 0, 2, 5, 0, 3, 7, 3, 0, 4, 7, 1, 1, 6, 2, 1, 2, 6, 1, 1, 4, 8, 2, 10, 4, 8, 1, 5, 5, 13, 1, 5, 2, 18, 18, 2, 2, 18, 9, 1, 11, 19, 9, 1, 2, 7, 4, 4, 2, 7, 2, 2, 4, 9, 2, 2, 17, 3, 3, 4, 18, 3, 1, 4, 16, 9, 2, 8, 16, 9, 1, 4, 17, 13, 1, 4, 15, 7, 1, 6, 15, 9, 1, 2, 14, 2, 2, 2, 14, 3, 2, 1, 17, 0, 2, 3, 17, 1, 2, 1, 16, 18, 2, 2, 16, 18, 1, 1, 17, 19, 1, 1, 10, 4, 4, 3, 10, 5, 4, 1, 0, 2, 8, 6, 4, 2, 4, 6, 7, 14, 6, 6, 7, 16, 6, 2, 11, 15, 2, 2, 11, 16, 2, 1, 7, 1, 9, 4, 10, 1, 3, 4, 9, 7, 3, 7, 10, 7, 1, 7, 6, 17, 2, 2, 6, 17, 1, 1, 7, 18, 1, 1, 4, 6, 3, 9, 5, 6, 1, 9, 0, 10, 19, 10, 0, 15, 19, 5, 5, 17, 6, 1, 7, 17, 2, 1, 0, 12, 6, 3, 3, 12, 3, 3, 2, 5, 18, 5, 8, 5, 6, 5, 1, 15, 6, 4, 1, 17, 6, 2, 14, 10, 6, 6, 16, 10, 2, 6, 0, 14, 4, 3, 0, 15, 4, 1, 1, 7, 6, 11, 3, 7, 2, 11, 13, 17, 7, 2, 13, 18, 7, 1, 0, 14, 2, 3, 0, 15, 2, 1, 0, 0, 6, 2, 3, 0, 3, 2, 0, 1, 6, 3, 3, 1, 3, 3, 0, 8, 2, 6, 0, 10, 2, 2, 1, 2, 6, 14, 1, 2, 3, 7, 4, 9, 3, 7, 17, 5, 2, 2, 17, 5, 1, 1, 18, 6, 1, 1, 11, 10, 9, 4, 14, 10, 3, 4, 2, 9, 12, 4, 6, 9, 4, 4, 7, 10, 12, 2, 11, 10, 4, 2, 2, 13, 1, 2, 2, 14, 1, 1, 16, 7, 4, 3, 16, 8, 4, 1, 19, 16, 1, 3, 19, 17, 1, 1, 18, 11, 1, 2, 18, 12, 1, 1, 12, 7, 8, 2, 12, 7, 4, 1, 16, 8, 4, 1, 14, 9, 2, 4, 15, 9, 1, 4, 14, 2, 6, 4, 14, 2, 3, 2, 17, 4, 3, 2, 14, 0, 6, 1, 17, 0, 3, 1, 3, 12, 2, 1, 4, 12, 1, 1, 17, 2, 3, 1, 18, 2, 1, 1, 1, 16, 18, 2, 7, 16, 6, 2, 2, 19, 8, 1, 6, 19, 4, 1, 1, 17, 4, 3, 1, 18, 4, 1, 19, 13, 1, 2, 19, 14, 1, 1, 9, 16, 10, 4, 9, 16, 5, 2, 14, 18, 5, 2, 12, 9, 2, 4, 12, 9, 1, 2, 13, 11, 1, 2, 19, 11, 1, 9, 19, 14, 1, 3, 6, 6, 14, 14, 6, 13, 14, 7, 2, 17, 4, 2, 2, 18, 4, 1, 0, 2, 1, 3, 0, 3, 1, 1, 0, 12, 1, 3, 0, 13, 1, 1, 15, 15, 4, 4, 15, 17, 4, 2, 2, 5, 18, 7, 8, 5, 6, 7, 1, 16, 5, 3, 1, 17, 5, 1, 0, 4, 2, 3, 0, 5, 2, 1, 0, 6, 2, 6, 1, 6, 1, 6, 16, 14, 4, 3, 16, 15, 4, 1, 0, 0, 10, 6, 0, 0, 5, 3, 5, 3, 5, 3, 2, 2, 3, 6, 3, 2, 1, 6, 2, 0, 3, 10, 3, 0, 1, 10, 5, 5, 2, 2, 5, 6, 2, 1, 12, 6, 4, 4, 12, 8, 4, 2, 13, 5, 7, 3, 13, 6, 7, 1, 10, 13, 1, 2, 10, 14, 1, 1, 16, 16, 4, 2, 18, 16, 2, 2, 16, 12, 4, 7, 18, 12, 2, 7, 16, 17, 1, 3, 16, 18, 1, 1, 19, 9, 1, 3, 19, 10, 1, 1, 18, 7, 2, 6, 19, 7, 1, 6, 8, 1, 3, 4, 9, 1, 1, 4, 14, 0, 6, 9, 16, 0, 2, 9, 4, 2, 10, 2, 9, 2, 5, 2, 2, 12, 8, 4, 2, 12, 4, 2, 6, 14, 4, 2, 0, 4, 7, 3, 0, 5, 7, 1, 14, 14, 3, 3, 15, 14, 1, 3, 0, 3, 4, 3, 2, 3, 2, 3, 1, 0, 2, 7, 2, 0, 1, 7, 15, 16, 4, 4, 15, 18, 4, 2, 5, 8, 12, 4, 5, 10, 12, 2, 3, 17, 1, 2, 3, 18, 1, 1, 6, 1, 3, 4, 7, 1, 1, 4, 6, 2, 3, 4, 7, 2, 1, 4, 6, 8, 9, 12, 9, 8, 3, 12, 8, 1, 8, 6, 8, 3, 8, 2, 14, 2, 6, 3, 17, 2, 3, 3, 0, 6, 1, 3, 0, 7, 1, 1, 10, 0, 10, 2, 15, 0, 5, 2, 11, 0, 3, 2, 12, 0, 1, 2, 3, 19, 10, 1, 8, 19, 5, 1, 0, 4, 7, 16, 0, 12, 7, 8, 2, 16, 1, 3, 2, 17, 1, 1, 7, 8, 12, 6, 11, 8, 4, 6, 14, 9, 6, 7, 16, 9, 2, 7, 12, 17, 6, 1, 14, 17, 2, 1, 16, 1, 3, 1, 17, 1, 1, 1, 0, 17, 8, 2, 0, 17, 4, 1, 4, 18, 4, 1, 17, 0, 2, 1, 18, 0, 1, 1, 4, 15, 6, 5, 6, 15, 2, 5, 7, 2, 8, 2, 7, 3, 8, 1, 4, 1, 8, 4, 4, 3, 8, 2, 5, 19, 2, 1, 6, 19, 1, 1, 5, 19, 2, 1, 6, 19, 1, 1, 16, 17, 1, 3, 16, 18, 1, 1, 0, 11, 2, 3, 1, 11, 1, 3, 0, 19, 4, 1, 2, 19, 2, 1, 0, 18, 4, 2, 2, 18, 2, 2, 2, 17, 1, 3, 2, 18, 1, 1, 5, 7, 11, 2, 5, 8, 11, 1, 9, 2, 4, 10, 9, 7, 4, 5, 0, 2, 4, 3, 0, 3, 4, 1, 10, 19, 10, 1, 15, 19, 5, 1, 11, 17, 8, 3, 15, 17, 4, 3, 8, 19, 3, 1, 9, 19, 1, 1, 14, 0, 3, 4, 15, 0, 1, 4, 10, 6, 4, 3, 10, 7, 4, 1, 0, 8, 3, 2, 0, 9, 3, 1, 7, 12, 3, 6, 7, 14, 3, 2, 1, 18, 1, 2, 1, 19, 1, 1, 0, 12, 4, 4, 2, 12, 2, 4, 1, 8, 6, 7, 3, 8, 2, 7, 0, 8, 4, 5, 2, 8, 2, 5, 19, 16, 1, 3, 19, 17, 1, 1, 1, 5, 18, 6, 7, 5, 6, 6, 2, 15, 4, 2, 2, 16, 4, 1, 18, 6, 2, 11, 19, 6, 1, 11, 0, 12, 2, 6, 0, 14, 2, 2, 12, 5, 3, 2, 12, 6, 3, 1, 1, 3, 2, 3, 1, 4, 2, 1, 16, 14, 4, 4, 16, 16, 4, 2, 6, 8, 12, 5, 10, 8, 4, 5, 13, 7, 2, 7, 14, 7, 1, 7, 1, 8, 2, 6, 2, 8, 1, 6, 15, 0, 3, 7, 16, 0, 1, 7, 4, 2, 6, 2, 6, 2, 2, 2, 0, 9, 20, 9, 0, 12, 20, 3, 10, 14, 2, 2, 10, 15, 2, 1, 6, 5, 10, 4, 6, 7, 10, 2, 6, 1, 5, 9, 6, 4, 5, 3, 16, 18, 2, 2, 16, 18, 1, 1, 17, 19, 1, 1, 0, 14, 2, 4, 0, 16, 2, 2, 10, 8, 2, 5, 11, 8, 1, 5, 3, 7, 12, 7, 7, 7, 4, 7, 0, 0, 6, 6, 3, 0, 3, 6, 1, 0, 4, 4, 3, 0, 2, 4, 0, 0, 6, 8, 2, 0, 2, 8, 0, 0, 2, 1, 1, 0, 1, 1, 0, 0, 3, 3, 0, 1, 3, 1, 5, 4, 2, 4, 5, 6, 2, 2, 2, 10, 9, 1, 5, 10, 3, 1, 1, 17, 1, 3, 1, 18, 1, 1, 0, 17, 2, 3, 0, 18, 2, 1, 0, 15, 16, 3, 8, 15, 8, 3, 0, 5, 4, 1, 2, 5, 2, 1, 1, 0, 6, 20, 3, 0, 2, 20, 2, 5, 4, 6, 2, 5, 2, 3, 4, 8, 2, 3, 9, 16, 6, 3, 11, 16, 2, 3, 11, 17, 6, 1, 14, 17, 3, 1, 3, 17, 15, 2, 8, 17, 5, 2, 18, 0, 2, 3, 18, 1, 2, 1, 13, 1, 7, 4, 13, 3, 7, 2, 13, 6, 4, 4, 13, 6, 2, 2, 15, 8, 2, 2, 17, 6, 3, 4, 17, 8, 3, 2, 14, 9, 2, 2, 15, 9, 1, 2, 17, 17, 1, 3, 17, 18, 1, 1, 3, 19, 8, 1, 7, 19, 4, 1, 0, 9, 3, 6, 0, 12, 3, 3, 4, 7, 15, 5, 9, 7, 5, 5, 6, 9, 9, 5, 9, 9, 3, 5, 8, 1, 6, 2, 10, 1, 2, 2, 4, 0, 12, 2, 10, 0, 6, 2, 7, 0, 10, 3, 12, 0, 5, 3, 5, 0, 9, 6, 5, 2, 9, 2, 8, 3, 6, 4, 8, 5, 6, 2, 17, 4, 2, 3, 17, 5, 2, 1, 5, 2, 4, 3, 5, 3, 4, 1, 5, 9, 2, 6, 6, 9, 1, 6, 14, 10, 2, 6, 15, 10, 1, 6, 7, 4, 3, 3, 7, 5, 3, 1, 12, 4, 8, 2, 12, 4, 4, 1, 16, 5, 4, 1, 15, 8, 1, 6, 15, 10, 1, 2, 4, 17, 11, 3, 4, 18, 11, 1, 3, 0, 16, 20, 3, 10, 16, 10, 12, 4, 4, 6, 12, 6, 4, 2, 11, 0, 6, 6, 13, 0, 2, 6, 13, 1, 6, 4, 13, 1, 3, 2, 16, 3, 3, 2, 11, 0, 6, 4, 13, 0, 2, 4, 8, 6, 6, 9, 10, 6, 2, 9, 7, 0, 3, 4, 8, 0, 1, 4, 0, 17, 14, 2, 0, 17, 7, 1, 7, 18, 7, 1, 6, 18, 2, 2, 6, 18, 1, 1, 7, 19, 1, 1, 18, 17, 1, 3, 18, 18, 1, 1, 17, 18, 2, 2, 17, 18, 1, 1, 18, 19, 1, 1, 5, 7, 1, 9, 5, 10, 1, 3, 5, 3, 6, 4, 7, 3, 2, 4, 1, 9, 6, 2, 1, 9, 3, 1, 4, 10, 3, 1, 6, 9, 2, 3, 7, 9, 1, 3, 6, 8, 6, 12, 8, 8, 2, 12, 4, 18, 2, 2, 4, 18, 1, 1, 5, 19, 1, 1, 9, 1, 6, 6, 9, 3, 6, 2, 6, 17, 6, 2, 6, 18, 6, 1, 3, 18, 16, 2, 3, 19, 16, 1, 3, 0, 3, 11, 4, 0, 1, 11, 13, 18, 3, 1, 14, 18, 1, 1, 6, 0, 9, 6, 6, 2, 9, 2, 1, 2, 12, 4, 1, 2, 6, 2, 7, 4, 6, 2, 3, 3, 6, 4, 5, 3, 2, 4, 12, 0, 8, 1, 16, 0, 4, 1, 9, 0, 6, 2, 11, 0, 2, 2, 3, 3, 12, 1, 9, 3, 6, 1, 2, 7, 6, 2, 2, 7, 3, 1, 5, 8, 3, 1, 0, 8, 4, 6, 0, 10, 4, 2, 9, 6, 3, 7, 10, 6, 1, 7, 9, 6, 6, 13, 11, 6, 2, 13, 11, 12, 6, 1, 13, 12, 2, 1, 18, 9, 2, 6, 18, 12, 2, 3, 17, 2, 3, 9, 18, 2, 1, 9, 13, 8, 4, 6, 13, 8, 2, 3, 15, 11, 2, 3, 4, 2, 12, 6, 10, 2, 6, 6, 4, 14, 16, 6, 12, 14, 8, 6, 6, 19, 10, 1, 11, 19, 5, 1, 6, 17, 1, 3, 6, 18, 1, 1, 4, 14, 10, 3, 4, 15, 10, 1, 6, 0, 12, 12, 6, 4, 12, 4, 5, 7, 4, 2, 5, 7, 2, 1, 7, 8, 2, 1, 17, 5, 3, 2, 18, 5, 1, 2, 8, 13, 6, 3, 8, 14, 6, 1, 8, 13, 5, 3, 8, 14, 5, 1, 13, 2, 1, 18, 13, 11, 1, 9, 6, 10, 9, 2, 9, 10, 3, 2, 11, 0, 7, 4, 11, 2, 7, 2, 1, 0, 6, 8, 3, 0, 2, 8, 9, 15, 3, 3, 9, 16, 3, 1, 9, 17, 9, 3, 9, 18, 9, 1, 12, 12, 3, 3, 12, 13, 3, 1, 4, 1, 3, 5, 5, 1, 1, 5, 10, 14, 2, 3, 10, 15, 2, 1, 18, 17, 2, 2, 18, 17, 1, 1, 19, 18, 1, 1, 18, 18, 2, 2, 18, 18, 1, 1, 19, 19, 1, 1, 18, 18, 2, 2, 18, 18, 1, 1, 19, 19, 1, 1, 4, 10, 9, 1, 7, 10, 3, 1, 3, 9, 6, 5, 5, 9, 2, 5, 18, 8, 1, 12, 18, 14, 1, 6, 0, 2, 8, 6, 0, 2, 4, 3, 4, 5, 4, 3, 9, 4, 3, 3, 9, 5, 3, 1, 3, 18, 2, 2, 3, 18, 1, 1, 4, 19, 1, 1, 6, 4, 4, 3, 6, 5, 4, 1, 16, 7, 4, 2, 16, 7, 2, 1, 18, 8, 2, 1, 5, 17, 1, 3, 5, 18, 1, 1, 2, 0, 15, 20, 2, 10, 15, 10, 8, 11, 6, 4, 8, 11, 3, 2, 11, 13, 3, 2, 8, 16, 4, 3, 8, 17, 4, 1, 8, 18, 2, 2, 8, 18, 1, 1, 9, 19, 1, 1, 2, 16, 13, 3, 2, 17, 13, 1, 16, 16, 2, 2, 16, 16, 1, 1, 17, 17, 1, 1, 8, 1, 6, 3, 10, 1, 2, 3, 16, 7, 2, 2, 16, 7, 1, 1, 17, 8, 1, 1, 14, 7, 4, 2, 14, 7, 2, 1, 16, 8, 2, 1, 4, 0, 14, 1, 11, 0, 7, 1, 10, 4, 8, 2, 10, 4, 4, 1, 14, 5, 4, 1, 8, 2, 3, 2, 9, 2, 1, 2, 12, 11, 6, 3, 12, 12, 6, 1, 1, 5, 1, 4, 1, 7, 1, 2, 1, 1, 1, 18, 1, 7, 1, 6, 11, 13, 3, 2, 11, 14, 3, 1, 0, 1, 12, 2, 0, 1, 6, 1, 6, 2, 6, 1, 10, 18, 2, 2, 10, 18, 1, 1, 11, 19, 1, 1, 4, 5, 4, 4, 4, 5, 2, 2, 6, 7, 2, 2, 6, 7, 1, 3, 6, 8, 1, 1, 14, 10, 6, 2, 16, 10, 2, 2, 16, 8, 3, 6, 17, 8, 1, 6, 4, 10, 6, 2, 6, 10, 2, 2, 6, 5, 3, 7, 7, 5, 1, 7, 0, 13, 6, 6, 0, 16, 6, 3, 12, 5, 1, 9, 12, 8, 1, 3, 5, 9, 3, 3, 6, 9, 1, 3, 7, 5, 6, 13, 9, 5, 2, 13, 19, 8, 1, 10, 19, 13, 1, 5, 11, 18, 6, 1, 13, 18, 2, 1, 9, 7, 6, 12, 11, 7, 2, 12, 12, 7, 6, 6, 14, 7, 2, 6, 15, 8, 3, 4, 16, 8, 1, 4, 6, 11, 4, 2, 6, 12, 4, 1, 1, 6, 6, 8, 3, 6, 2, 8, 11, 15, 6, 5, 13, 15, 2, 5, 15, 17, 4, 2, 15, 18, 4, 1, 13, 11, 6, 1, 15, 11, 2, 1, 5, 18, 2, 2, 5, 18, 1, 1, 6, 19, 1, 1, 4, 8, 4, 4, 4, 8, 2, 2, 6, 10, 2, 2, 11, 7, 9, 3, 11, 8, 9, 1, 0, 3, 10, 4, 0, 3, 5, 2, 5, 5, 5, 2, 7, 18, 6, 1, 9, 18, 2, 1, 0, 8, 3, 3, 0, 9, 3, 1, 0, 0, 6, 8, 0, 0, 3, 4, 3, 4, 3, 4, 7, 6, 3, 8, 8, 6, 1, 8, 13, 7, 7, 3, 13, 8, 7, 1, 3, 3, 2, 2, 3, 4, 2, 1, 0, 3, 3, 3, 0, 4, 3, 1, 9, 3, 5, 2, 9, 4, 5, 1, 6, 5, 9, 4, 9, 5, 3, 4, 3, 10, 12, 3, 7, 10, 4, 3, 8, 7, 3, 6, 9, 7, 1, 6, 5, 5, 6, 5, 8, 5, 3, 5, 0, 5, 2, 3, 0, 6, 2, 1, 9, 7, 3, 4, 10, 7, 1, 4, 1, 0, 6, 15, 3, 0, 2, 15, 15, 1, 3, 5, 16, 1, 1, 5, 9, 2, 3, 10, 10, 2, 1, 10, 8, 8, 6, 12, 10, 8, 2, 12, 16, 4, 3, 4, 16, 6, 3, 2, 16, 7, 2, 2, 16, 7, 1, 1, 17, 8, 1, 1, 13, 0, 6, 9, 13, 3, 6, 3, 7, 17, 1, 3, 7, 18, 1, 1, 12, 1, 4, 2, 12, 2, 4, 1, 17, 3, 1, 3, 17, 4, 1, 1, 0, 16, 9, 3, 0, 17, 9, 1, 3, 6, 2, 4, 3, 6, 1, 2, 4, 8, 1, 2, 13, 18, 3, 1, 14, 18, 1, 1, 0, 18, 4, 2, 2, 18, 2, 2, 1, 19, 2, 1, 2, 19, 1, 1, 0, 18, 4, 2, 0, 19, 4, 1, 2, 17, 1, 3, 2, 18, 1, 1, 4, 8, 3, 5, 5, 8, 1, 5, 2, 1, 6, 7, 4, 1, 2, 7, 3, 6, 2, 8, 3, 6, 1, 4, 4, 10, 1, 4, 4, 5, 11, 10, 4, 10, 11, 5, 0, 13, 20, 2, 10, 13, 10, 2, 1, 13, 16, 3, 9, 13, 8, 3, 16, 4, 4, 4, 16, 4, 2, 2, 18, 6, 2, 2, 16, 0, 4, 12, 16, 0, 2, 6, 18, 6, 2, 6, 14, 15, 3, 1, 15, 15, 1, 1, 3, 4, 12, 10, 3, 9, 12, 5, 9, 18, 2, 2, 9, 18, 1, 1, 10, 19, 1, 1, 9, 18, 2, 2, 9, 18, 1, 1, 10, 19, 1, 1, 13, 4, 2, 14, 13, 4, 1, 7, 14, 11, 1, 7, 4, 2, 6, 4, 7, 2, 3, 4, 0, 0, 18, 20, 0, 0, 9, 10, 9, 10, 9, 10, 15, 11, 1, 2, 15, 12, 1, 1, 16, 10, 2, 4, 16, 10, 1, 2, 17, 12, 1, 2, 18, 17, 2, 2, 18, 17, 1, 1, 19, 18, 1, 1, 9, 17, 1, 2, 9, 18, 1, 1, 8, 4, 9, 6, 11, 4, 3, 6, 6, 9, 9, 10, 9, 9, 3, 10, 5, 0, 5, 4, 5, 2, 5, 2, 5, 7, 11, 4, 5, 9, 11, 2, 2, 4, 2, 14, 3, 4, 1, 14, 8, 6, 3, 5, 9, 6, 1, 5, 8, 4, 3, 9, 9, 4, 1, 9, 0, 8, 20, 6, 0, 10, 20, 2, 14, 16, 6, 1, 17, 16, 3, 1, 17, 18, 2, 2, 17, 19, 2, 1, 8, 17, 6, 3, 10, 17, 2, 3, 4, 1, 9, 15, 7, 1, 3, 15, 11, 5, 3, 12, 12, 5, 1, 12, 0, 15, 4, 3, 0, 16, 4, 1, 0, 0, 15, 1, 5, 0, 5, 1, 6, 0, 6, 4, 8, 0, 2, 4, 2, 0, 9, 3, 5, 0, 3, 3, 13, 6, 3, 7, 14, 6, 1, 7, 7, 6, 4, 2, 7, 7, 4, 1, 6, 18, 6, 1, 8, 18, 2, 1, 18, 6, 2, 2, 18, 7, 2, 1, 6, 4, 7, 3, 6, 5, 7, 1, 12, 7, 3, 1, 13, 7, 1, 1, 15, 1, 2, 10, 15, 1, 1, 5, 16, 6, 1, 5, 0, 18, 2, 2, 0, 19, 2, 1, 19, 4, 1, 8, 19, 8, 1, 4, 1, 17, 1, 3, 1, 18, 1, 1, 0, 15, 6, 4, 0, 15, 3, 2, 3, 17, 3, 2, 19, 0, 1, 18, 19, 6, 1, 6, 10, 2, 6, 2, 12, 2, 2, 2, 2, 8, 12, 2, 6, 8, 4, 2, 16, 0, 4, 1, 18, 0, 2, 1, 8, 4, 2, 6, 8, 7, 2, 3, 14, 5, 2, 10, 15, 5, 1, 10, 13, 4, 2, 2, 13, 5, 2, 1, 11, 1, 3, 6, 11, 3, 3, 2, 6, 9, 12, 2, 10, 9, 4, 2, 9, 16, 4, 2, 9, 17, 4, 1, 5, 14, 15, 4, 5, 16, 15, 2, 18, 16, 2, 2, 18, 17, 2, 1, 16, 18, 2, 2, 16, 18, 1, 1, 17, 19, 1, 1, 6, 4, 3, 8, 7, 4, 1, 8, 5, 9, 3, 1, 6, 9, 1, 1, 0, 8, 1, 6, 0, 10, 1, 2, 11, 2, 9, 6, 14, 2, 3, 6, 12, 2, 6, 4, 14, 2, 2, 4, 1, 7, 2, 4, 1, 9, 2, 2, 13, 1, 6, 4, 13, 3, 6, 2, 4, 10, 2, 10, 4, 10, 1, 5, 5, 15, 1, 5, 2, 16, 9, 3, 5, 16, 3, 3, 1, 2, 3, 9, 2, 2, 1, 9, 19, 7, 1, 4, 19, 9, 1, 2, 14, 11, 6, 8, 14, 11, 3, 4, 17, 15, 3, 4, 15, 12, 4, 6, 15, 12, 2, 3, 17, 15, 2, 3, 16, 15, 2, 2, 16, 15, 1, 1, 17, 16, 1, 1, 17, 16, 2, 2, 17, 16, 1, 1, 18, 17, 1, 1, 17, 16, 2, 2, 17, 16, 1, 1, 18, 17, 1, 1, 2, 3, 2, 2, 2, 3, 1, 1, 3, 4, 1, 1, 10, 10, 3, 3, 11, 10, 1, 3, 5, 9, 7, 8, 5, 13, 7, 4, 7, 16, 2, 2, 7, 16, 1, 1, 8, 17, 1, 1, 7, 16, 2, 2, 7, 16, 1, 1, 8, 17, 1, 1, 9, 8, 10, 3, 14, 8, 5, 3, 6, 7, 4, 8, 6, 7, 2, 4, 8, 11, 2, 4, 1, 6, 4, 3, 1, 7, 4, 1, 6, 10, 6, 10, 8, 10, 2, 10, 4, 6, 3, 6, 5, 6, 1, 6, 3, 10, 4, 4, 3, 10, 2, 2, 5, 12, 2, 2, 3, 10, 4, 4, 3, 10, 2, 2, 5, 12, 2, 2, 3, 10, 4, 4, 3, 10, 2, 2, 5, 12, 2, 2, 14, 8, 2, 6, 15, 8, 1, 6, 3, 10, 4, 4, 3, 10, 2, 2, 5, 12, 2, 2, 3, 10, 4, 4, 3, 10, 2, 2, 5, 12, 2, 2, 12, 4, 3, 9, 13, 4, 1, 9, 12, 3, 1, 12, 12, 7, 1, 4, 2, 0, 18, 1, 8, 0, 6, 1, 10, 0, 10, 6, 10, 0, 5, 3, 15, 3, 5, 3, 18, 16, 2, 2, 18, 17, 2, 1, 3, 5, 4, 2, 3, 5, 2, 1, 5, 6, 2, 1, 11, 8, 3, 3, 12, 8, 1, 3, 11, 7, 3, 5, 12, 7, 1, 5, 3, 19, 15, 1, 8, 19, 5, 1, 8, 13, 3, 2, 8, 14, 3, 1, 2, 12, 8, 4, 2, 12, 4, 2, 6, 14, 4, 2, 16, 16, 2, 2, 16, 16, 1, 1, 17, 17, 1, 1, 7, 0, 3, 2, 8, 0, 1, 2, 6, 7, 2, 5, 7, 7, 1, 5, 18, 0, 2, 17, 19, 0, 1, 17, 16, 16, 1, 3, 16, 17, 1, 1, 14, 8, 3, 7, 15, 8, 1, 7, 10, 17, 2, 2, 10, 17, 1, 1, 11, 18, 1, 1, 4, 9, 1, 3, 4, 10, 1, 1, 18, 10, 2, 3, 18, 11, 2, 1, 12, 1, 3, 10, 13, 1, 1, 10, 8, 12, 9, 1, 11, 12, 3, 1, 5, 18, 2, 2, 5, 18, 1, 1, 6, 19, 1, 1, 19, 6, 1, 9, 19, 9, 1, 3, 4, 7, 2, 4, 4, 7, 1, 2, 5, 9, 1, 2, 1, 4, 6, 14, 3, 4, 2, 14, 10, 5, 9, 3, 13, 5, 3, 3, 18, 7, 2, 6, 18, 9, 2, 2, 5, 6, 2, 7, 6, 6, 1, 7, 10, 4, 6, 8, 13, 4, 3, 8, 0, 8, 2, 9, 0, 11, 2, 3, 0, 7, 5, 3, 0, 8, 5, 1, 8, 1, 7, 2, 8, 2, 7, 1, 7, 5, 3, 5, 8, 5, 1, 5, 19, 2, 1, 2, 19, 3, 1, 1, 6, 7, 10, 11, 11, 7, 5, 11, 9, 19, 6, 1, 11, 19, 2, 1, 3, 0, 12, 1, 7, 0, 4, 1, 4, 1, 6, 5, 6, 1, 2, 5, 6, 12, 12, 6, 10, 12, 4, 6, 16, 13, 2, 3, 16, 14, 2, 1, 7, 14, 4, 2, 7, 15, 4, 1, 7, 14, 2, 2, 7, 15, 2, 1, 3, 10, 2, 4, 3, 10, 1, 2, 4, 12, 1, 2, 0, 3, 2, 6, 0, 5, 2, 2, 1, 10, 2, 2, 1, 10, 1, 1, 2, 11, 1, 1, 16, 4, 4, 3, 16, 5, 4, 1, 5, 10, 2, 4, 5, 10, 1, 2, 6, 12, 1, 2, 5, 11, 13, 2, 5, 12, 13, 1, 10, 2, 3, 11, 11, 2, 1, 11, 10, 2, 4, 4, 10, 4, 4, 2, 8, 8, 6, 2, 10, 8, 2, 2, 11, 2, 3, 3, 12, 2, 1, 3, 6, 18, 14, 2, 6, 18, 7, 1, 13, 19, 7, 1, 17, 7, 1, 12, 17, 11, 1, 4, 10, 5, 10, 3, 10, 6, 10, 1, 6, 1, 3, 3, 7, 1, 1, 3, 13, 8, 3, 1, 14, 8, 1, 1, 10, 14, 2, 6, 10, 16, 2, 2, 4, 1, 12, 14, 8, 1, 4, 14, 14, 1, 6, 14, 16, 1, 2, 14, 3, 16, 2, 2, 3, 16, 1, 1, 4, 17, 1, 1, 0, 16, 2, 2, 0, 17, 2, 1, 15, 6, 4, 6, 15, 6, 2, 3, 17, 9, 2, 3, 12, 5, 2, 2, 12, 6, 2, 1, 7, 6, 6, 13, 9, 6, 2, 13, 1, 9, 6, 5, 3, 9, 2, 5, 0, 5, 3, 4, 0, 7, 3, 2, 4, 1, 16, 2, 4, 1, 8, 1, 12, 2, 8, 1, 1, 18, 4, 2, 1, 18, 2, 1, 3, 19, 2, 1, 7, 7, 3, 4, 8, 7, 1, 4, 3, 4, 9, 3, 6, 4, 3, 3, 4, 6, 6, 10, 6, 6, 2, 10, 9, 0, 8, 10, 13, 0, 4, 10, 8, 0, 8, 1, 12, 0, 4, 1, 6, 2, 8, 16, 6, 2, 4, 8, 10, 10, 4, 8, 14, 10, 2, 10, 14, 10, 1, 5, 15, 15, 1, 5, 12, 11, 1, 2, 12, 12, 1, 1, 16, 0, 3, 8, 17, 0, 1, 8, 14, 0, 6, 10, 17, 0, 3, 10, 16, 0, 3, 5, 17, 0, 1, 5, 4, 5, 11, 2, 4, 6, 11, 1, 1, 0, 2, 1, 2, 0, 1, 1, 0, 0, 2, 3, 0, 1, 2, 1, 11, 6, 6, 11, 13, 6, 2, 11, 14, 0, 3, 1, 15, 0, 1, 1, 19, 7, 1, 2, 19, 8, 1, 1, 17, 0, 3, 9, 18, 0, 1, 9, 12, 7, 3, 4, 13, 7, 1, 4, 0, 1, 14, 2, 0, 1, 7, 1, 7, 2, 7, 1, 3, 1, 3, 2, 4, 1, 1, 2, 4, 0, 15, 2, 9, 0, 5, 2, 10, 2, 6, 1, 12, 2, 2, 1, 9, 4, 6, 11, 11, 4, 2, 11, 2, 16, 2, 4, 2, 18, 2, 2, 6, 17, 6, 3, 8, 17, 2, 3, 7, 9, 6, 2, 9, 9, 2, 2, 6, 8, 9, 2, 9, 8, 3, 2, 6, 6, 2, 10, 6, 6, 1, 5, 7, 11, 1, 5, 0, 11, 2, 3, 0, 12, 2, 1, 11, 15, 4, 1, 13, 15, 2, 1, 6, 17, 1, 2, 6, 18, 1, 1, 0, 0, 6, 20, 2, 0, 2, 20, 3, 10, 2, 2, 4, 10, 1, 2, 4, 7, 3, 5, 5, 7, 1, 5, 3, 12, 6, 2, 5, 12, 2, 2, 6, 15, 7, 4, 6, 17, 7, 2, 17, 16, 2, 2, 17, 16, 1, 1, 18, 17, 1, 1, 15, 1, 3, 16, 16, 1, 1, 16, 6, 16, 6, 3, 8, 16, 2, 3, 15, 14, 3, 2, 15, 15, 3, 1, 12, 16, 1, 2, 12, 17, 1, 1, 0, 2, 4, 4, 0, 2, 2, 2, 2, 4, 2, 2, 1, 1, 6, 4, 1, 1, 3, 2, 4, 3, 3, 2, 1, 18, 1, 2, 1, 19, 1, 1, 4, 7, 2, 3, 4, 8, 2, 1, 1, 0, 9, 14, 1, 7, 9, 7, 4, 9, 2, 6, 4, 9, 1, 3, 5, 12, 1, 3, 3, 9, 4, 3, 5, 9, 2, 3, 0, 9, 2, 4, 0, 11, 2, 2, 16, 6, 3, 10, 17, 6, 1, 10, 16, 11, 2, 1, 17, 11, 1, 1, 5, 7, 4, 4, 5, 9, 4, 2, 10, 11, 9, 2, 13, 11, 3, 2, 15, 10, 2, 2, 15, 10, 1, 1, 16, 11, 1, 1, 10, 6, 6, 14, 10, 13, 6, 7, 14, 7, 3, 5, 15, 7, 1, 5, 6, 11, 12, 3, 10, 11, 4, 3, 17, 16, 1, 2, 17, 17, 1, 1, 8, 5, 5, 4, 8, 7, 5, 2, 11, 6, 4, 2, 11, 7, 4, 1, 3, 4, 8, 2, 3, 4, 4, 1, 7, 5, 4, 1, 0, 8, 6, 6, 2, 8, 2, 6, 7, 4, 6, 2, 7, 5, 6, 1, 7, 3, 6, 3, 9, 3, 2, 3, 2, 17, 3, 3, 2, 18, 3, 1, 3, 10, 6, 1, 5, 10, 2, 1, 7, 2, 6, 2, 9, 2, 2, 2, 4, 11, 9, 1, 7, 11, 3, 1, 7, 7, 11, 12, 7, 13, 11, 6, 3, 2, 3, 4, 4, 2, 1, 4, 9, 7, 9, 3, 12, 7, 3, 3, 15, 11, 2, 6, 15, 11, 1, 3, 16, 14, 1, 3, 0, 5, 5, 3, 0, 6, 5, 1, 8, 1, 6, 12, 10, 1, 2, 12, 3, 7, 15, 13, 8, 7, 5, 13, 0, 9, 9, 9, 0, 12, 9, 3, 16, 0, 3, 8, 17, 0, 1, 8, 16, 2, 4, 2, 18, 2, 2, 2, 13, 0, 6, 5, 16, 0, 3, 5, 15, 1, 3, 2, 16, 1, 1, 2, 11, 8, 3, 2, 12, 8, 1, 2, 1, 8, 2, 12, 1, 8, 1, 6, 2, 14, 1, 6, 0, 1, 6, 12, 2, 1, 2, 12, 19, 17, 1, 3, 19, 18, 1, 1, 11, 3, 3, 10, 12, 3, 1, 10, 8, 1, 9, 8, 11, 1, 3, 8, 18, 16, 2, 2, 18, 16, 1, 1, 19, 17, 1, 1, 18, 16, 2, 2, 18, 16, 1, 1, 19, 17, 1, 1, 6, 13, 2, 6, 6, 15, 2, 2, 9, 14, 2, 2, 9, 15, 2, 1, 14, 10, 2, 4, 14, 10, 1, 2, 15, 12, 1, 2, 0, 15, 2, 2, 0, 15, 1, 1, 1, 16, 1, 1, 6, 7, 2, 2, 6, 7, 1, 1, 7, 8, 1, 1, 11, 18, 2, 2, 11, 18, 1, 1, 12, 19, 1, 1, 0, 0, 6, 4, 0, 0, 3, 2, 3, 2, 3, 2, 4, 1, 6, 6, 6, 1, 2, 6, 15, 13, 5, 4, 15, 15, 5, 2, 7, 17, 6, 1, 9, 17, 2, 1, 16, 19, 4, 1, 18, 19, 2, 1, 16, 16, 4, 4, 18, 16, 2, 4, 7, 8, 9, 4, 10, 8, 3, 4, 16, 18, 2, 2, 16, 18, 1, 1, 17, 19, 1, 1, 2, 9, 2, 4, 2, 9, 1, 2, 3, 11, 1, 2, 0, 3, 8, 4, 0, 3, 4, 2, 4, 5, 4, 2, 0, 1, 8, 1, 4, 1, 4, 1, 0, 5, 8, 9, 4, 5, 4, 9, 7, 18, 6, 2, 9, 18, 2, 2, 0, 4, 1, 12, 0, 8, 1, 4, 19, 13, 1, 6, 19, 15, 1, 2, 2, 8, 6, 8, 4, 8, 2, 8, 0, 0, 9, 17, 3, 0, 3, 17, 7, 9, 6, 8, 9, 9, 2, 8, 5, 10, 9, 4, 8, 10, 3, 4, 5, 0, 8, 3, 5, 1, 8, 1, 16, 6, 4, 4, 16, 6, 2, 2, 18, 8, 2, 2, 17, 4, 2, 8, 17, 4, 1, 4, 18, 8, 1, 4, 2, 16, 1, 3, 2, 17, 1, 1, 2, 16, 1, 3, 2, 17, 1, 1, 11, 0, 1, 3, 11, 1, 1, 1, 11, 2, 9, 7, 14, 2, 3, 7, 10, 2, 3, 6, 11, 2, 1, 6, 5, 9, 15, 2, 5, 10, 15, 1, 8, 16, 6, 2, 8, 17, 6, 1, 9, 16, 10, 2, 9, 16, 5, 1, 14, 17, 5, 1, 9, 17, 2, 2, 9, 17, 1, 1, 10, 18, 1, 1, 10, 15, 6, 4, 10, 15, 3, 2, 13, 17, 3, 2, 4, 5, 15, 12, 9, 5, 5, 12, 11, 13, 2, 3, 11, 14, 2, 1, 8, 13, 7, 3, 8, 14, 7, 1, 1, 12, 1, 2, 1, 13, 1, 1, 16, 18, 2, 2, 16, 18, 1, 1, 17, 19, 1, 1, 1, 19, 18, 1, 7, 19, 6, 1, 1, 17, 6, 1, 4, 17, 3, 1, 1, 3, 1, 12, 1, 9, 1, 6, 0, 9, 3, 6, 0, 11, 3, 2, 5, 4, 3, 10, 6, 4, 1, 10, 6, 17, 2, 1, 7, 17, 1, 1, 1, 0, 6, 12, 3, 0, 2, 12, 4, 7, 9, 2, 7, 7, 3, 2, 6, 11, 9, 1, 9, 11, 3, 1, 17, 10, 2, 10, 17, 15, 2, 5, 4, 10, 2, 10, 4, 10, 1, 5, 5, 15, 1, 5, 12, 3, 3, 12, 13, 3, 1, 12, 15, 3, 4, 6, 15, 3, 2, 3, 17, 6, 2, 3, 12, 8, 3, 3, 13, 8, 1, 3, 4, 14, 2, 4, 4, 16, 2, 2, 6, 16, 1, 3, 6, 17, 1, 1, 1, 1, 2, 3, 2, 1, 1, 3, 0, 2, 4, 1, 2, 2, 2, 1, 8, 17, 12, 3, 12, 17, 4, 3, 9, 16, 6, 4, 11, 16, 2, 4, 4, 6, 3, 6, 4, 9, 3, 3, 6, 2, 12, 9, 6, 5, 12, 3, 6, 0, 14, 20, 6, 0, 7, 10, 13, 10, 7, 10, 15, 16, 2, 2, 15, 16, 1, 1, 16, 17, 1, 1, 15, 16, 2, 2, 15, 16, 1, 1, 16, 17, 1, 1, 19, 8, 1, 3, 19, 9, 1, 1, 13, 4, 1, 2, 13, 5, 1, 1, 0, 4, 4, 2, 0, 5, 4, 1, 19, 5, 1, 6, 19, 7, 1, 2, 16, 0, 2, 1, 17, 0, 1, 1, 13, 1, 1, 3, 13, 2, 1, 1, 17, 17, 1, 3, 17, 18, 1, 1, 5, 4, 8, 8, 5, 4, 4, 4, 9, 8, 4, 4, 1, 2, 2, 2, 1, 2, 1, 1, 2, 3, 1, 1, 0, 0, 8, 6, 0, 0, 4, 3, 4, 3, 4, 3, 6, 3, 4, 2, 6, 4, 4, 1, 1, 0, 3, 3, 1, 1, 3, 1, 6, 1, 7, 2, 6, 2, 7, 1, 2, 6, 12, 6, 6, 6, 4, 6, 1, 16, 9, 2, 4, 16, 3, 2, 7, 15, 6, 4, 9, 15, 2, 4, 6, 15, 12, 1, 12, 15, 6, 1, 17, 17, 1, 3, 17, 18, 1, 1, 17, 15, 2, 2, 17, 15, 1, 1, 18, 16, 1, 1, 3, 13, 3, 3, 3, 14, 3, 1, 10, 17, 1, 3, 10, 18, 1, 1, 4, 0, 14, 8, 11, 0, 7, 8, 2, 0, 12, 2, 6, 0, 4, 2, 2, 0, 4, 3, 4, 0, 2, 3, 13, 1, 1, 2, 13, 2, 1, 1, 7, 5, 3, 6, 8, 5, 1, 6, 18, 2, 2, 2, 18, 2, 1, 1, 19, 3, 1, 1, 15, 1, 2, 14, 16, 1, 1, 14, 15, 6, 2, 2, 15, 6, 1, 1, 16, 7, 1, 1, 3, 1, 6, 3, 5, 1, 2, 3, 7, 16, 2, 2, 7, 16, 1, 1, 8, 17, 1, 1, 5, 17, 2, 2, 5, 17, 1, 1, 6, 18, 1, 1, 9, 10, 6, 10, 11, 10, 2, 10, 10, 17, 6, 3, 12, 17, 2, 3, 14, 5, 2, 10, 14, 10, 2, 5, 11, 12, 6, 2, 11, 13, 6, 1, 8, 1, 1, 3, 8, 2, 1, 1, 12, 15, 2, 2, 12, 15, 1, 1, 13, 16, 1, 1, 6, 8, 6, 4, 6, 8, 3, 2, 9, 10, 3, 2, 7, 5, 3, 5, 8, 5, 1, 5, 0, 5, 7, 3, 0, 6, 7, 1, 7, 9, 6, 6, 9, 9, 2, 6, 5, 7, 8, 8, 5, 11, 8, 4, 4, 9, 2, 6, 4, 9, 1, 3, 5, 12, 1, 3, 10, 11, 6, 1, 12, 11, 2, 1, 13, 6, 6, 11, 15, 6, 2, 11, 8, 17, 2, 2, 8, 17, 1, 1, 9, 18, 1, 1, 4, 12, 12, 1, 8, 12, 4, 1, 11, 17, 3, 2, 11, 18, 3, 1, 8, 17, 6, 1, 10, 17, 2, 1, 4, 1, 14, 6, 4, 3, 14, 2, 14, 2, 2, 12, 14, 8, 2, 6, 12, 13, 3, 2, 12, 14, 3, 1, 6, 1, 6, 1, 8, 1, 2, 1, 10, 6, 6, 1, 12, 6, 2, 1, 3, 19, 2, 1, 4, 19, 1, 1, 18, 16, 2, 2, 18, 16, 1, 1, 19, 17, 1, 1, 16, 11, 3, 7, 17, 11, 1, 7, 19, 5, 1, 6, 19, 8, 1, 3, 9, 8, 4, 3, 9, 9, 4, 1, 16, 8, 4, 4, 16, 8, 2, 2, 18, 10, 2, 2, 2, 8, 2, 2, 2, 8, 1, 1, 3, 9, 1, 1, 3, 5, 6, 4, 3, 5, 3, 2, 6, 7, 3, 2, 2, 3, 8, 16, 2, 3, 4, 8, 6, 11, 4, 8, 17, 17, 1, 3, 17, 18, 1, 1, 7, 2, 8, 11, 11, 2, 4, 11, 13, 3, 6, 14, 16, 3, 3, 14, 0, 9, 18, 2, 6, 9, 6, 2, 6, 10, 14, 3, 6, 11, 14, 1, 10, 9, 9, 3, 13, 9, 3, 3, 3, 5, 4, 6, 3, 5, 2, 3, 5, 8, 2, 3, 3, 7, 3, 7, 4, 7, 1, 7, 2, 8, 11, 6, 2, 10, 11, 2, 8, 9, 6, 3, 8, 10, 6, 1, 3, 3, 3, 11, 4, 3, 1, 11, 0, 19, 6, 1, 3, 19, 3, 1, 18, 18, 1, 2, 18, 19, 1, 1, 8, 0, 12, 6, 8, 0, 6, 3, 14, 3, 6, 3, 19, 5, 1, 3, 19, 6, 1, 1, 5, 8, 2, 1, 6, 8, 1, 1, 13, 11, 2, 1, 14, 11, 1, 1, 3, 6, 15, 13, 8, 6, 5, 13, 4, 3, 6, 2, 6, 3, 2, 2, 0, 18, 1, 2, 0, 19, 1, 1, 7, 8, 2, 6, 8, 8, 1, 6, 3, 0, 6, 19, 5, 0, 2, 19, 3, 1, 6, 5, 5, 1, 2, 5, 17, 14, 3, 6, 17, 16, 3, 2, 17, 13, 2, 6, 18, 13, 1, 6, 17, 18, 2, 2, 18, 18, 1, 2, 11, 14, 9, 4, 14, 14, 3, 4, 15, 8, 4, 6, 15, 8, 2, 3, 17, 11, 2, 3, 1, 16, 1, 3, 1, 17, 1, 1, 7, 0, 3, 14, 8, 0, 1, 14, 12, 0, 2, 1, 13, 0, 1, 1, 7, 9, 6, 5, 10, 9, 3, 5, 15, 5, 4, 9, 17, 5, 2, 9, 11, 0, 6, 6, 13, 0, 2, 6, 16, 15, 2, 2, 16, 15, 1, 1, 17, 16, 1, 1, 16, 15, 2, 2, 16, 15, 1, 1, 17, 16, 1, 1, 13, 2, 2, 18, 13, 11, 2, 9, 8, 4, 8, 10, 8, 9, 8, 5, 8, 3, 2, 3, 8, 4, 2, 1, 11, 1, 6, 9, 11, 4, 6, 3, 15, 4, 5, 6, 15, 6, 5, 2, 12, 18, 2, 2, 12, 18, 1, 1, 13, 19, 1, 1, 1, 17, 1, 3, 1, 18, 1, 1, 12, 19, 2, 1, 13, 19, 1, 1, 8, 10, 6, 6, 10, 10, 2, 6, 14, 2, 6, 5, 16, 2, 2, 5, 9, 5, 2, 6, 9, 7, 2, 2, 1, 15, 2, 2, 2, 15, 1, 2, 18, 17, 1, 3, 18, 18, 1, 1, 10, 14, 4, 6, 10, 16, 4, 2, 9, 7, 3, 2, 10, 7, 1, 2, 6, 9, 6, 2, 6, 9, 3, 1, 9, 10, 3, 1, 0, 2, 1, 12, 0, 6, 1, 4, 4, 0, 15, 1, 9, 0, 5, 1, 9, 0, 8, 2, 9, 0, 4, 1, 13, 1, 4, 1, 12, 2, 8, 1, 16, 2, 4, 1, 7, 1, 10, 6, 7, 3, 10, 2, 18, 6, 2, 3, 18, 7, 2, 1, 4, 12, 2, 2, 4, 12, 1, 1, 5, 13, 1, 1, 6, 6, 6, 2, 8, 6, 2, 2, 0, 9, 9, 6, 3, 9, 3, 6, 17, 18, 2, 2, 18, 18, 1, 2, 11, 2, 6, 16, 13, 2, 2, 16, 2, 4, 15, 13, 7, 4, 5, 13, 16, 2, 3, 10, 17, 2, 1, 10, 6, 10, 2, 1, 7, 10, 1, 1, 1, 1, 18, 16, 10, 1, 9, 16, 14, 4, 3, 15, 15, 4, 1, 15, 19, 13, 1, 2, 19, 14, 1, 1, 2, 6, 5, 8, 2, 10, 5, 4}; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/github_source/minicv2/include/collections.h b/github_source/minicv2/include/collections.h new file mode 100644 index 0000000..ab9c48d --- /dev/null +++ b/github_source/minicv2/include/collections.h @@ -0,0 +1,134 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Common data structures. + */ +#ifndef __COLLECTIONS_H__ +#define __COLLECTIONS_H__ +#include +#include +#ifdef __cplusplus +extern "C" +{ +#endif +//////////// +// bitmap // +//////////// + +typedef struct bitmap +{ + size_t size; + char *data; +} +bitmap_t; + +void bitmap_alloc(bitmap_t *ptr, size_t size); +void bitmap_free(bitmap_t *ptr); +void bitmap_clear(bitmap_t *ptr); +void bitmap_bit_set(bitmap_t *ptr, size_t index); +bool bitmap_bit_get(bitmap_t *ptr, size_t index); +#define BITMAP_COMPUTE_ROW_INDEX(image, y) (((image)->w)*(y)) +#define BITMAP_COMPUTE_INDEX(row_index, x) ((row_index)+(x)) + +////////// +// lifo // +////////// + +typedef struct lifo +{ + size_t len, size, data_len; + char *data; +} +lifo_t; + +void lifo_alloc(lifo_t *ptr, size_t size, size_t data_len); +void lifo_alloc_all(lifo_t *ptr, size_t *size, size_t data_len); +void lifo_free(lifo_t *ptr); +void lifo_clear(lifo_t *ptr); +size_t lifo_size(lifo_t *ptr); +bool lifo_is_not_empty(lifo_t *ptr); +bool lifo_is_not_full(lifo_t *ptr); +void lifo_enqueue(lifo_t *ptr, void *data); +void lifo_dequeue(lifo_t *ptr, void *data); +void lifo_poke(lifo_t *ptr, void *data); +void lifo_peek(lifo_t *ptr, void *data); + +////////// +// fifo // +////////// + +typedef struct fifo +{ + size_t head_ptr, tail_ptr, len, size, data_len; + char *data; +} +fifo_t; + +void fifo_alloc(fifo_t *ptr, size_t size, size_t data_len); +void fifo_alloc_all(fifo_t *ptr, size_t *size, size_t data_len); +void fifo_free(fifo_t *ptr); +void fifo_clear(fifo_t *ptr); +size_t fifo_size(fifo_t *ptr); +bool fifo_is_not_empty(fifo_t *ptr); +bool fifo_is_not_full(fifo_t *ptr); +void fifo_enqueue(fifo_t *ptr, void *data); +void fifo_dequeue(fifo_t *ptr, void *data); +void fifo_poke(fifo_t *ptr, void *data); +void fifo_peek(fifo_t *ptr, void *data); + +////////// +// list // +////////// + +typedef struct list_lnk +{ + struct list_lnk *next_ptr, *prev_ptr; + char data[]; +} +list_lnk_t; + +typedef struct list +{ + list_lnk_t *head_ptr, *tail_ptr; + size_t size, data_len; +} +list_t; + +void imlib_list_init(list_t *ptr, size_t data_len); +void list_init(list_t *ptr, size_t data_len); +void list_copy(list_t *dst, list_t *src); +void list_free(list_t *ptr); +void list_clear(list_t *ptr); +size_t list_size(list_t *ptr); +void list_push_front(list_t *ptr, void *data); +void list_push_back(list_t *ptr, void *data); +void list_pop_front(list_t *ptr, void *data); +void list_pop_back(list_t *ptr, void *data); +void list_get_front(list_t *ptr, void *data); +void list_get_back(list_t *ptr, void *data); +void list_set_front(list_t *ptr, void *data); +void list_set_back(list_t *ptr, void *data); +void list_insert(list_t *ptr, void *data, size_t index); +void list_remove(list_t *ptr, void *data, size_t index); +void list_get(list_t *ptr, void *data, size_t index); +void list_set(list_t *ptr, void *data, size_t index); + +////////////// +// iterator // +////////////// + +list_lnk_t *iterator_start_from_head(list_t *ptr); +list_lnk_t *iterator_start_from_tail(list_t *ptr); +list_lnk_t *iterator_next(list_lnk_t *lnk); +list_lnk_t *iterator_prev(list_lnk_t *lnk); +void iterator_get(list_t *ptr, list_lnk_t *lnk, void *data); +void iterator_set(list_t *ptr, list_lnk_t *lnk, void *data); +#ifdef __cplusplus +} +#endif +#endif /* __COLLECTIONS_H__ */ diff --git a/github_source/minicv2/include/common.h b/github_source/minicv2/include/common.h new file mode 100644 index 0000000..17663f6 --- /dev/null +++ b/github_source/minicv2/include/common.h @@ -0,0 +1,48 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Common macros. + */ +#ifndef __OMV_COMMON_H__ +#ifdef __cplusplus +extern "C" +{ +#endif + +#define OMV_ATTR_ALIGNED(x, a) x __attribute__((aligned(a))) +#define OMV_ATTR_SECTION(x, s) x __attribute__((section(s))) +#define OMV_ATTR_ALWAYS_INLINE inline __attribute__((always_inline)) +#define OMV_ATTR_OPTIMIZE(o) __attribute__((optimize(o))) + +#define OMG_BREAK() __asm__ volatile ("BKPT") + +#ifdef OMV_DEBUG_PRINTF +#define debug_printf(fmt, ...) \ + do { imlib_printf(5, "%s(): " fmt, __func__, ##__VA_ARGS__);} while (0) +#else +#define debug_printf(...) +#endif + +#define OMV_MAX(a,b) \ +({ \ + __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; \ +}) + +#define OMV_MIN(a,b) \ +({ \ + __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; \ +}) + +#ifdef __cplusplus +} +#endif +#endif //__OMV_COMMON_H__ diff --git a/github_source/minicv2/include/fb_alloc.h b/github_source/minicv2/include/fb_alloc.h new file mode 100644 index 0000000..730e2c9 --- /dev/null +++ b/github_source/minicv2/include/fb_alloc.h @@ -0,0 +1,82 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013-2016 Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Interface for using extra frame buffer RAM as a stack. + * + * Theory of operation: + * + * The frame buffer stack may be used to allocate large areas of RAM very quickly. You can allocate + * memory using fb_alloc() which returns a poiner to an allocated region of memory equal in size to + * the amount requested. If the memory is not avaiable fb_alloc() will generate an exception. + * + * After RAM is allocated with fb_alloc() you can free it with fb_free() in the order of allocs. + * + * Now, to prevent leaking allocated regions on the frame buffer stack all fb_alloc()s should be + * preceded by fb_alloc_mark() which starts an fb_alloc() region (which may have many fb_alloc()s + * in it). This ensures that if an exception occurs all fb_alloc()s are freed in the region. + * + * This is because all exceptions call fb_alloc_free_till_mark() to free the previously allocated + * region. Your code should call fb_alloc_free_till_mark() to free previously allocated memory also + * once you are done with it. This will cleanup all allocs along with the alloced mark. + * + * You may conveniently use fb_alloc_free_till_mark() to avoid having to manually free all + * previous allocs in one go very easily. + * + * Now, it can be tricky to allocate a region permanently that you do not want freed because + * exceptions pop the frame buffer stack using fb_alloc_free_till_mark(). Additionally, you may + * actually want exceptions to do this until you know an allocation operation that has multiple + * steps has succeeded. To handle these situations call fb_alloc_mark_permanent() after a complex + * operation to prevent fb_alloc_free_till_mark() from freeing past the last marked alloc. + * + * When you want deallocate this permanent region just call fb_alloc_free_till_mark_permanent() + * which will ignore the permanent mark and free backwards until it hits the previously allocated + * mark. + * + * Note that fb_free() and fb_free_all() do not respect any marks and permanent regions. + * + * Regardings the flags below: + * - FB_ALLOC_NO_HINT - fb_alloc doesn't do anything special. + * - FB_ALLOC_PREFER_SPEED - fb_alloc will make sure the allocated region is in the fatest possible + * memory. E.g. allocs will be in SRAM versus SDRAM if SDRAM is available. + * Setting this flag affects where fb_alloc_all() gets RAM from. If this + * flag is set then fb_alloc_all() will not use the SDRAM. + * - FB_ALLOC_PREFER_SIZE - fb_alloc will make sure the allocated region is the largest possible + * memory. E.g. allocs will be in SDRAM versus SRAM if SDRAM is available. + * Setting this flag affects where fb_alloc_all() gets RAM from. If this + * flag is set then fb_alloc_all() will use the SDRAM (default). + * - FB_ALLOC_CACHE_ALIGN - Aligns the starting address returned to a cache line and makes sure + * the amount of memory allocated is padded to the end of a cache line. + */ +#ifndef __FB_ALLOC_H__ +#define __FB_ALLOC_H__ +#include +#ifdef __cplusplus +extern "C" +{ +#endif +#define FB_ALLOC_NO_HINT 0 +#define FB_ALLOC_PREFER_SPEED 1 +#define FB_ALLOC_PREFER_SIZE 2 +#define FB_ALLOC_CACHE_ALIGN 4 +char *fb_alloc_stack_pointer(); +void fb_alloc_fail(); +void fb_alloc_init0(); +void fb_realloc_init1(uint32_t size); +void fb_alloc_close0(); +uint32_t fb_avail(); +void fb_alloc_mark(); +void fb_alloc_free_till_mark(); +void fb_alloc_mark_permanent(); // tag memory that should not be popped on exception +void fb_alloc_free_till_mark_past_mark_permanent(); // frees past marked permanent allocations +void *fb_alloc(uint32_t size, int hints); +void *fb_alloc0(uint32_t size, int hints); +void *fb_alloc_all(uint32_t *size, int hints); // returns pointer and sets size +void *fb_alloc0_all(uint32_t *size, int hints); // returns pointer and sets size +void fb_free(void *msm); +void fb_free_all(); +#ifdef __cplusplus +} +#endif +#endif /* __FF_ALLOC_H__ */ diff --git a/github_source/minicv2/include/ff_wrapper.h b/github_source/minicv2/include/ff_wrapper.h new file mode 100644 index 0000000..f9bfc64 --- /dev/null +++ b/github_source/minicv2/include/ff_wrapper.h @@ -0,0 +1,64 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013-2016 Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * File System Helper Functions + * + */ +#ifndef __FF_WRAPPER_H__ +#define __FF_WRAPPER_H__ +#include +// #include +#include +#ifdef __cplusplus +extern "C" +{ +#endif +typedef FILE* FIL; +// extern const char *ffs_strerror(FRESULT res); + +//OOFATFS wrappers +// FRESULT f_open_helper(FIL *fp, const TCHAR *path, BYTE mode); +// FRESULT f_opendir_helper(FF_DIR *dp, const TCHAR *path); +// FRESULT f_stat_helper(const TCHAR *path, FILEINFO *fno); +// FRESULT f_mkdir_helper(const TCHAR *path); +// FRESULT f_unlink_helper(const TCHAR *path); +// FRESULT f_rename_helper(const TCHAR *path_old, const TCHAR *path_new); + +int ff_unsupported_format(FIL *fp); +int ff_file_corrupted(FIL *fp); +int ff_not_equal(FIL *fp); +int ff_no_intersection(FIL *fp); +int file_read_open(FIL *fp, const char *path); +int file_write_open(FIL *fp, const char *path); +int file_close(FIL *fp); +int file_seek(FIL *fp, size_t offset); +int file_truncate(FIL *fp); +int file_sync(FIL *fp); +long file_fsize(FIL *fp); +// File buffer functions. +int file_buffer_init0(); +int file_buffer_on(FIL *fp); // does fb_alloc_all +uint32_t file_tell_w_buf(FIL *fp); // valid between on and off +uint32_t file_size_w_buf(FIL *fp); // valid between on and off +int file_buffer_off(FIL *fp); // does fb_free +int read_byte(FIL *fp, uint8_t *value); +int read_byte_expect(FIL *fp, uint8_t value); +int read_byte_ignore(FIL *fp); +int read_word(FIL *fp, uint16_t *value); +int read_word_expect(FIL *fp, uint16_t value); +int read_word_ignore(FIL *fp); +int read_long(FIL *fp, uint32_t *value); +int read_long_expect(FIL *fp, uint32_t value); +int read_long_ignore(FIL *fp); +int read_data(FIL *fp, void *data, size_t size); +int write_byte(FIL *fp, uint8_t value); +int write_word(FIL *fp, uint16_t value); +int write_long(FIL *fp, uint32_t value); +int write_data(FIL *fp, const void *data, size_t size); +#ifdef __cplusplus +} +#endif +#endif /* __FF_WRAPPER_H__ */ + diff --git a/github_source/minicv2/include/fft.h b/github_source/minicv2/include/fft.h new file mode 100644 index 0000000..9118509 --- /dev/null +++ b/github_source/minicv2/include/fft.h @@ -0,0 +1,55 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013-2016 Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * FFT LIB - can do 1024 point real FFTs and 512 point complex FFTs + * + */ +#ifndef __FFT_H__ +#define __FFT_H__ +#include +#include "imlib.h" +#ifdef __cplusplus +extern "C" +{ +#endif +typedef struct fft1d_controller { + uint8_t *d_pointer; + int d_len; + int pow2; + float *data; +} fft1d_controller_t; +void fft1d_alloc(fft1d_controller_t *controller, uint8_t *buf, int len); +void fft1d_dealloc(); +void fft1d_run(fft1d_controller_t *controller); +void ifft1d_run(fft1d_controller_t *controller); +void fft1d_mag(fft1d_controller_t *controller); +void fft1d_phase(fft1d_controller_t *controller); +void fft1d_log(fft1d_controller_t *controller); +void fft1d_exp(fft1d_controller_t *controller); +void fft1d_swap(fft1d_controller_t *controller); // a.k.a MATLAB fftshift +void fft1d_run_again(fft1d_controller_t *controller); // Do FFT again on real mag/phase of the FFT. +typedef struct fft2d_controller { + image_t *img; + rectangle_t r; + int w_pow2, h_pow2; + float *data; +} fft2d_controller_t; +void fft2d_alloc(fft2d_controller_t *controller, image_t *img, rectangle_t *r); +void fft2d_dealloc(void *msm); +void fft2d_run(fft2d_controller_t *controller); +void ifft2d_run(fft2d_controller_t *controller); +void fft2d_mag(fft2d_controller_t *controller); +void fft2d_phase(fft2d_controller_t *controller); +void fft2d_log(fft2d_controller_t *controller); +void fft2d_exp(fft2d_controller_t *controller); +void fft2d_swap(fft2d_controller_t *controller); // a.k.a MATLAB fftshift +void fft2d_linpolar(fft2d_controller_t *controller); +void fft2d_logpolar(fft2d_controller_t *controller); +void fft2d_run_again(fft2d_controller_t *controller); // Do FFT again on real mag/phase of the FFT. +// END +#ifdef __cplusplus +} +#endif +#endif /* __FFT_H__ */ diff --git a/github_source/minicv2/include/fmath.h b/github_source/minicv2/include/fmath.h new file mode 100644 index 0000000..fe988d9 --- /dev/null +++ b/github_source/minicv2/include/fmath.h @@ -0,0 +1,48 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Fast approximate math functions. + */ +#ifndef __FMATH_H__ +#define __FMATH_H__ +#include +#include +#include +#ifdef __cplusplus +extern "C" +{ +#endif + + +#include + +float fast_sqrtf(float x); +int fast_floorf(float x); +int fast_ceilf(float x); +int fast_roundf(float x); +float fast_fabsf(float x); + + + + +float fast_atanf(float x); +float fast_atan2f(float y, float x); +float fast_expf(float x); +float fast_cbrtf(float d); +float fast_log(float x); +float fast_log2(float x); +float fast_powf(float a, float b); +void fast_get_min_max(float *data, size_t data_len, float *p_min, float *p_max); +extern const float cos_table[360]; +extern const float sin_table[360]; +void fmath_init(); +uint32_t rng_randint(uint32_t min, uint32_t max); +#ifdef __cplusplus +} +#endif +#endif // __FMATH_H__ diff --git a/github_source/minicv2/include/font.h b/github_source/minicv2/include/font.h new file mode 100644 index 0000000..2cd542a --- /dev/null +++ b/github_source/minicv2/include/font.h @@ -0,0 +1,27 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Font data. + */ +#ifndef __FONT_H__ +#define __FONT_H__ +#include +#ifdef __cplusplus +extern "C" +{ +#endif +typedef struct { + int w; + int h; + uint8_t data[10]; +} glyph_t; +extern const glyph_t font[95]; +#ifdef __cplusplus +} +#endif +#endif // __FONT_H__ diff --git a/github_source/minicv2/include/font_ttf.h b/github_source/minicv2/include/font_ttf.h new file mode 100644 index 0000000..2d26a3a --- /dev/null +++ b/github_source/minicv2/include/font_ttf.h @@ -0,0 +1,41 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Font data. + * + */ +#ifndef __FONT_TTF_H__ +#define __FONT_TTF_H__ +#include +#include +#include +#include "fmath.h" +// #include "imdefs.h" +#include "imlib.h" +#ifdef __cplusplus +extern "C" +{ +#endif +enum FontIndex { + ASCII, + Unicode, + UTF8, + GBK, + GB2312, +}; + +enum FontSource { + BuildIn, + FileIn, + StringIO, + ArrayIn, +}; + +void font_load(uint8_t index, uint8_t width, uint8_t high, uint8_t source_type, void *src_addr); +void font_free(); +#ifdef __cplusplus +} +#endif +#endif // __FONT_H__ diff --git a/github_source/minicv2/include/fsort.h b/github_source/minicv2/include/fsort.h new file mode 100644 index 0000000..e0fd05f --- /dev/null +++ b/github_source/minicv2/include/fsort.h @@ -0,0 +1,20 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013-2016 Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Fast 9 and 25 bin sort. + * + */ +#ifndef __FSORT_H__ +#define __FSORT_H__ +#include +#ifdef __cplusplus +extern "C" +{ +#endif +void fsort(int *data, int n); +#ifdef __cplusplus +} +#endif +#endif /* __FSORT_H__ */ diff --git a/github_source/minicv2/include/imlib.h b/github_source/minicv2/include/imlib.h new file mode 100644 index 0000000..866d2a1 --- /dev/null +++ b/github_source/minicv2/include/imlib.h @@ -0,0 +1,1613 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Image processing library. + */ +#ifndef __IMLIB_H__ +#define __IMLIB_H__ +#include +#include +#include +#include +#include +#include +#include +#include +// #include +// #include +#include "fb_alloc.h" +#include "umm_malloc.h" +#include "xalloc.h" +#include "array.h" +#include "fmath.h" +#include "collections.h" +#include "imlib_config.h" +#include "omv_boardconfig.h" +#include "ff_wrapper.h" +#include "imlib_io.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifndef M_PI +#define M_PI 3.14159265f +#define M_PI_2 1.57079632f +#define M_PI_4 0.78539816f +#endif + +#define IM_LOG2_2(x) (((x) & 0x2ULL) ? ( 2 ) : 1) // NO ({ ... }) ! +#define IM_LOG2_4(x) (((x) & 0xCULL) ? ( 2 + IM_LOG2_2((x) >> 2)) : IM_LOG2_2(x)) // NO ({ ... }) ! +#define IM_LOG2_8(x) (((x) & 0xF0ULL) ? ( 4 + IM_LOG2_4((x) >> 4)) : IM_LOG2_4(x)) // NO ({ ... }) ! +#define IM_LOG2_16(x) (((x) & 0xFF00ULL) ? ( 8 + IM_LOG2_8((x) >> 8)) : IM_LOG2_8(x)) // NO ({ ... }) ! +#define IM_LOG2_32(x) (((x) & 0xFFFF0000ULL) ? (16 + IM_LOG2_16((x) >> 16)) : IM_LOG2_16(x)) // NO ({ ... }) ! +#define IM_LOG2(x) (((x) & 0xFFFFFFFF00000000ULL) ? (32 + IM_LOG2_32((x) >> 32)) : IM_LOG2_32(x)) // NO ({ ... }) ! + +#define IM_MAX(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) +#define IM_MIN(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) +#define IM_DIV(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _b ? (_a / _b) : 0; }) +#define IM_MOD(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _b ? (_a % _b) : 0; }) +#define IM_LIMIT(a, min_b, max_c) ({__typeof__ (a) _a = (a);__typeof__ (min_b) _min_b = (min_b);__typeof__ (max_c) _max_c = (max_c); _a = _a > max_c ? max_c : _a;_a < min_b ? min_b : _a;}) + +#define INT8_T_BITS (sizeof(int8_t) * 8) +#define INT8_T_MASK (INT8_T_BITS - 1) +#define INT8_T_SHIFT IM_LOG2(INT8_T_MASK) + +#define INT16_T_BITS (sizeof(int16_t) * 8) +#define INT16_T_MASK (INT16_T_BITS - 1) +#define INT16_T_SHIFT IM_LOG2(INT16_T_MASK) + +#define INT32_T_BITS (sizeof(int32_t) * 8) +#define INT32_T_MASK (INT32_T_BITS - 1) +#define INT32_T_SHIFT IM_LOG2(INT32_T_MASK) + +#define INT64_T_BITS (sizeof(int64_t) * 8) +#define INT64_T_MASK (INT64_T_BITS - 1) +#define INT64_T_SHIFT IM_LOG2(INT64_T_MASK) + +#define UINT8_T_BITS (sizeof(uint8_t) * 8) +#define UINT8_T_MASK (UINT8_T_BITS - 1) +#define UINT8_T_SHIFT IM_LOG2(UINT8_T_MASK) + +#define UINT16_T_BITS (sizeof(uint16_t) * 8) +#define UINT16_T_MASK (UINT16_T_BITS - 1) +#define UINT16_T_SHIFT IM_LOG2(UINT16_T_MASK) + +#define UINT32_T_BITS (sizeof(uint32_t) * 8) +#define UINT32_T_MASK (UINT32_T_BITS - 1) +#define UINT32_T_SHIFT IM_LOG2(UINT32_T_MASK) + +#define UINT64_T_BITS (sizeof(uint64_t) * 8) +#define UINT64_T_MASK (UINT64_T_BITS - 1) +#define UINT64_T_SHIFT IM_LOG2(UINT64_T_MASK) + +#define IM_DEG2RAD(x) (((x)*M_PI)/180) +#define IM_RAD2DEG(x) (((x)*180)/M_PI) + + +///////////////// +// pixel Stuff // +///////////////// + + + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +// ARGB + +typedef struct pixel_s { + char blue; + char green; + char red; +} pixel24_t; + +#define pixel24232(_u24_t) \ +({\ + __typeof__ (_u24_t) ___u24_t = _u24_t;\ + ((*((uint32_t*)((void*)&___u24_t))) & 0xffffff00);\ +}) +//input_ uint32_t,output pixel24_t +#define pixel32224(_u32_t) \ +({\ + __typeof__ (_u32_t) __u32_t = _u32_t;\ + __u32_t = __u32_t >> 8;\ + (*((pixel24_t*)((void*)&__u32_t)));\ +}) +void nihao() +{ + printf("t am __ORDER_BIG_ENDIAN__ \n"); +} +#else +//cpu is little +//input pixel24_t,output uint32_t +// BGRA + +typedef struct pixel_s { + uint8_t red; + uint8_t green; + uint8_t blue; +} pixel24_t; + +#define pixel24232(_u24_t) \ +({\ + __typeof__ (_u24_t) ___u24_t = _u24_t;\ + ((*((uint32_t*)&___u24_t)) & 0x00ffffff);\ +}) +//input_ uint32_t,output pixel24_t +#define pixel32224(_u32_t) \ +({\ + __typeof__ (_u32_t) __u32_t = _u32_t;\ + (*((pixel24_t*)&__u32_t));\ +}) +#endif //__BYTE_ORDER__ + + + +///////////////// +// Point Stuff // +///////////////// + +typedef struct point { + int16_t x; + int16_t y; +} point_t; + +void point_init(point_t *ptr, int x, int y); +void point_copy(point_t *dst, point_t *src); +bool point_equal_fast(point_t *ptr0, point_t *ptr1); +int point_quadrance(point_t *ptr0, point_t *ptr1); +int point_quadrance_i(point_t *ptr0, point_t *ptr1); +void point_rotate(int x, int y, float r, int center_x, int center_y, int16_t *new_x, int16_t *new_y); +void point_min_area_rectangle(point_t *corners, point_t *new_corners, int corners_len); + +//////////////// +// Line Stuff // +//////////////// + +typedef struct line { + int16_t x1; + int16_t y1; + int16_t x2; + int16_t y2; +} line_t; + +bool lb_clip_line(line_t *l, int x, int y, int w, int h); + +///////////////////// +// Rectangle Stuff // +///////////////////// + +typedef struct rectangle { + int16_t x; + int16_t y; + int16_t w; + int16_t h; +} rectangle_t; + +void rectangle_init(rectangle_t *ptr, int x, int y, int w, int h); +void rectangle_copy(rectangle_t *dst, rectangle_t *src); +bool rectangle_equal_fast(rectangle_t *ptr0, rectangle_t *ptr1); +bool rectangle_overlap(rectangle_t *ptr0, rectangle_t *ptr1); +void rectangle_intersected(rectangle_t *dst, rectangle_t *src); +void rectangle_united(rectangle_t *dst, rectangle_t *src); + +///////////////// +// Color Stuff // +///////////////// + +typedef struct color_thresholds_list_lnk_data +{ + uint8_t LMin, LMax; // or grayscale + int8_t AMin, AMax; + int8_t BMin, BMax; +} +color_thresholds_list_lnk_data_t; + +#define COLOR_THRESHOLD_BINARY(pixel, threshold, invert) \ +({ \ + __typeof__ (pixel) _pixel = (pixel); \ + __typeof__ (threshold) _threshold = (threshold); \ + __typeof__ (invert) _invert = (invert); \ + ((_threshold->LMin <= _pixel) && (_pixel <= _threshold->LMax)) ^ _invert; \ +}) + +#define COLOR_THRESHOLD_GRAYSCALE(pixel, threshold, invert) \ +({ \ + __typeof__ (pixel) _pixel = (pixel); \ + __typeof__ (threshold) _threshold = (threshold); \ + __typeof__ (invert) _invert = (invert); \ + ((_threshold->LMin <= _pixel) && (_pixel <= _threshold->LMax)) ^ _invert; \ +}) + +#define COLOR_THRESHOLD_RGB565(pixel, threshold, invert) \ +({ \ + __typeof__ (pixel) _pixel = (pixel); \ + __typeof__ (threshold) _threshold = (threshold); \ + __typeof__ (invert) _invert = (invert); \ + uint8_t _l = COLOR_RGB565_TO_L(_pixel); \ + int8_t _a = COLOR_RGB565_TO_A(_pixel); \ + int8_t _b = COLOR_RGB565_TO_B(_pixel); \ + ((_threshold->LMin <= _l) && (_l <= _threshold->LMax) && \ + (_threshold->AMin <= _a) && (_a <= _threshold->AMax) && \ + (_threshold->BMin <= _b) && (_b <= _threshold->BMax)) ^ _invert; \ +}) +#define COLOR_THRESHOLD_RGB888(pixel, threshold, invert) \ +({\ + __typeof__(pixel) _pixel = (pixel); \ + __typeof__(threshold) _threshold = (threshold); \ + __typeof__(invert) _invert = (invert); \ + uint8_t _l = COLOR_RGB888_TO_L(_pixel); \ + int8_t _a = COLOR_RGB888_TO_A(_pixel); \ + int8_t _b = COLOR_RGB888_TO_B(_pixel); \ + ((_threshold->LMin <= _l) && (_l <= _threshold->LMax) && \ + (_threshold->AMin <= _a) && (_a <= _threshold->AMax) && \ + (_threshold->BMin <= _b) && (_b <= _threshold->BMax)) ^ _invert; \ +}) +#define COLOR_THRESHOLD_LAB(pixel, threshold, invert) \ +({\ + __typeof__(pixel) _pixel = (pixel); \ + __typeof__(threshold) _threshold = (threshold); \ + __typeof__(invert) _invert = (invert); \ + ((_threshold->LMin <= _pixel->L) && (_pixel->L <= _threshold->LMax) && \ + (_threshold->AMin <= _pixel->A) && (_pixel->A <= _threshold->AMax) && \ + (_threshold->BMin <= _pixel->B) && (_pixel->B <= _threshold->BMax)) ^ _invert; \ +}) +#define COLOR_BOUND_BINARY(pixel0, pixel1, threshold) \ +({ \ + __typeof__ (pixel0) _pixel0 = (pixel0); \ + __typeof__ (pixel1) _pixel1 = (pixel1); \ + __typeof__ (threshold) _threshold = (threshold); \ + (abs(_pixel0 - _pixel1) <= _threshold); \ +}) + +#define COLOR_BOUND_GRAYSCALE(pixel0, pixel1, threshold) \ +({ \ + __typeof__ (pixel0) _pixel0 = (pixel0); \ + __typeof__ (pixel1) _pixel1 = (pixel1); \ + __typeof__ (threshold) _threshold = (threshold); \ + (abs(_pixel0 - _pixel1) <= _threshold); \ +}) + +#define COLOR_BOUND_RGB565(pixel0, pixel1, threshold) \ +({ \ + __typeof__ (pixel0) _pixel0 = (pixel0); \ + __typeof__ (pixel1) _pixel1 = (pixel1); \ + __typeof__ (threshold) _threshold = (threshold); \ + (abs(COLOR_RGB565_TO_R5(_pixel0) - COLOR_RGB565_TO_R5(_pixel1)) <= COLOR_RGB565_TO_R5(_threshold)) && \ + (abs(COLOR_RGB565_TO_G6(_pixel0) - COLOR_RGB565_TO_G6(_pixel1)) <= COLOR_RGB565_TO_G6(_threshold)) && \ + (abs(COLOR_RGB565_TO_B5(_pixel0) - COLOR_RGB565_TO_B5(_pixel1)) <= COLOR_RGB565_TO_B5(_threshold)); \ +}) +#define COLOR_BOUND_RGB888(pixel0, pixel1, threshold) \ +({ \ + __typeof__(pixel0) _pixel0 = (pixel0); \ + __typeof__(pixel1) _pixel1 = (pixel1); \ + __typeof__(threshold) _threshold = (threshold); \ + (abs(COLOR_RGB888_TO_R8(_pixel0) - COLOR_RGB888_TO_R8(_pixel1)) <= COLOR_RGB888_TO_R8(_threshold)) && \ + (abs(COLOR_RGB888_TO_G8(_pixel0) - COLOR_RGB888_TO_G8(_pixel1)) <= COLOR_RGB888_TO_G8(_threshold)) && \ + (abs(COLOR_RGB888_TO_B8(_pixel0) - COLOR_RGB888_TO_B8(_pixel1)) <= COLOR_RGB888_TO_B8(_threshold)); \ +}) +#define COLOR_BINARY_MIN 0 +#define COLOR_BINARY_MAX 1 +#define COLOR_GRAYSCALE_BINARY_MIN 0x00 +#define COLOR_GRAYSCALE_BINARY_MAX 0xFF +#define COLOR_RGB565_BINARY_MIN 0x0000 +#define COLOR_RGB565_BINARY_MAX 0xFFFF +#define COLOR_RGB888_BINARY_MIN 0x000000 +#define COLOR_RGB888_BINARY_MAX 0xFFFFFF + +#define COLOR_GRAYSCALE_MIN 0 +#define COLOR_GRAYSCALE_MAX 255 + +#define COLOR_R5_MIN 0 +#define COLOR_R5_MAX 31 +#define COLOR_G6_MIN 0 +#define COLOR_G6_MAX 63 +#define COLOR_B5_MIN 0 +#define COLOR_B5_MAX 31 + +#define COLOR_R8_MIN 0 +#define COLOR_R8_MAX 255 +#define COLOR_G8_MIN 0 +#define COLOR_G8_MAX 255 +#define COLOR_B8_MIN 0 +#define COLOR_B8_MAX 255 + +#define COLOR_L_MIN 0 +#define COLOR_L_MAX 100 +#define COLOR_A_MIN -128 +#define COLOR_A_MAX 127 +#define COLOR_B_MIN -128 +#define COLOR_B_MAX 127 + +#define COLOR_Y_MIN 0 +#define COLOR_Y_MAX 255 +#define COLOR_U_MIN -128 +#define COLOR_U_MAX 127 +#define COLOR_V_MIN -128 +#define COLOR_V_MAX 127 + +// RGB565 Stuff // + +#define COLOR_RGB565_TO_R5(pixel) (((pixel) >> 11) & 0x1F) +#define COLOR_RGB565_TO_R8(pixel) \ +({ \ + __typeof__ (pixel) __pixel = (pixel); \ + __pixel = (__pixel >> 8) & 0xF8; \ + __pixel | (__pixel >> 5); \ +}) + +#define COLOR_RGB565_TO_G6(pixel) (((pixel) >> 5) & 0x3F) +#define COLOR_RGB565_TO_G8(pixel) \ +({ \ + __typeof__ (pixel) __pixel = (pixel); \ + __pixel = (__pixel >> 3) & 0xFC; \ + __pixel | (__pixel >> 6); \ +}) + +#define COLOR_RGB565_TO_B5(pixel) ((pixel) & 0x1F) +#define COLOR_RGB565_TO_B8(pixel) \ +({ \ + __typeof__ (pixel) __pixel = (pixel); \ + __pixel = (__pixel << 3) & 0xF8; \ + __pixel | (__pixel >> 5); \ +}) + +#define COLOR_RGB888_TO_R8(pixel) pixel32224(pixel).red +#define COLOR_RGB888_TO_G8(pixel) pixel32224(pixel).green +#define COLOR_RGB888_TO_B8(pixel) pixel32224(pixel).blue + +#define COLOR_R5_G6_B5_TO_RGB565(r5, g6, b5) (((r5) << 11) | ((g6) << 5) | (b5)) +#define COLOR_R8_G8_B8_TO_RGB565(r8, g8, b8) ((((r8) & 0xF8) << 8) | (((g8) & 0xFC) << 3) | ((b8) >> 3)) + +#define COLOR_R8_G8_B8_TO_RGB888(r8, g8, b8) pixel24232(((pixel24_t){.red = r8,.green = g8, .blue = b8})) + +#define COLOR_RGB888_TO_Y_(r8, g8, b8) ((((r8) * 38) + ((g8) * 75) + ((b8) * 15)) >> 7) // 0.299R + 0.587G + 0.114B + +#define COLOR_RGB888_TO_Y(rgb888) COLOR_RGB888_TO_Y_(COLOR_RGB888_TO_R8(rgb888), COLOR_RGB888_TO_G8(rgb888), COLOR_RGB888_TO_B8(rgb888)) // 0.299R + 0.587G + 0.114B + +#define COLOR_RGB565_TO_Y(rgb565) \ +({ \ + __typeof__ (rgb565) __rgb565 = (rgb565); \ + int r = COLOR_RGB565_TO_R8(__rgb565); \ + int g = COLOR_RGB565_TO_G8(__rgb565); \ + int b = COLOR_RGB565_TO_B8(__rgb565); \ + COLOR_RGB888_TO_Y_(r, g, b); \ +}) + +#define COLOR_Y_TO_RGB888(pixel) ((pixel) * 0x010101) +#define COLOR_Y_TO_RGB565(pixel) \ +({ \ + __typeof__ (pixel) __pixel = (pixel); \ + int __rb_pixel = (__pixel >> 3) & 0x1F; \ + (__rb_pixel * 0x0801) + ((__pixel << 3) & 0x7E0); \ +}) +#define COLOR_RGB888_TO_U_(r8, g8, b8) ((((r8) * -21) - ((g8) * 43) + ((b8) * 64)) >> 7) // -0.168736R - 0.331264G + 0.5B +#define COLOR_RGB888_TO_U(rgb888) COLOR_RGB888_TO_U_(COLOR_RGB888_TO_R8(rgb888), COLOR_RGB888_TO_G8(rgb888), COLOR_RGB888_TO_B8(rgb888)) // -0.168736R - 0.331264G + 0.5B +#define COLOR_RGB565_TO_U(rgb565) \ +({ \ + __typeof__ (rgb565) __rgb565 = (rgb565); \ + int r = COLOR_RGB565_TO_R8(__rgb565); \ + int g = COLOR_RGB565_TO_G8(__rgb565); \ + int b = COLOR_RGB565_TO_B8(__rgb565); \ + COLOR_RGB888_TO_U_(r, g, b); \ +}) +#define COLOR_RGB888_TO_V_(r8, g8, b8) ((((r8) * 64) - ((g8) * 54) - ((b8) * 10)) >> 7) // 0.5R - 0.418688G - 0.081312B +#define COLOR_RGB888_TO_V(rgb888) COLOR_RGB888_TO_V_(COLOR_RGB888_TO_R8(rgb888), COLOR_RGB888_TO_G8(rgb888), COLOR_RGB888_TO_B8(rgb888)) // 0.5R - 0.418688G - 0.081312B +#define COLOR_RGB565_TO_V(rgb565) \ +({ \ + __typeof__ (rgb565) __rgb565 = (rgb565); \ + int r = COLOR_RGB565_TO_R8(__rgb565); \ + int g = COLOR_RGB565_TO_G8(__rgb565); \ + int b = COLOR_RGB565_TO_B8(__rgb565); \ + COLOR_RGB888_TO_V_(r, g, b); \ +}) + + + +#ifdef IMLIB_ENABLE_LAB_LUT +extern const int8_t lab_table[196608/2]; +#define COLOR_RGB565_TO_L(pixel) lab_table[((pixel>>1) * 3) + 0] +#define COLOR_RGB565_TO_A(pixel) lab_table[((pixel>>1) * 3) + 1] +#define COLOR_RGB565_TO_B(pixel) lab_table[((pixel>>1) * 3) + 2] +#else +#define COLOR_RGB565_TO_L(pixel) imlib_rgb565_to_l(pixel) +#define COLOR_RGB565_TO_A(pixel) imlib_rgb565_to_a(pixel) +#define COLOR_RGB565_TO_B(pixel) imlib_rgb565_to_b(pixel) +#define COLOR_RGB888_TO_L(pixel) imlib_rgb888_to_l(pixel) +#define COLOR_RGB888_TO_A(pixel) imlib_rgb888_to_a(pixel) +#define COLOR_RGB888_TO_B(pixel) imlib_rgb888_to_b(pixel) +#endif + +#define COLOR_LAB_TO_RGB565(l, a, b) imlib_lab_to_rgb(l, a, b) +#define COLOR_YUV_TO_RGB565(y, u, v) imlib_yuv_to_rgb((y) + 128, u, v) + +#define COLOR_LAB_TO_RGB888(l, a, b) imlib_lab_to_rgb888(l, a, b) +#define COLOR_YUV_TO_RGB888(y, u, v) imlib_yuv_to_rgb888((y) + 128, u, v) + +#define COLOR_BINARY_TO_GRAYSCALE(pixel) ((pixel) * COLOR_GRAYSCALE_MAX) +#define COLOR_BINARY_TO_RGB565(pixel) COLOR_YUV_TO_RGB565(((pixel) ? 127 : -128), 0, 0) +#define COLOR_BINARY_TO_RGB888(pixel) COLOR_YUV_TO_RGB888(((pixel) ? 127 : -128), 0, 0) +#define COLOR_RGB565_TO_BINARY(pixel) (COLOR_RGB565_TO_Y(pixel) > (((COLOR_Y_MAX - COLOR_Y_MIN) / 2) + COLOR_Y_MIN)) +#define COLOR_RGB565_TO_GRAYSCALE(pixel) COLOR_RGB565_TO_Y(pixel) +#define COLOR_RGB565_TO_RGB888(pixel) COLOR_R8_G8_B8_TO_RGB888(COLOR_RGB565_TO_R8(pixel), COLOR_RGB565_TO_G8(pixel), COLOR_RGB565_TO_B8(pixel)) +#define COLOR_RGB888_TO_GRAYSCALE(pixel) COLOR_RGB888_TO_Y(pixel) +#define COLOR_RGB888_TO_BINARY(pixel) (COLOR_RGB888_TO_Y(pixel) > (((COLOR_Y_MAX - COLOR_Y_MIN) / 2) + COLOR_Y_MIN)) +#define COLOR_RGB888_TO_RGB565(pixel) COLOR_R8_G8_B8_TO_RGB565(COLOR_RGB888_TO_R8(pixel), COLOR_RGB888_TO_G8(pixel), COLOR_RGB888_TO_B8(pixel)) + +#define COLOR_GRAYSCALE_TO_BINARY(pixel) ((pixel) > (((COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN) / 2) + COLOR_GRAYSCALE_MIN)) +#define COLOR_GRAYSCALE_TO_RGB565(pixel) COLOR_YUV_TO_RGB565(((pixel) - 128), 0, 0) +#define COLOR_GRAYSCALE_TO_RGB888(pixel) COLOR_YUV_TO_RGB888(((pixel) - 128), 0, 0) + +typedef enum { + COLOR_PALETTE_RAINBOW, + COLOR_PALETTE_IRONBOW +} color_palette_t; + +// Color palette LUTs +extern const uint16_t rainbow_table[256]; +extern const uint16_t ironbow_table[256]; + +///////////////// +// Image Stuff // +///////////////// + +// Pixel format IDs. +typedef enum { + PIXFORMAT_ID_BINARY = 1, + PIXFORMAT_ID_GRAY = 2, + PIXFORMAT_ID_RGB565 = 3, + PIXFORMAT_ID_BAYER = 4, + PIXFORMAT_ID_YUV422 = 5, + PIXFORMAT_ID_JPEG = 6, + PIXFORMAT_ID_RGB888 = 7, + PIXFORMAT_ID_PNG = 9, + PIXFORMAT_ID_ARGB8 = 8, + + /* Note: Update PIXFORMAT_IS_VALID when adding new formats */ +} pixformat_id_t; + +// Pixel sub-format IDs. +typedef enum { + SUBFORMAT_ID_GRAY8 = 0, + SUBFORMAT_ID_GRAY16 = 1, + SUBFORMAT_ID_BGGR = 0, // !!! Note: Make sure bayer sub-formats don't !!! + SUBFORMAT_ID_GBRG = 1, // !!! overflow the sensor.hw_flags.bayer field !!! + SUBFORMAT_ID_GRBG = 2, + SUBFORMAT_ID_RGGB = 3, + SUBFORMAT_ID_YUV422 = 0, + SUBFORMAT_ID_YVU422 = 1, + /* Note: Update PIXFORMAT_IS_VALID when adding new formats */ +} subformat_id_t; + +// Pixel format Byte Per Pixel. +typedef enum { + PIXFORMAT_BPP_BINARY = 0, + PIXFORMAT_BPP_GRAY8 = 1, + PIXFORMAT_BPP_GRAY16 = 2, + PIXFORMAT_BPP_RGB565 = 2, + PIXFORMAT_BPP_BAYER = 1, + PIXFORMAT_BPP_YUV422 = 2, + PIXFORMAT_BPP_RGB888 = 3, + PIXFORMAT_BPP_ARGB8 = 4, + /* Note: Update PIXFORMAT_IS_VALID when adding new formats */ +} pixformat_bpp_t; + +// Pixel format flags. +#define PIXFORMAT_FLAGS_Y (1 << 28) // YUV format. +#define PIXFORMAT_FLAGS_M (1 << 27) // Mutable format. +#define PIXFORMAT_FLAGS_C (1 << 26) // Colored format. +#define PIXFORMAT_FLAGS_J (1 << 25) // Compressed format (JPEG/PNG). +#define PIXFORMAT_FLAGS_R (1 << 24) // RAW/Bayer format. +#define PIXFORMAT_FLAGS_CY (PIXFORMAT_FLAGS_C | PIXFORMAT_FLAGS_Y) +#define PIXFORMAT_FLAGS_CM (PIXFORMAT_FLAGS_C | PIXFORMAT_FLAGS_M) +#define PIXFORMAT_FLAGS_CR (PIXFORMAT_FLAGS_C | PIXFORMAT_FLAGS_R) +#define PIXFORMAT_FLAGS_CJ (PIXFORMAT_FLAGS_C | PIXFORMAT_FLAGS_J) +#define IMLIB_IMAGE_MAX_SIZE(x) ((x) & 0xFFFFFFFF) + +// Each pixel format encodes flags, pixel format id and bpp as follows: +// 31......29 28 27 26 25 24 23..........16 15...........8 7.............0 +// YF MF CF JF RF +// NOTE: Bit 31-30 must Not be used for pixformat_t to be used as mp_int_t. +typedef enum { + PIXFORMAT_INVALID = (0x00000000U), + PIXFORMAT_BINARY = (PIXFORMAT_FLAGS_M | (PIXFORMAT_ID_BINARY << 16) | (0 << 8) | PIXFORMAT_BPP_BINARY ), + PIXFORMAT_GRAYSCALE = (PIXFORMAT_FLAGS_M | (PIXFORMAT_ID_GRAY << 16) | (SUBFORMAT_ID_GRAY8 << 8) | PIXFORMAT_BPP_GRAY8 ), + PIXFORMAT_RGB565 = (PIXFORMAT_FLAGS_CM | (PIXFORMAT_ID_RGB565 << 16) | (0 << 8) | PIXFORMAT_BPP_RGB565 ), + PIXFORMAT_ARGB8 = (PIXFORMAT_FLAGS_CM | (PIXFORMAT_ID_ARGB8 << 16) | (0 << 8) | PIXFORMAT_BPP_ARGB8 ), + PIXFORMAT_BAYER = (PIXFORMAT_FLAGS_CR | (PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_BGGR << 8) | PIXFORMAT_BPP_BAYER ), + PIXFORMAT_BAYER_BGGR = (PIXFORMAT_FLAGS_CR | (PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_BGGR << 8) | PIXFORMAT_BPP_BAYER ), + PIXFORMAT_BAYER_GBRG = (PIXFORMAT_FLAGS_CR | (PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_GBRG << 8) | PIXFORMAT_BPP_BAYER ), + PIXFORMAT_BAYER_GRBG = (PIXFORMAT_FLAGS_CR | (PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_GRBG << 8) | PIXFORMAT_BPP_BAYER ), + PIXFORMAT_BAYER_RGGB = (PIXFORMAT_FLAGS_CR | (PIXFORMAT_ID_BAYER << 16) | (SUBFORMAT_ID_RGGB << 8) | PIXFORMAT_BPP_BAYER ), + PIXFORMAT_YUV = (PIXFORMAT_FLAGS_CY | (PIXFORMAT_ID_YUV422 << 16) | (SUBFORMAT_ID_YUV422 << 8) | PIXFORMAT_BPP_YUV422 ), + PIXFORMAT_YUV422 = (PIXFORMAT_FLAGS_CY | (PIXFORMAT_ID_YUV422 << 16) | (SUBFORMAT_ID_YUV422 << 8) | PIXFORMAT_BPP_YUV422 ), + PIXFORMAT_YVU422 = (PIXFORMAT_FLAGS_CY | (PIXFORMAT_ID_YUV422 << 16) | (SUBFORMAT_ID_YVU422 << 8) | PIXFORMAT_BPP_YUV422 ), + PIXFORMAT_JPEG = (PIXFORMAT_FLAGS_CJ | (PIXFORMAT_ID_JPEG << 16) | (0 << 8) | 0 ), + PIXFORMAT_PNG = (PIXFORMAT_FLAGS_CJ | (PIXFORMAT_ID_PNG << 16) | (0 << 8) | 0 ), + PIXFORMAT_RGB888 = (PIXFORMAT_FLAGS_CM | (PIXFORMAT_ID_RGB888 << 16) | (0 << 8) | PIXFORMAT_BPP_RGB888 ), + PIXFORMAT_LAST = (0xFFFFFFFFU), +} pixformat_t; + +#define PIXFORMAT_MUTABLE_ANY \ + PIXFORMAT_BINARY: \ + case PIXFORMAT_GRAYSCALE: \ + case PIXFORMAT_RGB565: \ + case PIXFORMAT_ARGB8: \ + case PIXFORMAT_RGB888 \ + +#define PIXFORMAT_BAYER_ANY \ + PIXFORMAT_BAYER_BGGR: \ + case PIXFORMAT_BAYER_GBRG: \ + case PIXFORMAT_BAYER_GRBG: \ + case PIXFORMAT_BAYER_RGGB \ + +#define PIXFORMAT_YUV_ANY \ + PIXFORMAT_YUV422: \ + case PIXFORMAT_YVU422 \ + +#define PIXFORMAT_COMPRESSED_ANY \ + PIXFORMAT_JPEG: \ + case PIXFORMAT_PNG \ + +#define IMLIB_PIXFORMAT_IS_VALID(x) \ + ((x == PIXFORMAT_BINARY) \ + || (x == PIXFORMAT_GRAYSCALE) \ + || (x == PIXFORMAT_RGB565) \ + || (x == PIXFORMAT_ARGB8) \ + || (x == PIXFORMAT_BAYER_BGGR) \ + || (x == PIXFORMAT_BAYER_GBRG) \ + || (x == PIXFORMAT_BAYER_GRBG) \ + || (x == PIXFORMAT_BAYER_RGGB) \ + || (x == PIXFORMAT_YUV422) \ + || (x == PIXFORMAT_YVU422) \ + || (x == PIXFORMAT_JPEG) \ + || (x == PIXFORMAT_PNG) \ + || (x == PIXFORMAT_RGB888)) \ + +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +#define PIXFORMAT_STRUCT \ +struct { \ + union { \ + struct { \ + uint32_t bpp :8; \ + uint32_t subfmt_id :8; \ + uint32_t pixfmt_id :8; \ + uint32_t is_bayer :1; \ + uint32_t is_compressed :1; \ + uint32_t is_color :1; \ + uint32_t is_mutable :1; \ + uint32_t is_yuv :1; \ + uint32_t /*reserved*/ :3; \ + }; \ + uint32_t pixfmt; \ + }; \ + uint32_t size; /* for compressed images */ \ + bool is_data_alloc; /*data need to free when call destory*/\ +} +#elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) +#define PIXFORMAT_STRUCT \ +struct { \ + union { \ + struct { \ + uint32_t /*reserved*/ :3; \ + uint32_t is_yuv :1; \ + uint32_t is_mutable :1; \ + uint32_t is_color :1; \ + uint32_t is_compressed :1; \ + uint32_t is_bayer :1; \ + uint32_t pixfmt_id :8; \ + uint32_t subfmt_id :8; \ + uint32_t bpp :8; \ + }; \ + uint32_t pixfmt; \ + }; \ + uint32_t size; /* for compressed images */ \ + bool is_data_alloc; /*data need to free when call destory*/\ +} +#else +#error "Byte order is not defined." +#endif + +typedef struct image { + uint32_t w; + uint32_t h; + PIXFORMAT_STRUCT; + union { + uint8_t *pixels; + uint8_t *data; + }; +} image_t; + +image_t* imlib_image_create(int w, int h, pixformat_t pixfmt, uint32_t size, void *pixels, bool is_data_alloc); +image_t* imlib_image_create_fb_buff(int w, int h, pixformat_t pixfmt, uint32_t size, void *pixels, bool is_data_alloc); +void imlib_image_destroy_fb_buff(image_t **obj); +void imlib_image_destroy(image_t **obj); +void imlib_image_init(image_t *ptr, int w, int h, pixformat_t pixfmt, uint32_t size, void *pixels); +void image_copy(image_t *dst, image_t *src); +size_t image_size(image_t *ptr); +bool image_get_mask_pixel(image_t *ptr, int x, int y); + +#define IMAGE_BINARY_LINE_LEN(image) (((image)->w + UINT32_T_MASK) >> UINT32_T_SHIFT) +#define IMAGE_BINARY_LINE_LEN_BYTES(image) (IMAGE_BINARY_LINE_LEN(image) * sizeof(uint32_t)) + +#define IMAGE_GRAYSCALE_LINE_LEN(image) ((image)->w) +#define IMAGE_GRAYSCALE_LINE_LEN_BYTES(image) (IMAGE_GRAYSCALE_LINE_LEN(image) * sizeof(uint8_t)) + +#define IMAGE_RGB565_LINE_LEN(image) ((image)->w) +#define IMAGE_RGB565_LINE_LEN_BYTES(image) (IMAGE_RGB565_LINE_LEN(image) * sizeof(uint16_t)) + +#define IMAGE_RGB888_LINE_LEN(image) ((image)->w) +#define IMAGE_RGB888_LINE_LEN_BYTES(image) (IMAGE_RGB888_LINE_LEN(image) * sizeof(pixel24_t)) + +#define IMAGE_GET_BINARY_PIXEL(image, x, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + (((uint32_t *) _image->data)[(((_image->w + UINT32_T_MASK) >> UINT32_T_SHIFT) * _y) + (_x >> UINT32_T_SHIFT)] >> (_x & UINT32_T_MASK)) & 1; \ +}) + +#define IMAGE_PUT_BINARY_PIXEL(image, x, y, v) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (v) _v = (v); \ + size_t _i = (((_image->w + UINT32_T_MASK) >> UINT32_T_SHIFT) * _y) + (_x >> UINT32_T_SHIFT); \ + size_t _j = _x & UINT32_T_MASK; \ + ((uint32_t *) _image->data)[_i] = (((uint32_t *) _image->data)[_i] & (~(1 << _j))) | ((_v & 1) << _j); \ +}) + +#define IMAGE_CLEAR_BINARY_PIXEL(image, x, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint32_t *) _image->data)[(((_image->w + UINT32_T_MASK) >> UINT32_T_SHIFT) * _y) + (_x >> UINT32_T_SHIFT)] &= ~(1 << (_x & UINT32_T_MASK)); \ +}) + +#define IMAGE_SET_BINARY_PIXEL(image, x, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint32_t *) _image->data)[(((_image->w + UINT32_T_MASK) >> UINT32_T_SHIFT) * _y) + (_x >> UINT32_T_SHIFT)] |= 1 << (_x & UINT32_T_MASK); \ +}) + +#define IMAGE_GET_GRAYSCALE_PIXEL(image, x, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint8_t *) _image->data)[(_image->w * _y) + _x]; \ +}) + +#define IMAGE_PUT_GRAYSCALE_PIXEL(image, x, y, v) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (v) _v = (v); \ + ((uint8_t *) _image->data)[(_image->w * _y) + _x] = _v; \ +}) + +#define IMAGE_GET_RGB565_PIXEL(image, x, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint16_t *) _image->data)[(_image->w * _y) + _x]; \ +}) + +#define IMAGE_PUT_RGB565_PIXEL(image, x, y, v) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (v) _v = (v); \ + ((uint16_t *) _image->data)[(_image->w * _y) + _x] = _v; \ +}) + +#define IMAGE_GET_RGB888_PIXEL_(image, x, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((pixel24_t *) _image->data)[(_image->w * _y) + _x]; \ +}) + +#define IMAGE_GET_RGB888_PIXEL(image, x, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + pixel24232(((pixel24_t *) _image->data)[(_image->w * _y) + _x]); \ +}) + +#define IMAGE_PUT_RGB888_PIXEL_(image, x, y, v) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (v) _v = (v); \ + ((pixel24_t *) _image->data)[(_image->w * _y) + _x] = _v; \ +}) + +#define IMAGE_PUT_RGB888_PIXEL(image, x, y, v) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (v) _v = (v); \ + ((pixel24_t *) _image->data)[(_image->w * _y) + _x] = pixel32224(_v); \ +}) + +#define IMAGE_GET_YUV_PIXEL(image, x, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint16_t *) _image->data)[(_image->w * _y) + _x]; \ +}) + +#define IMAGE_PUT_YUV_PIXEL(image, x, y, v) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (v) _v = (v); \ + ((uint16_t *) _image->data)[(_image->w * _y) + _x] = _v; \ +}) + +#define IMAGE_GET_BAYER_PIXEL(image, x, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint8_t *) _image->data)[(_image->w * _y) + _x]; \ +}) + +#define IMAGE_PUT_BAYER_PIXEL(image, x, y, v) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (v) _v = (v); \ + ((uint8_t *) _image->data)[(_image->w * _y) + _x] = _v; \ +}) + +// Fast Stuff // + +#define IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(image, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (y) _y = (y); \ + ((uint32_t *) _image->data) + (((_image->w + UINT32_T_MASK) >> UINT32_T_SHIFT) * _y); \ +}) + +#define IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x) \ +({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + (_row_ptr[_x >> UINT32_T_SHIFT] >> (_x & UINT32_T_MASK)) & 1; \ +}) + +#define IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, v) \ +({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + __typeof__ (v) _v = (v); \ + size_t _i = _x >> UINT32_T_SHIFT; \ + size_t _j = _x & UINT32_T_MASK; \ + _row_ptr[_i] = (_row_ptr[_i] & (~(1 << _j))) | ((_v & 1) << _j); \ +}) + +#define IMAGE_CLEAR_BINARY_PIXEL_FAST(row_ptr, x) \ +({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + _row_ptr[_x >> UINT32_T_SHIFT] &= ~(1 << (_x & UINT32_T_MASK)); \ +}) + +#define IMAGE_SET_BINARY_PIXEL_FAST(row_ptr, x) \ +({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + _row_ptr[_x >> UINT32_T_SHIFT] |= 1 << (_x & UINT32_T_MASK); \ +}) + +#define IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(image, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (y) _y = (y); \ + ((uint8_t *) _image->data) + (_image->w * _y); \ +}) + +#define IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x) \ +({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + _row_ptr[_x]; \ +}) + +#define IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, v) \ +({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + __typeof__ (v) _v = (v); \ + _row_ptr[_x] = _v; \ +}) + +#define IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(image, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (y) _y = (y); \ + ((uint16_t *) _image->data) + (_image->w * _y); \ +}) + +#define IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x) \ +({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + _row_ptr[_x]; \ +}) + +#define IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, v) \ +({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + __typeof__ (v) _v = (v); \ + _row_ptr[_x] = _v; \ +}) + +#define IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(image, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (y) _y = (y); \ + ((pixel24_t *) _image->data) + (_image->w * _y); \ +}) + +#define IMAGE_GET_RGB888_PIXEL_FAST_(row_ptr, x) \ +({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + _row_ptr[_x]; \ +}) + +#define IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x) \ +({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + pixel24232(_row_ptr[_x]); \ +}) + +#define IMAGE_PUT_RGB888_PIXEL_FAST_(row_ptr, x, v) \ +({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + __typeof__ (v) _v = (v); \ + _row_ptr[_x] = _v; \ +}) + +#define IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr, x, v) \ +({ \ + __typeof__ (row_ptr) _row_ptr = (row_ptr); \ + __typeof__ (x) _x = (x); \ + __typeof__ (v) _v = (v); \ + _row_ptr[_x] = pixel32224(_v); \ +}) + +#define IMAGE_COMPUTE_BAYER_PIXEL_ROW_PTR(image, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (y) _y = (y); \ + ((uint8_t *) _image->data) + (_image->w * _y); \ +}) + +#define IMAGE_COMPUTE_YUV_PIXEL_ROW_PTR(image, y) \ +({ \ + __typeof__ (image) _image = (image); \ + __typeof__ (y) _y = (y); \ + ((uint16_t *) _image->data) + (_image->w * _y); \ +}) + +// Old Image Macros - Will be refactor and removed. But, only after making sure through testing new macros work. + +// Image kernels +extern const int8_t kernel_gauss_3[9]; +extern const int8_t kernel_gauss_5[25]; +extern const int kernel_laplacian_3[9]; +extern const int kernel_high_pass_3[9]; + +// Grayscale maxes +#define IM_MAX_GS (255) + +#define IM_IS_BINARY(img) ((img)->pixfmt == PIXFORMAT_BINARY) +#define IM_IS_GS(img) ((img)->pixfmt == PIXFORMAT_GRAYSCALE) +#define IM_IS_RGB565(img) ((img)->pixfmt == PIXFORMAT_RGB565) +#define IM_IS_BAYER(img) ((img)->is_bayer) +#define IM_IS_JPEG(img) ((img)->pixfmt == PIXFORMAT_JPEG) +#define IM_IS_RGB888(img) ((img)->pixfmt == PIXFORMAT_RGB888) + +#define IM_X_INSIDE(img, x) \ + ({ __typeof__ (img) _img = (img); \ + __typeof__ (x) _x = (x); \ + (0<=_x)&&(_x<_img->w); }) + +#define IM_Y_INSIDE(img, y) \ + ({ __typeof__ (img) _img = (img); \ + __typeof__ (y) _y = (y); \ + (0<=_y)&&(_y<_img->h); }) + +#define IM_GET_GS_PIXEL(img, x, y) \ + ({ __typeof__ (img) _img = (img); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint8_t*)_img->pixels)[(_y*_img->w)+_x]; }) + +#define IM_GET_RGB565_PIXEL(img, x, y) \ + ({ __typeof__ (img) _img = (img); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((uint16_t*)_img->pixels)[(_y*_img->w)+_x]; }) + +#define IM_GET_RGB888_PIXEL(img, x, y) \ + ({ __typeof__ (img) _img = (img); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + pixel24232(((pixel24_t*)_img->pixels)[(_y*_img->w)+_x]); }) +#define IM_GET_RGB888_PIXEL_(img, x, y) \ + ({ __typeof__ (img) _img = (img); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + ((pixel24_t*)_img->pixels)[(_y*_img->w)+_x]; }) + +#define IM_SET_GS_PIXEL(img, x, y, p) \ + ({ __typeof__ (img) _img = (img); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (p) _p = (p); \ + ((uint8_t*)_img->pixels)[(_y*_img->w)+_x]=_p; }) + +#define IM_SET_RGB565_PIXEL(img, x, y, p) \ + ({ __typeof__ (img) _img = (img); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (p) _p = (p); \ + ((uint16_t*)_img->pixels)[(_y*_img->w)+_x]=_p; }) + +#define IM_SET_RGB888_PIXEL(img, x, y, p) \ + ({ __typeof__ (img) _img = (img); \ + __typeof__ (x) _x = (x); \ + __typeof__ (y) _y = (y); \ + __typeof__ (p) _p = (p); \ + ((pixel24_t*)_img->pixels)[(_y*_img->w)+_x]=pixel32224(_p); }) + +#define IM_EQUAL(img0, img1) \ + ({ __typeof__ (img0) _img0 = (img0); \ + __typeof__ (img1) _img1 = (img1); \ + (_img0->w==_img1->w)&&(_img0->h==_img1->h)&&(_img0->pixfmt=_img1->pixfmt); }) + +#define IM_TO_GS_PIXEL(img, x, y) \ + (img->bpp == 1 ? img->pixels[((y)*img->w)+(x)] : COLOR_RGB565_TO_Y(((uint16_t*)img->pixels)[((y)*img->w)+(x)]) ) + +typedef struct simple_color { + uint8_t G; // Gray + union { + int8_t L; // LAB L + uint8_t red; // RGB888 Red + }; + union { + int8_t A; // LAB A + uint8_t green; // RGB888 Green + }; + union { + int8_t B; // LAB B + uint8_t blue; // RGB888 Blue + }; +} simple_color_t; + +typedef struct integral_image { + int w; + int h; + uint32_t *data; +} i_image_t; + +typedef struct { + int w; + int h; + int y_offs; + int x_ratio; + int y_ratio; + uint32_t **data; + uint32_t **swap; +} mw_image_t; + +typedef struct _vector { + float x; + float y; + float m; + uint16_t cx,cy; +} vec_t; + +typedef struct cluster { + int x, y, w, h; + array_t *points; +} cluster_t; + +// Return the distance between a cluster centroid and some object. +typedef float (*cluster_dist_t)(int cx, int cy, void *obj); + +/* Keypoint */ +typedef struct kp { + uint16_t x; + uint16_t y; + uint16_t score; + uint16_t octave; + uint16_t angle; + uint16_t matched; + uint8_t desc[32]; +} kp_t; + +typedef struct size { + int w; + int h; +} wsize_t; + +/* Haar cascade struct */ +typedef struct cascade { + int std; // Image standard deviation. + int step; // Image scanning factor. + float threshold; // Detection threshold. + float scale_factor; // Image scaling factor. + int n_stages; // Number of stages in the cascade. + int n_features; // Number of features in the cascade. + int n_rectangles; // Number of rectangles in the cascade. + struct size window; // Detection window size. + struct image *img; // Grayscale image. + mw_image_t *sum; // Integral image. + mw_image_t *ssq; // Squared integral image. + uint8_t *stages_array; // Number of features per stage. + int16_t *stages_thresh_array; // Stages thresholds. + int16_t *tree_thresh_array; // Features threshold (1 per feature). + int16_t *alpha1_array; // Alpha1 array (1 per feature). + int16_t *alpha2_array; // Alpha2 array (1 per feature). + int8_t *num_rectangles_array; // Number of rectangles per features (1 per feature). + int8_t *weights_array; // Rectangles weights (1 per rectangle). + int8_t *rectangles_array; // Rectangles array. +} cascade_t; + +typedef struct bmp_read_settings { + int32_t bmp_w; + int32_t bmp_h; + uint16_t bmp_bpp; + uint32_t bmp_fmt; + uint32_t bmp_row_bytes; +} bmp_read_settings_t; + +typedef struct ppm_read_settings { + uint8_t read_int_c; + bool read_int_c_valid; + uint8_t ppm_fmt; +} ppm_read_settings_t; + +typedef struct jpg_read_settings { + int32_t jpg_w; + int32_t jpg_h; + int32_t jpg_size; +} jpg_read_settings_t; + +typedef struct png_read_settings { + int32_t png_w; + int32_t png_h; + int32_t png_size; +} png_read_settings_t; + +typedef enum save_image_format { + FORMAT_DONT_CARE, + FORMAT_BMP, + FORMAT_PNM, + FORMAT_JPG, + FORMAT_PNG, + FORMAT_RAW, +} save_image_format_t; + +typedef struct img_read_settings { + union { + bmp_read_settings_t bmp_rs; + ppm_read_settings_t ppm_rs; + jpg_read_settings_t jpg_rs; + png_read_settings_t png_rs; + }; + save_image_format_t format; +} img_read_settings_t; + +typedef void (*line_op_t)(image_t*, int, void*, void*, bool); +typedef void (*flood_fill_call_back_t)(image_t *, int, int, int, void *); + +typedef enum descriptor_type { + DESC_LBP, + DESC_ORB, +} descriptor_t; + +typedef enum edge_detector_type { + EDGE_CANNY, + EDGE_SIMPLE, +} edge_detector_t; + +typedef enum template_match { + SEARCH_EX, // Exhaustive search + SEARCH_DS, // Diamond search +} template_match_t; + +typedef enum jpeg_subsample { + JPEG_SUBSAMPLE_1x1 = 0x11, // 1x1 chroma subsampling (No subsampling) + JPEG_SUBSAMPLE_2x1 = 0x21, // 2x2 chroma subsampling + JPEG_SUBSAMPLE_2x2 = 0x22, // 2x2 chroma subsampling +} jpeg_subsample_t; + +typedef enum corner_detector_type { + CORNER_FAST, + CORNER_AGAST +} corner_detector_t; + +typedef struct histogram { + int LBinCount; + float *LBins; + int ABinCount; + float *ABins; + int BBinCount; + float *BBins; +} histogram_t; + +typedef struct percentile { + uint8_t LValue; + int8_t AValue; + int8_t BValue; +} percentile_t; + +typedef struct threshold { + uint8_t LValue; + int8_t AValue; + int8_t BValue; +} threshold_t; + //平均数, 中位数, 众数, stdev, 最小值, 最大值, +typedef struct statistics { + uint8_t LMean, LMedian, LMode, LSTDev, LMin, LMax, LLQ, LUQ; + int8_t AMean, AMedian, AMode, ASTDev, AMin, AMax, ALQ, AUQ; + int8_t BMean, BMedian, BMode, BSTDev, BMin, BMax, BLQ, BUQ; +} statistics_t; + +#define FIND_BLOBS_CORNERS_RESOLUTION 20 // multiple of 4 +#define FIND_BLOBS_ANGLE_RESOLUTION (360 / FIND_BLOBS_CORNERS_RESOLUTION) + +typedef struct find_blobs_list_lnk_data { + point_t corners[FIND_BLOBS_CORNERS_RESOLUTION]; + rectangle_t rect; + uint32_t pixels, perimeter, code, count; + float centroid_x, centroid_y, rotation, roundness; + uint16_t x_hist_bins_count, y_hist_bins_count, *x_hist_bins, *y_hist_bins; + float centroid_x_acc, centroid_y_acc, rotation_acc_x, rotation_acc_y, roundness_acc; +} find_blobs_list_lnk_data_t; + +typedef struct find_lines_list_lnk_data { + line_t line; + uint32_t magnitude; + int16_t theta, rho; +} find_lines_list_lnk_data_t; + +typedef struct find_circles_list_lnk_data { + point_t p; + uint16_t r, magnitude; +} find_circles_list_lnk_data_t; + +typedef struct find_rects_list_lnk_data { + point_t corners[4]; + rectangle_t rect; + uint32_t magnitude; +} find_rects_list_lnk_data_t; + +typedef struct find_qrcodes_list_lnk_data { + point_t corners[4]; + rectangle_t rect; + size_t payload_len; + char *payload; + uint8_t version, ecc_level, mask, data_type; + uint32_t eci; +} find_qrcodes_list_lnk_data_t; + +typedef enum apriltag_families { + TAG16H5 = 1, + TAG25H7 = 2, + TAG25H9 = 4, + TAG36H10 = 8, + TAG36H11 = 16, + ARTOOLKIT = 32 +} apriltag_families_t; + +typedef struct find_apriltags_list_lnk_data { + point_t corners[4]; + rectangle_t rect; + uint16_t id; + uint8_t family, hamming; + point_t centroid; + float goodness, decision_margin; + float x_translation, y_translation, z_translation; + float x_rotation, y_rotation, z_rotation; +} find_apriltags_list_lnk_data_t; + +typedef struct find_datamatrices_list_lnk_data { + point_t corners[4]; + rectangle_t rect; + size_t payload_len; + char *payload; + uint16_t rotation; + uint8_t rows, columns; + uint16_t capacity, padding; +} find_datamatrices_list_lnk_data_t; + +typedef enum barcodes { + BARCODE_EAN2, + BARCODE_EAN5, + BARCODE_EAN8, + BARCODE_UPCE, + BARCODE_ISBN10, + BARCODE_UPCA, + BARCODE_EAN13, + BARCODE_ISBN13, + BARCODE_I25, + BARCODE_DATABAR, + BARCODE_DATABAR_EXP, + BARCODE_CODABAR, + BARCODE_CODE39, + BARCODE_PDF417, + BARCODE_CODE93, + BARCODE_CODE128 +} barcodes_t; + +typedef struct find_barcodes_list_lnk_data { + point_t corners[4]; + rectangle_t rect; + size_t payload_len; + char *payload; + uint16_t type, rotation; + int quality; +} find_barcodes_list_lnk_data_t; + +typedef enum image_hint { + IMAGE_HINT_AREA = 1 << 0, + IMAGE_HINT_BILINEAR = 1 << 1, + IMAGE_HINT_BICUBIC = 1 << 2, + IMAGE_HINT_CENTER = 1 << 7, + IMAGE_HINT_EXTRACT_RGB_CHANNEL_FIRST = 1 << 8, + IMAGE_HINT_APPLY_COLOR_PALETTE_FIRST = 1 << 9, + IMAGE_HINT_BLACK_BACKGROUND = 1 << 31 +} image_hint_t; + +typedef struct imlib_draw_row_data { + image_t *dst_img; // user + pixformat_t src_img_pixfmt; // user + int rgb_channel; // user + int alpha; // user + const uint16_t *color_palette; // user + const uint8_t *alpha_palette; // user + bool black_background; // user + void *callback; // user + void *dst_row_override; // user + int toggle; // private + void *row_buffer[2]; // private + #ifdef IMLIB_ENABLE_DMA2D + bool dma2d_request; // user + bool dma2d_enabled; // private + bool dma2d_initialized; // private + DMA2D_HandleTypeDef dma2d; // private + #endif + long smuad_alpha; // private + uint32_t *smuad_alpha_palette; // private +} imlib_draw_row_data_t; + +typedef void (*imlib_draw_row_callback_t)(int x_start, int x_end, int y_row, imlib_draw_row_data_t *data); + +// Library Hardware Init +void imlib_init_all(); +void imlib_deinit_all(); + +//imlib base operation +void imlib_pixfmt_to(image_t *dst, image_t *src, rectangle_t *roi_i); +void imlib_image_resize(image_t *dst, image_t *src, int hist); + +// Generic Helper Functions +void imlib_fill_image_from_float(image_t *img, int w, int h, float *data, float min, float max, + bool mirror, bool flip, bool dst_transpose, bool src_transpose); + +// Bayer Image Processing +void imlib_debayer_line(int x_start, int x_end, int y_row, void *dst_row_ptr, pixformat_t pixfmt, image_t *src); +void imlib_debayer_image(image_t *dst, image_t *src); + +// YUV Image Processing +void imlib_deyuv_line(int x_start, int x_end, int y_row, void *dst_row_ptr, pixformat_t pixfmt, image_t *src); +void imlib_deyuv_image(image_t *dst, image_t *src); + +/* Color space functions */ +int8_t imlib_rgb565_to_l(uint16_t pixel); +int8_t imlib_rgb565_to_a(uint16_t pixel); +int8_t imlib_rgb565_to_b(uint16_t pixel); +int8_t imlib_rgb888_to_l(uint32_t pixel); +int8_t imlib_rgb888_to_a(uint32_t pixel); +int8_t imlib_rgb888_to_b(uint32_t pixel); +uint16_t imlib_lab_to_rgb(uint8_t l, int8_t a, int8_t b); +uint16_t imlib_yuv_to_rgb(uint8_t y, int8_t u, int8_t v); +uint32_t imlib_lab_to_rgb888(uint8_t l, int8_t a, int8_t b); +uint32_t imlib_yuv_to_rgb888(uint8_t y, int8_t u, int8_t v); + +/* Image file functions */ +void ppm_read_geometry(FIL *fp, image_t *img, const char *path, ppm_read_settings_t *rs); +void ppm_read_pixels(FIL *fp, image_t *img, int n_lines, ppm_read_settings_t *rs); +void ppm_read(image_t *img, const char *path); +void ppm_write_subimg(image_t *img, const char *path, rectangle_t *r); +bool bmp_read_geometry(FIL *fp, image_t *img, const char *path, bmp_read_settings_t *rs); +void bmp_read_pixels(FIL *fp, image_t *img, int n_lines, bmp_read_settings_t *rs); +void bmp_read(image_t *img, const char *path); +void bmp_write_subimg(image_t *img, const char *path, rectangle_t *r); +#if (OMV_HARDWARE_JPEG == 1) +void imlib_jpeg_compress_init(); +void imlib_jpeg_compress_deinit(); +void jpeg_mdma_irq_handler(); +#endif +void jpeg_decompress(image_t *dst, image_t *src); +bool jpeg_compress(image_t *src, image_t *dst, int quality, bool realloc); +int jpeg_clean_trailing_bytes(int bpp, uint8_t *data); +void jpeg_read_geometry(FIL *fp, image_t *img, const char *path, jpg_read_settings_t *rs); +void jpeg_read_pixels(FIL *fp, image_t *img); +void jpeg_read(image_t *img, const char *path); +void jpeg_write(image_t *img, const char *path, int quality); +void png_decompress(image_t *dst, image_t *src); +bool png_compress(image_t *src, image_t *dst); +void png_read_geometry(FIL *fp, image_t *img, const char *path, png_read_settings_t *rs); +void png_read_pixels(FIL *fp, image_t *img); +void png_read(image_t *img, const char *path); +void png_write(image_t *img, const char *path); +bool imlib_read_geometry(FIL *fp, image_t *img, const char *path, img_read_settings_t *rs); +void imlib_image_operation(image_t *img, const char *path, image_t *other, int scalar, line_op_t op, void *data); +void imlib_load_image(image_t *img, const char *path); +void imlib_save_image(image_t *img, const char *path, rectangle_t *roi, int quality); + +/* GIF functions */ +void gif_open(FIL *fp, int width, int height, bool color, bool loop); +void gif_add_frame(FIL *fp, image_t *img, uint16_t delay); +void gif_close(FIL *fp); + +/* MJPEG functions */ +void mjpeg_open(FIL *fp, int width, int height); +void mjpeg_add_frame(FIL *fp, uint32_t *frames, uint32_t *bytes, image_t *img, int quality); +void mjpeg_close(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps); + +/* Point functions */ +point_t *point_alloc(int16_t x, int16_t y); +bool point_equal(point_t *p1, point_t *p2); +float point_distance(point_t *p1, point_t *p2); + +/* Rectangle functions */ +rectangle_t *rectangle_alloc(int16_t x, int16_t y, int16_t w, int16_t h); +bool rectangle_equal(rectangle_t *r1, rectangle_t *r2); +bool rectangle_intersects(rectangle_t *r1, rectangle_t *r2); +bool rectangle_subimg(image_t *img, rectangle_t *r, rectangle_t *r_out); +array_t *rectangle_merge(array_t *rectangles); +void rectangle_expand(rectangle_t *r, int x, int y); + +/* Separable 2D convolution */ +void imlib_sepconv3(image_t *img, const int8_t *krn, const float m, const int b); + +/* Image Statistics */ +int imlib_image_mean(image_t *src, int *r_mean, int *g_mean, int *b_mean); +int imlib_image_std(image_t *src); // grayscale only + +/* Template Matching */ +void imlib_midpoint_pool(image_t *img_i, image_t *img_o, int x_div, int y_div, const int bias); +void imlib_mean_pool(image_t *img_i, image_t *img_o, int x_div, int y_div); +float imlib_template_match_ds(image_t *image, image_t *template_obj, rectangle_t *r); +float imlib_template_match_ex(image_t *image, image_t *template_obj, rectangle_t *roi, int step, rectangle_t *r); + +/* Clustering functions */ +array_t *cluster_kmeans(array_t *points, int k, cluster_dist_t dist_func); + +/* Integral image functions */ +void imlib_integral_image_alloc(struct integral_image *sum, int w, int h); +void imlib_integral_image_free(struct integral_image *sum); +void imlib_integral_image(struct image *src, struct integral_image *sum); +void imlib_integral_image_sq(struct image *src, struct integral_image *sum); +void imlib_integral_image_scaled(struct image *src, struct integral_image *sum); +uint32_t imlib_integral_lookup(struct integral_image *src, int x, int y, int w, int h); + +// Integral moving window +void imlib_integral_mw_alloc(mw_image_t *sum, int w, int h); +void imlib_integral_mw_free(mw_image_t *sum); +void imlib_integral_mw_scale(rectangle_t *roi, mw_image_t *sum, int w, int h); +void imlib_integral_mw(image_t *src, mw_image_t *sum); +void imlib_integral_mw_sq(image_t *src, mw_image_t *sum); +void imlib_integral_mw_shift(image_t *src, mw_image_t *sum, int n); +void imlib_integral_mw_shift_sq(image_t *src, mw_image_t *sum, int n); +void imlib_integral_mw_ss(image_t *src, mw_image_t *sum, mw_image_t *ssq, rectangle_t *roi); +void imlib_integral_mw_shift_ss(image_t *src, mw_image_t *sum, mw_image_t *ssq, rectangle_t *roi, int n); +long imlib_integral_mw_lookup(mw_image_t *sum, int x, int y, int w, int h); + +/* Haar/VJ */ +int imlib_load_cascade(struct cascade* cascade, const char *path); +array_t *imlib_detect_objects(struct image *image, struct cascade *cascade, struct rectangle *roi); + +/* Corner detectors */ +void fast_detect(image_t *image, array_t *keypoints, int threshold, rectangle_t *roi); +void agast_detect(image_t *image, array_t *keypoints, int threshold, rectangle_t *roi); + +/* ORB descriptor */ +array_t *orb_find_keypoints(image_t *image, bool normalized, int threshold, + float scale_factor, int max_keypoints, corner_detector_t corner_detector, rectangle_t *roi); +int orb_match_keypoints(array_t *kpts1, array_t *kpts2, int *match, int threshold, rectangle_t *r, point_t *c, int *angle); +int orb_filter_keypoints(array_t *kpts, rectangle_t *r, point_t *c); +int orb_save_descriptor(FIL *fp, array_t *kpts); +int orb_load_descriptor(FIL *fp, array_t *kpts); +float orb_cluster_dist(int cx, int cy, void *kp); + +/* LBP Operator */ +uint8_t *imlib_lbp_desc(image_t *image, rectangle_t *roi); +int imlib_lbp_desc_distance(uint8_t *d0, uint8_t *d1); +int imlib_lbp_desc_save(FIL *fp, uint8_t *desc); +int imlib_lbp_desc_load(FIL *fp, uint8_t **desc); + +/* Iris detector */ +void imlib_find_iris(image_t *src, point_t *iris, rectangle_t *roi); + +// Image filter functions +void im_filter_bw(uint8_t *src, uint8_t *dst, int size, int bpp, void *args); +void im_filter_skin(uint8_t *src, uint8_t *dst, int size, int bpp, void *args); + +// Edge detection +void imlib_edge_simple(image_t *src, rectangle_t *roi, int low_thresh, int high_thresh); +void imlib_edge_canny(image_t *src, rectangle_t *roi, int low_thresh, int high_thresh); + +// HoG +void imlib_find_hog(image_t *src, rectangle_t *roi, int cell_size); + +// Helper Functions +void imlib_zero(image_t *img, image_t *mask, bool invert); +void imlib_draw_row_setup(imlib_draw_row_data_t *data); +void imlib_draw_row_teardown(imlib_draw_row_data_t *data); +#ifdef IMLIB_ENABLE_DMA2D +void imlib_draw_row_deinit_all(); +#endif +void *imlib_draw_row_get_row_buffer(imlib_draw_row_data_t *data); +void imlib_draw_row_put_row_buffer(imlib_draw_row_data_t *data, void *row_buffer); +void imlib_draw_row(int x_start, int x_end, int y_row, imlib_draw_row_data_t *data); +bool imlib_draw_image_rectangle(image_t *dst_img, image_t *src_img, int dst_x_start, int dst_y_start, float x_scale, float y_scale, rectangle_t *roi, + int alpha, const uint8_t *alpha_palette, image_hint_t hint, + int *x0, int *x1, int *y0, int *y1); +void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, + int seed_threshold, int floating_threshold, + flood_fill_call_back_t cb, void *data); +// Drawing Functions +int imlib_get_pixel(image_t *img, int x, int y); +int imlib_get_pixel_fast(image_t *img, const void *row_ptr, int x); +void imlib_set_pixel(image_t *img, int x, int y, int p); +void imlib_draw_line(image_t *img, int x0, int y0, int x1, int y1, int c, int thickness); +void imlib_draw_rectangle(image_t *img, int rx, int ry, int rw, int rh, int c, int thickness, bool fill); +void imlib_draw_circle(image_t *img, int cx, int cy, int r, int c, int thickness, bool fill); +void imlib_draw_ellipse(image_t *img, int cx, int cy, int rx, int ry, int rotation, int c, int thickness, bool fill); +void imlib_draw_string(image_t *img, int x_off, int y_off, const char *str, int c, float scale, int x_spacing, int y_spacing, bool mono_space, + int char_rotation, bool char_hmirror, bool char_vflip, int string_rotation, bool string_hmirror, bool string_hflip); +void imlib_draw_image_fast(image_t *img, image_t *other, int x_off, int y_off, float x_scale, float y_scale, float alpha, image_t *mask); +void imlib_draw_image(image_t *dst_img, image_t *src_img, int dst_x_start, int dst_y_start, float x_scale, float y_scale, rectangle_t *roi, + int rgb_channel, int alpha, const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint, + imlib_draw_row_callback_t callback, void *dst_row_override); +void imlib_flood_fill(image_t *img, int x, int y, + float seed_threshold, float floating_threshold, + int c, bool invert, bool clear_background, image_t *mask); +void imlib_draw_cross(image_t *img, int x, int y, int c, int size, int thickness); +// ISP Functions +void imlib_awb(image_t *img, bool max); +void imlib_ccm(image_t *img, float *ccm, bool offset); +void imlib_gamma(image_t *img, float gamma, float scale, float offset); +// Binary Functions +void imlib_binary(image_t *out, image_t *img, list_t *thresholds, bool invert, bool zero, image_t *mask); +void imlib_invert(image_t *img); +void imlib_b_and(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); +void imlib_b_nand(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); +void imlib_b_or(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); +void imlib_b_nor(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); +void imlib_b_xor(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); +void imlib_b_xnor(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); +void imlib_erode(image_t *img, int ksize, int threshold, image_t *mask); +void imlib_dilate(image_t *img, int ksize, int threshold, image_t *mask); +void imlib_open(image_t *img, int ksize, int threshold, image_t *mask); +void imlib_close(image_t *img, int ksize, int threshold, image_t *mask); +void imlib_top_hat(image_t *img, int ksize, int threshold, image_t *mask); +void imlib_black_hat(image_t *img, int ksize, int threshold, image_t *mask); +// Math Functions +void imlib_gamma_corr(image_t *img, float gamma, float scale, float offset); +void imlib_negate(image_t *img); +void imlib_replace(image_t *img, const char *path, image_t *other, int scalar, bool hmirror, bool vflip, bool transpose, image_t *mask); +void imlib_add(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); +void imlib_sub(image_t *img, const char *path, image_t *other, int scalar, bool reverse, image_t *mask); +void imlib_mul(image_t *img, const char *path, image_t *other, int scalar, bool invert, image_t *mask); +void imlib_div(image_t *img, const char *path, image_t *other, int scalar, bool invert, bool mod, image_t *mask); +void imlib_min(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); +void imlib_max(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); +void imlib_difference(image_t *img, const char *path, image_t *other, int scalar, image_t *mask); +void imlib_blend(image_t *img, const char *path, image_t *other, int scalar, float alpha, image_t *mask); +// Filtering Functions +void imlib_histeq(image_t *img, image_t *mask); +void imlib_clahe_histeq(image_t *img, float clip_limit, image_t *mask); +void imlib_mean_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert, image_t *mask); +void imlib_median_filter(image_t *img, const int ksize, float percentile, bool threshold, int offset, bool invert, image_t *mask); +void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert, image_t *mask); +void imlib_midpoint_filter(image_t *img, const int ksize, float bias, bool threshold, int offset, bool invert, image_t *mask); +void imlib_morph(image_t *img, const int ksize, const int *krn, const float m, const int b, bool threshold, int offset, bool invert, image_t *mask); +void imlib_bilateral_filter(image_t *img, const int ksize, float color_sigma, float space_sigma, bool threshold, int offset, bool invert, image_t *mask); +void imlib_cartoon_filter(image_t *img, float seed_threshold, float floating_threshold, image_t *mask); +// Image Correction +void imlib_logpolar_int(image_t *dst, image_t *src, rectangle_t *roi, bool linear, bool reverse); // helper/internal +void imlib_logpolar(image_t *img, bool linear, bool reverse); +// Lens/Rotation Correction +void imlib_lens_corr(image_t *img, float strength, float zoom, float x_corr, float y_corr); +void imlib_rotation_corr(image_t *img, float x_rotation, float y_rotation, + float z_rotation, float x_translation, float y_translation, + float zoom, float fov, float *corners); +// Statistics +void imlib_get_similarity(image_t *img, const char *path, image_t *other, int scalar, float *avg, float *std, float *min, float *max); +void imlib_get_histogram(histogram_t *out, image_t *ptr, rectangle_t *roi, list_t *thresholds, bool invert, image_t *other); +void imlib_get_percentile(percentile_t *out, pixformat_t pixfmt, histogram_t *ptr, float percentile); +void imlib_get_threshold(threshold_t *out, pixformat_t pixfmt, histogram_t *ptr); +void imlib_get_statistics(statistics_t *out, pixformat_t pixfmt, histogram_t *ptr); +bool imlib_get_regression(find_lines_list_lnk_data_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, + list_t *thresholds, bool invert, unsigned int area_threshold, unsigned int pixels_threshold, bool robust); +// Color Tracking +void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, + list_t *thresholds, bool invert, unsigned int area_threshold, unsigned int pixels_threshold, + bool merge, int margin, + bool (*threshold_cb)(void*,find_blobs_list_lnk_data_t*), void *threshold_cb_arg, + bool (*merge_cb)(void*,find_blobs_list_lnk_data_t*,find_blobs_list_lnk_data_t*), void *merge_cb_arg, + unsigned int x_hist_bins_max, unsigned int y_hist_bins_max); +// Shape Detection +size_t trace_line(image_t *ptr, line_t *l, int *theta_buffer, uint32_t *mag_buffer, point_t *point_buffer); // helper/internal +void merge_alot(list_t *out, int threshold, int theta_threshold); // helper/internal +void imlib_find_lines(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, + uint32_t threshold, unsigned int theta_margin, unsigned int rho_margin); +void imlib_lsd_find_line_segments(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int merge_distance, unsigned int max_theta_diff); +void imlib_find_line_segments(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, + uint32_t threshold, unsigned int theta_margin, unsigned int rho_margin, + uint32_t segment_threshold); +void imlib_find_circles(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, + uint32_t threshold, unsigned int x_margin, unsigned int y_margin, unsigned int r_margin, + unsigned int r_min, unsigned int r_max, unsigned int r_step); +void imlib_find_rects(list_t *out, image_t *ptr, rectangle_t *roi, + uint32_t threshold); +// 1/2D Bar Codes +void imlib_find_qrcodes(list_t *out, image_t *ptr, rectangle_t *roi); +void custom_imlib_find_apriltags(list_t *out, image_t *ptr, rectangle_t *roi, apriltag_families_t families, + float fx, float fy, float cx, float cy, int status); +void imlib_find_apriltags(list_t *out, image_t *ptr, rectangle_t *roi, apriltag_families_t families, + float fx, float fy, float cx, float cy); +void imlib_find_datamatrices(list_t *out, image_t *ptr, rectangle_t *roi, int effort); +void imlib_find_barcodes(list_t *out, image_t *ptr, rectangle_t *roi); +// Template Matching +void imlib_phasecorrelate(image_t *img0, image_t *img1, rectangle_t *roi0, rectangle_t *roi1, bool logpolar, bool fix_rotation_scale, + float *x_translation, float *y_translation, float *rotation, float *scale, float *response); + +array_t *imlib_selective_search(image_t *src, float t, int min_size, float a1, float a2, float a3); + + +// custem + +void imlib_find_domain(image_t* img, image_t** dst, float edge_gate); + + + + +#ifdef __cplusplus +} +#endif +#endif //__IMLIB_H__ diff --git a/github_source/minicv2/include/imlib_config.h b/github_source/minicv2/include/imlib_config.h new file mode 100644 index 0000000..81bf361 --- /dev/null +++ b/github_source/minicv2/include/imlib_config.h @@ -0,0 +1,177 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Image library configuration. + */ +// #define IMLIB_CONFIG_H_FILE "costom_imlib_config.h" +#ifdef IMLIB_CONFIG_H_FILE +#include IMLIB_CONFIG_H_FILE +#else +#ifndef __IMLIB_CONFIG_H__ +#define __IMLIB_CONFIG_H__ +#ifdef __cplusplus +extern "C" +{ +#endif +// Enable Image I/O +#define IMLIB_ENABLE_IMAGE_IO + +// Enable Image File I/O +#define IMLIB_ENABLE_IMAGE_FILE_IO + +// Enable LAB LUT +//#define IMLIB_ENABLE_LAB_LUT + +// Enable YUV LUT +//#define IMLIB_ENABLE_YUV_LUT + +// Enable mean pooling +#define IMLIB_ENABLE_MEAN_POOLING + +// Enable midpoint pooling +#define IMLIB_ENABLE_MIDPOINT_POOLING + +// Enable binary ops +#define IMLIB_ENABLE_BINARY_OPS + +// Enable math ops +#define IMLIB_ENABLE_MATH_OPS + +// Enable flood_fill() +#define IMLIB_ENABLE_FLOOD_FILL + +// Enable mean() +#define IMLIB_ENABLE_MEAN + +// Enable median() +#define IMLIB_ENABLE_MEDIAN + +// Enable mode() +#define IMLIB_ENABLE_MODE + +// Enable midpoint() +#define IMLIB_ENABLE_MIDPOINT + +// Enable morph() +#define IMLIB_ENABLE_MORPH + +// Enable Gaussian +#define IMLIB_ENABLE_GAUSSIAN + +// Enable Laplacian +#define IMLIB_ENABLE_LAPLACIAN + +// Enable bilateral() +#define IMLIB_ENABLE_BILATERAL + +// Enable cartoon() +#define IMLIB_ENABLE_CARTOON + +// Enable linpolar() +#define IMLIB_ENABLE_LINPOLAR + +// Enable logpolar() +#define IMLIB_ENABLE_LOGPOLAR + +// Enable lens_corr() +#define IMLIB_ENABLE_LENS_CORR + +// Enable find_apriltags() (64 KB) +#define IMLIB_ENABLE_APRILTAGS + +// Enable fine find_apriltags() - (8-way connectivity versus 4-way connectivity) +// #define IMLIB_ENABLE_FINE_APRILTAGS + +// Enable high res find_apriltags() - uses more RAM +// #define IMLIB_ENABLE_HIGH_RES_APRILTAGS + +// Enable rotation_corr() +#if defined(IMLIB_ENABLE_APRILTAGS) +#define IMLIB_ENABLE_ROTATION_CORR +#endif + +// Enable phasecorrelate() +#if defined(IMLIB_ENABLE_ROTATION_CORR) +#define IMLIB_ENABLE_FIND_DISPLACEMENT +#endif + +// Enable find_rects() +#if defined(IMLIB_ENABLE_ROTATION_CORR) +#define IMLIB_ENABLE_FIND_RECTS +#endif + +// Enable get_similarity() +#define IMLIB_ENABLE_GET_SIMILARITY + +// Enable find_lines() +#define IMLIB_ENABLE_FIND_LINES + +// Enable find_line_segments() +#define IMLIB_ENABLE_FIND_LINE_SEGMENTS + +// Enable find_circles() +#define IMLIB_ENABLE_FIND_CIRCLES + + +// Enable find_qrcodes() (14 KB) +#define IMLIB_ENABLE_QRCODES + + +// Enable find_datamatrices() (26 KB) +#define IMLIB_ENABLE_DATAMATRICES + +// Enable find_barcodes() (42 KB) +#define IMLIB_ENABLE_BARCODES + +// Enable CMSIS NN +// #if !defined(CUBEAI) +// #define IMLIB_ENABLE_CNN +// #endif + +// Enable Tensor Flow +#if !defined(CUBEAI) +#define IMLIB_ENABLE_TF +#endif + +// Enable FAST (20+ KBs). +#define IMLIB_ENABLE_FAST + +// Enable find_template() +#define IMLIB_FIND_TEMPLATE + +// Enable find_lbp() +// #define IMLIB_ENABLE_FIND_LBP + +// Enable find_keypoints() +#define IMLIB_ENABLE_FIND_KEYPOINTS + +// Enable load, save and match descriptor +#define IMLIB_ENABLE_DESCRIPTOR + +// Enable find_hog() +//#define IMLIB_ENABLE_HOG + +// Enable selective_search() +#define IMLIB_ENABLE_SELECTIVE_SEARCH + +// Enable STM32 DMA2D +// #define IMLIB_ENABLE_DMA2D + +// Enable PRINT +#define IMLIB_ENABLE_PRINT + +// FB Heap Block Size +#define OMV_UMM_BLOCK_SIZE 256 + +#define OMV_FB_ALLOC_SIZE (4 * 1024 * 1024) // minimum fb alloc size + +#ifdef __cplusplus +} +#endif +#endif //__IMLIB_CONFIG_H__ +#endif \ No newline at end of file diff --git a/github_source/minicv2/include/imlib_io.h b/github_source/minicv2/include/imlib_io.h new file mode 100644 index 0000000..745a46f --- /dev/null +++ b/github_source/minicv2/include/imlib_io.h @@ -0,0 +1,59 @@ +#ifndef __IMLIB_IO__ +#define __IMLIB_IO__ +#ifdef __cplusplus +extern "C" +{ +#endif + + +int imlib_printf(int level, char *fmt, ...); +void imlib_set_print_out_level(int le); + + + +#define DEBUG_PRINT(fmt, ...) do{\ + imlib_printf(4,fmt" [DEBUG:%s:%d] [%s]\n", ##__VA_ARGS__, __FILE__, __LINE__, __FUNCTION__);\ +}while(0); + +#define LOG_PRINT(fmt, ...) do{\ + imlib_printf(3,fmt" [LOG] [%s]\n", ##__VA_ARGS__, __FUNCTION__);\ +}while(0); + +#define INFO_PRINT(fmt, ...) do{\ + imlib_printf(2,fmt" [INFO]\n", ##__VA_ARGS__);\ +}while(0); + +#define WORN_PRINT(fmt, ...) do{\ + imlib_printf(1,fmt" [WORN:%s:%d] [%s]\n", ##__VA_ARGS__, __FILE__, __LINE__, __FUNCTION__);\ +}while(0); + + +#define ERR_PRINT(fmt, ...) do{\ + imlib_printf(0, fmt" [ERR:%s:%d] [%s]\n", ##__VA_ARGS__, __FILE__, __LINE__, __FUNCTION__);\ +}while(0); + + +void imlib_printf_image_info(void *img); + + +#define IMLIB_2_MAIX(img) \ +({\ + const libmaix_image_mode_t __tmp_mode[4] = {LIBMAIX_IMAGE_MODE_GRAY, LIBMAIX_IMAGE_MODE_GRAY, LIBMAIX_IMAGE_MODE_RGB565, LIBMAIX_IMAGE_MODE_RGB888};\ + libmaix_image_create(img->w, img->h, __tmp_mode[img->bpp], LIBMAIX_IMAGE_LAYOUT_HWC, img->data, false);\ +}) + + +#define MAIX_2_IMLIB(img) \ +({\ + const pixformat_t __tmp_mode[5] = {PIXFORMAT_GRAYSCALE, PIXFORMAT_GRAYSCALE, PIXFORMAT_GRAYSCALE, PIXFORMAT_RGB888, PIXFORMAT_RGB565};\ + imlib_image_create(img->width, img->height, __tmp_mode[img->mode], img->width * img->height * (__tmp_mode[img->mode] & 0xff), img->data, false);\ +}) + +#ifdef __cplusplus +} +#endif +#endif // __IMLIB_IO__ + + + + diff --git a/github_source/minicv2/include/ini.h b/github_source/minicv2/include/ini.h new file mode 100644 index 0000000..4693323 --- /dev/null +++ b/github_source/minicv2/include/ini.h @@ -0,0 +1,164 @@ +#ifdef ENABLE_OPENMV_INI +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Initialization file parser. + * inih library is released under the New BSD license (see LICENSE.txt). + * For more details see the following: https://github.com/benhoyt/inih + */ +#ifndef __INI_H__ +#define __INI_H__ + + + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +int ini_atoi(const char *string); +bool ini_is_true(const char *value); + +#include "ff_wrapper.h" + +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +#define INI_HANDLER_LINENO 0 +#endif + +/* Typedef for prototype of handler function. */ +#if INI_HANDLER_LINENO +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value, + int lineno); +#else +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); +#endif + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(FATFS *fs, const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FIL* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FIL* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O (see also + ini_parse_string). */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Same as ini_parse(), but takes a zero-terminated string with the INI data +instead of a file. Useful for parsing INI data from a network socket or +already in memory. */ +int ini_parse_string(const char* string, ini_handler handler, void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See http://code.google.com/p/inih/issues/detail?id=21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Maximum line length for any line in INI file (stack or heap). Note that + this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +/* Nonzero to allow heap line buffer to grow via realloc(), zero for a + fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is + zero. */ +#ifndef INI_ALLOW_REALLOC +#define INI_ALLOW_REALLOC 0 +#endif + +/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK + is zero. */ +#ifndef INI_INITIAL_ALLOC +#define INI_INITIAL_ALLOC 200 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __INI_H__ */ + +#else +/** + * Copyright (c) 2016 rxi + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the MIT license. See `ini.c` for details. + */ + + + +#ifndef INI_H +#define INI_H + +#define INI_VERSION "0.1.1" +#ifdef __cplusplus +extern "C" +{ +#endif +typedef struct ini_t ini_t; + +ini_t* ini_load(const char *filename); +void ini_free(ini_t *ini); +const char* ini_get(ini_t *ini, const char *section, const char *key); +int ini_sget(ini_t *ini, const char *section, const char *key, const char *scanfmt, void *dst); +#ifdef __cplusplus +} +#endif +#endif + +#endif \ No newline at end of file diff --git a/github_source/minicv2/include/lodepng.h b/github_source/minicv2/include/lodepng.h new file mode 100644 index 0000000..ad4903f --- /dev/null +++ b/github_source/minicv2/include/lodepng.h @@ -0,0 +1,2026 @@ +/* +LodePNG version 20220109 + +Copyright (c) 2005-2022 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef LODEPNG_H +#define LODEPNG_H + +#include /*for size_t*/ +#include "imlib.h" + +extern const char* LODEPNG_VERSION_STRING; + +/* +The following #defines are used to create code sections. They can be disabled +to disable code sections, which can give faster compile time and smaller binary. +The "NO_COMPILE" defines are designed to be used to pass as defines to the +compiler command to disable them without modifying this header, e.g. +-DLODEPNG_NO_COMPILE_ZLIB for gcc. +In addition to those below, you can also define LODEPNG_NO_COMPILE_CRC to +allow implementing a custom lodepng_crc32. +*/ +/*deflate & zlib. If disabled, you must specify alternative zlib functions in +the custom_zlib field of the compress and decompress settings*/ +#ifndef LODEPNG_NO_COMPILE_ZLIB +#define LODEPNG_COMPILE_ZLIB +#endif + +/*png encoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_PNG +#define LODEPNG_COMPILE_PNG +#endif + +/*deflate&zlib decoder and png decoder*/ +#ifdef IMLIB_ENABLE_PNG_DECODER +#define LODEPNG_COMPILE_DECODER +#endif + +/*deflate&zlib encoder and png encoder*/ +#ifdef IMLIB_ENABLE_PNG_ENCODER +#define LODEPNG_COMPILE_ENCODER +#endif + +/*the optional built in harddisk file loading and saving functions*/ +#ifndef LODEPNG_NO_COMPILE_DISK +//#define LODEPNG_COMPILE_DISK +#endif + +/*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ +#ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS +#endif + +/*ability to convert error numerical codes to English text string*/ +#ifndef LODEPNG_NO_COMPILE_ERROR_TEXT +#define LODEPNG_COMPILE_ERROR_TEXT +#endif + +/*Compile the default allocators (C's free, malloc and realloc). If you disable this, +you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your +source files with custom allocators.*/ +#ifndef LODEPNG_NO_COMPILE_ALLOCATORS +//#define LODEPNG_COMPILE_ALLOCATORS +#endif + +/*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ +#ifdef __cplusplus +#ifndef LODEPNG_NO_COMPILE_CPP +#define LODEPNG_COMPILE_CPP +#endif +#endif + +#ifdef LODEPNG_COMPILE_CPP +#include +#include +#endif /*LODEPNG_COMPILE_CPP*/ + +#ifdef LODEPNG_COMPILE_PNG +/*The PNG color types (also used for raw image).*/ +typedef enum LodePNGColorType { + LCT_GREY = 0, /*grayscale: 1,2,4,8,16 bit*/ + LCT_RGB = 2, /*RGB: 8,16 bit*/ + LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ + LCT_GREY_ALPHA = 4, /*grayscale with alpha: 8,16 bit*/ + LCT_RGBA = 6, /*RGB with alpha: 8,16 bit*/ + LCT_CUSTOM = 7, + /*LCT_MAX_OCTET_VALUE lets the compiler allow this enum to represent any invalid + byte value from 0 to 255 that could be present in an invalid PNG file header. Do + not use, compare with or set the name LCT_MAX_OCTET_VALUE, instead either use + the valid color type names above, or numeric values like 1 or 7 when checking for + particular disallowed color type byte values, or cast to integer to print it.*/ + LCT_MAX_OCTET_VALUE = 255 +} LodePNGColorType; + +#ifdef LODEPNG_COMPILE_DECODER +/* +Converts PNG data in memory to raw pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + After decoding, its size is w * h * (bytes per pixel) bytes larger than + initially. Bytes per pixel depends on colortype and bitdepth. + Must be freed after usage with free(*out). + Note: for 16-bit per channel colors, uses big endian format like PNG does. +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +in: Memory buffer with the PNG file. +insize: size of the in buffer. +colortype: the desired color type for the raw output image. See explanation on PNG color types. +bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_DISK +/* +Load PNG from disk, from file with given name. +Same as the other decode functions, but instead takes a filename as input. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); + +/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory.*/ +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Converts raw pixel data into a PNG image in memory. The colortype and bitdepth + of the output PNG image cannot be chosen, they are automatically determined + by the colortype, bitdepth and content of the input pixel data. + Note: for 16-bit per channel colors, needs big endian format like PNG does. +out: Output parameter. Pointer to buffer that will contain the PNG image data. + Must be freed after usage with free(*out). +outsize: Output parameter. Pointer to the size in bytes of the out buffer. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colortype: the color type of the raw input image. See explanation on PNG color types. +bitdepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DISK +/* +Converts raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. + +NOTE: This overwrites existing files without warning! + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode32_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory.*/ +unsigned lodepng_encode24_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { +#ifdef LODEPNG_COMPILE_DECODER +/*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype +is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts PNG file from disk to raw pixel data in memory. +Same as the other decode functions, but instead takes a filename as input. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::string& filename, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/*Same as lodepng_encode_memory, but encodes to an std::vector. colortype +is that of the raw input data. The output PNG color type will be auto chosen.*/ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts 32-bit RGBA raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. + +NOTE: This overwrites existing files without warning! + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/*Returns an English description of the numerical error code.*/ +const char* lodepng_error_text(unsigned code); +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Settings for zlib decompression*/ +typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; +struct LodePNGDecompressSettings { + /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */ + unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ + unsigned ignore_nlen; /*ignore complement of len checksum in uncompressed blocks*/ + + /*Maximum decompressed size, beyond this the decoder may (and is encouraged to) stop decoding, + return an error, output a data size > max_output_size and all the data up to that point. This is + not hard limit nor a guarantee, but can prevent excessive memory usage. This setting is + ignored by the PNG decoder, but is used by the deflate/zlib decoder and can be used by custom ones. + Set to 0 to impose no limit (the default).*/ + size_t max_output_size; + + /*use custom zlib decoder instead of built in one (default: null). + Should return 0 if success, any non-0 if error (numeric value not exposed).*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + /*use custom deflate decoder instead of built in one (default: null) + if custom_zlib is not null, custom_inflate is ignored (the zlib format uses deflate). + Should return 0 if success, any non-0 if error (numeric value not exposed).*/ + unsigned (*custom_inflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGDecompressSettings lodepng_default_decompress_settings; +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Settings for zlib compression. Tweaking these settings tweaks the balance +between speed and compression ratio. +*/ +typedef struct LodePNGCompressSettings LodePNGCompressSettings; +struct LodePNGCompressSettings /*deflate = compress*/ { + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ + unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ + unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/ + unsigned minmatch; /*minimum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ + unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ + unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ + + /*use custom zlib encoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + /*use custom deflate encoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_deflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGCompressSettings lodepng_default_compress_settings; +void lodepng_compress_settings_init(LodePNGCompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_PNG +/* +Color mode of an image. Contains all information required to decode the pixel +bits to RGBA colors. This information is the same as used in the PNG file +format, and is used both for PNG and raw image data in LodePNG. +*/ +typedef struct LodePNGColorMode { + /*header (IHDR)*/ + LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ + unsigned customfmt; + unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + Dynamically allocated with the colors of the palette, including alpha. + This field may not be allocated directly, use lodepng_color_mode_init first, + then lodepng_palette_add per color to correctly initialize it (to ensure size + of exactly 1024 bytes). + + The alpha channels must be set as well, set them to 255 for opaque images. + + When decoding, by default you can ignore this palette, since LodePNG already + fills the palette colors in the pixels of the raw RGBA output. + + The palette is only supported for color type 3. + */ + unsigned char* palette; /*palette in RGBARGBA... order. Must be either 0, or when allocated must have 1024 bytes*/ + size_t palettesize; /*palette size in number of colors (amount of used bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + + This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. + For grayscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + + The color key is only supported for color types 0 and 2. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/grayscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNGColorMode; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_color_mode_init(LodePNGColorMode* info); +void lodepng_color_mode_cleanup(LodePNGColorMode* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); +/* Makes a temporary LodePNGColorMode that does not need cleanup (no palette) */ +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth); + +void lodepng_palette_clear(LodePNGColorMode* info); +/*add 1 color to the palette*/ +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +/*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info); +/*get the amount of color channels used, based on colortype in the struct. +If a palette is used, it counts as 1 channel.*/ +unsigned lodepng_get_channels(const LodePNGColorMode* info); +/*is it a grayscale type? (only colortype 0 or 4)*/ +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); +/*has it got an alpha channel? (only colortype 2 or 6)*/ +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); +/*has it got a palette? (only colortype 3)*/ +unsigned lodepng_is_palette_type(const LodePNGColorMode* info); +/*only returns true if there is a palette and there is a value in the palette with alpha < 255. +Loops through the palette to check this.*/ +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); +/* +Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. +Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). +Returns false if the image can only have opaque pixels. +In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, +or if "key_defined" is true. +*/ +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); +/*Returns the byte size of a raw image buffer with given width, height and color mode*/ +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*The information of a Time chunk in PNG.*/ +typedef struct LodePNGTime { + unsigned year; /*2 bytes used (0-65535)*/ + unsigned month; /*1-12*/ + unsigned day; /*1-31*/ + unsigned hour; /*0-23*/ + unsigned minute; /*0-59*/ + unsigned second; /*0-60 (to allow for leap seconds)*/ +} LodePNGTime; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Information about the PNG image, except pixels, width and height.*/ +typedef struct LodePNGInfo { + /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ + unsigned compression_method;/*compression method of the original file. Always 0.*/ + unsigned filter_method; /*filter method of the original file*/ + unsigned interlace_method; /*interlace method of the original file: 0=none, 1=Adam7*/ + LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /* + Suggested background color chunk (bKGD) + + This uses the same color mode and bit depth as the PNG (except no alpha channel), + with values truncated to the bit depth in the unsigned integer. + + For grayscale and palette PNGs, the value is stored in background_r. The values + in background_g and background_b are then unused. + + So when decoding, you may get these in a different color mode than the one you requested + for the raw pixels. + + When encoding with auto_convert, you must use the color model defined in info_png.color for + these values. The encoder normally ignores info_png.color when auto_convert is on, but will + use it to interpret these values (and convert copies of them to its chosen color model). + + When encoding, avoid setting this to an expensive color, such as a non-gray value + when the image is gray, or the compression will be worse since it will be forced to + write the PNG with a more expensive color mode (when auto_convert is on). + + The decoder does not use this background color to edit the color of pixels. This is a + completely optional metadata feature. + */ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red/gray/palette component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /* + Non-international text chunks (tEXt and zTXt) + + The char** arrays each contain num strings. The actual messages are in + text_strings, while text_keys are keywords that give a short description what + the actual text represents, e.g. Title, Author, Description, or anything else. + + All the string fields below including strings, keys, names and language tags are null terminated. + The PNG specification uses null characters for the keys, names and tags, and forbids null + characters to appear in the main text which is why we can use null termination everywhere here. + + A keyword is minimum 1 character and maximum 79 characters long (plus the + additional null terminator). It's discouraged to use a single line length + longer than 79 characters for texts. + + Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use lodepng_add_text and lodepng_clear_text. + + Standard text chunk keywords and strings are encoded using Latin-1. + */ + size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ + char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** text_strings; /*the actual text*/ + + /* + International text chunks (iTXt) + Similar to the non-international text chunks, but with additional strings + "langtags" and "transkeys", and the following text encodings are used: + keys: Latin-1, langtags: ASCII, transkeys and strings: UTF-8. + keys must be 1-79 characters (plus the additional null terminator), the other + strings are any length. + */ + size_t itext_num; /*the amount of international texts in this PNG*/ + char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ + char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ + char** itext_strings; /*the actual international text - UTF-8 string*/ + + /*time chunk (tIME)*/ + unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ + LodePNGTime time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ + unsigned phys_x; /*pixels per unit in x direction*/ + unsigned phys_y; /*pixels per unit in y direction*/ + unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ + + /* + Color profile related chunks: gAMA, cHRM, sRGB, iCPP + + LodePNG does not apply any color conversions on pixels in the encoder or decoder and does not interpret these color + profile values. It merely passes on the information. If you wish to use color profiles and convert colors, please + use these values with a color management library. + + See the PNG, ICC and sRGB specifications for more information about the meaning of these values. + */ + + /* gAMA chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned gama_defined; /* Whether a gAMA chunk is present (0 = not present, 1 = present). */ + unsigned gama_gamma; /* Gamma exponent times 100000 */ + + /* cHRM chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned chrm_defined; /* Whether a cHRM chunk is present (0 = not present, 1 = present). */ + unsigned chrm_white_x; /* White Point x times 100000 */ + unsigned chrm_white_y; /* White Point y times 100000 */ + unsigned chrm_red_x; /* Red x times 100000 */ + unsigned chrm_red_y; /* Red y times 100000 */ + unsigned chrm_green_x; /* Green x times 100000 */ + unsigned chrm_green_y; /* Green y times 100000 */ + unsigned chrm_blue_x; /* Blue x times 100000 */ + unsigned chrm_blue_y; /* Blue y times 100000 */ + + /* + sRGB chunk: optional. May not appear at the same time as iCCP. + If gAMA is also present gAMA must contain value 45455. + If cHRM is also present cHRM must contain respectively 31270,32900,64000,33000,30000,60000,15000,6000. + */ + unsigned srgb_defined; /* Whether an sRGB chunk is present (0 = not present, 1 = present). */ + unsigned srgb_intent; /* Rendering intent: 0=perceptual, 1=rel. colorimetric, 2=saturation, 3=abs. colorimetric */ + + /* + iCCP chunk: optional. May not appear at the same time as sRGB. + + LodePNG does not parse or use the ICC profile (except its color space header field for an edge case), a + separate library to handle the ICC data (not included in LodePNG) format is needed to use it for color + management and conversions. + + For encoding, if iCCP is present, gAMA and cHRM are recommended to be added as well with values that match the ICC + profile as closely as possible, if you wish to do this you should provide the correct values for gAMA and cHRM and + enable their '_defined' flags since LodePNG will not automatically compute them from the ICC profile. + + For encoding, the ICC profile is required by the PNG specification to be an "RGB" profile for non-gray + PNG color types and a "GRAY" profile for gray PNG color types. If you disable auto_convert, you must ensure + the ICC profile type matches your requested color type, else the encoder gives an error. If auto_convert is + enabled (the default), and the ICC profile is not a good match for the pixel data, this will result in an encoder + error if the pixel data has non-gray pixels for a GRAY profile, or a silent less-optimal compression of the pixel + data if the pixels could be encoded as grayscale but the ICC profile is RGB. + + To avoid this do not set an ICC profile in the image unless there is a good reason for it, and when doing so + make sure you compute it carefully to avoid the above problems. + */ + unsigned iccp_defined; /* Whether an iCCP chunk is present (0 = not present, 1 = present). */ + char* iccp_name; /* Null terminated string with profile name, 1-79 bytes */ + /* + The ICC profile in iccp_profile_size bytes. + Don't allocate this buffer yourself. Use the init/cleanup functions + correctly and use lodepng_set_icc and lodepng_clear_icc. + */ + unsigned char* iccp_profile; + unsigned iccp_profile_size; /* The size of iccp_profile in bytes */ + + /* End of color profile related chunks */ + + + /* + unknown chunks: chunks not known by LodePNG, passed on byte for byte. + + There are 3 buffers, one for each position in the PNG where unknown chunks can appear. + Each buffer contains all unknown chunks for that position consecutively. + The 3 positions are: + 0: between IHDR and PLTE, 1: between PLTE and IDAT, 2: between IDAT and IEND. + + For encoding, do not store critical chunks or known chunks that are enabled with a "_defined" flag + above in here, since the encoder will blindly follow this and could then encode an invalid PNG file + (such as one with two IHDR chunks or the disallowed combination of sRGB with iCCP). But do use + this if you wish to store an ancillary chunk that is not supported by LodePNG (such as sPLT or hIST), + or any non-standard PNG chunk. + + Do not allocate or traverse this data yourself. Use the chunk traversing functions declared + later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. + */ + unsigned char* unknown_chunks_data[3]; + size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGInfo; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_info_init(LodePNGInfo* info); +void lodepng_info_cleanup(LodePNGInfo* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ +void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ + +/*replaces if exists*/ +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size); +void lodepng_clear_icc(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/* +Converts raw buffer from one color type to another color type, based on +LodePNGColorMode structs to describe the input and output color type. +See the reference manual at the end of this header file to see which color conversions are supported. +return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) +The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel +of the output color type (lodepng_get_bpp). +For < 8 bpp images, there should not be padding bits at the end of scanlines. +For 16-bit per channel colors, uses big endian format like PNG does. +Return value is LodePNG error code +*/ +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DECODER +/* +Settings for the decoder. This contains settings for the PNG and the Zlib +decoder, but not the Info settings from the Info structs. +*/ +typedef struct LodePNGDecoderSettings { + LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */ + unsigned ignore_crc; /*ignore CRC checksums*/ + unsigned ignore_critical; /*ignore unknown critical chunks*/ + unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/ + /* TODO: make a system involving warnings with levels and a strict mode instead. Other potentially recoverable + errors: srgb rendering intent value, size of content of ancillary chunks, more than 79 characters for some + strings, placement/combination rules for ancillary chunks, crc of unknown chunks, allowed characters + in string keys, etc... */ + + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ + + /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ + unsigned remember_unknown_chunks; + + /* maximum size for decompressed text chunks. If a text chunk's text is larger than this, an error is returned, + unless reading text chunks is disabled or this limit is set higher or disabled. Set to 0 to allow any size. + By default it is a value that prevents unreasonably large strings from hogging memory. */ + size_t max_text_size; + + /* maximum size for compressed ICC chunks. If the ICC profile is larger than this, an error will be returned. Set to + 0 to allow any size. By default this is a value that prevents ICC profiles that would be much larger than any + legitimate profile could be to hog memory. */ + size_t max_icc_size; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGDecoderSettings; + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ +typedef enum LodePNGFilterStrategy { + /*every filter at zero*/ + LFS_ZERO = 0, + /*every filter at 1, 2, 3 or 4 (paeth), unlike LFS_ZERO not a good choice, but for testing*/ + LFS_ONE = 1, + LFS_TWO = 2, + LFS_THREE = 3, + LFS_FOUR = 4, + /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/ + LFS_MINSUM, + /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending + on the image, this is better or worse than minsum.*/ + LFS_ENTROPY, + /* + Brute-force-search PNG filters by compressing each filter for each scanline. + Experimental, very slow, and only rarely gives better compression than MINSUM. + */ + LFS_BRUTE_FORCE, + /*use predefined_filters buffer: you specify the filter type for each scanline*/ + LFS_PREDEFINED +} LodePNGFilterStrategy; + +/*Gives characteristics about the integer RGBA colors of the image (count, alpha channel usage, bit depth, ...), +which helps decide which color model to use for encoding. +Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/ +typedef struct LodePNGColorStats { + unsigned colored; /*not grayscale*/ + unsigned key; /*image is not opaque and color key is possible instead of full alpha*/ + unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/ + unsigned short key_g; + unsigned short key_b; + unsigned alpha; /*image is not opaque and alpha channel or alpha palette required*/ + unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16 or allow_palette is disabled.*/ + unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order, only valid when numcolors is valid*/ + unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for grayscale only. 16 if 16-bit per channel required.*/ + size_t numpixels; + + /*user settings for computing/using the stats*/ + unsigned allow_palette; /*default 1. if 0, disallow choosing palette colortype in auto_choose_color, and don't count numcolors*/ + unsigned allow_greyscale; /*default 1. if 0, choose RGB or RGBA even if the image only has gray colors*/ +} LodePNGColorStats; + +void lodepng_color_stats_init(LodePNGColorStats* stats); + +/*Get a LodePNGColorStats of the image. The stats must already have been inited. +Returns error code (e.g. alloc fail) or 0 if ok.*/ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); + +/*Settings for the encoder.*/ +typedef struct LodePNGEncoderSettings { + LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/ + + /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than + 8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to + completely follow the official PNG heuristic, filter_palette_zero must be true and + filter_strategy must be LFS_MINSUM*/ + unsigned filter_palette_zero; + /*Which filter strategy to use when not using zeroes due to filter_palette_zero. + Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ + LodePNGFilterStrategy filter_strategy; + /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with + the same length as the amount of scanlines in the image, and each value must <= 5. You + have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero + must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ + const unsigned char* predefined_filters; + + /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). + If colortype is 3, PLTE is _always_ created.*/ + unsigned force_palette; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*add LodePNG identifier and version as a text chunk, for debugging*/ + unsigned add_id; + /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ + unsigned text_compression; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGEncoderSettings; + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) +/*The settings, state and information for extended encoding and decoding.*/ +typedef struct LodePNGState { +#ifdef LODEPNG_COMPILE_DECODER + LodePNGDecoderSettings decoder; /*the decoding settings*/ +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + LodePNGEncoderSettings encoder; /*the encoding settings*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ + unsigned error; + /* Callback for custom color format conversion */ + unsigned (*lodepng_convert) (unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, unsigned w, unsigned h); +} LodePNGState; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_state_init(LodePNGState* state); +void lodepng_state_cleanup(LodePNGState* state); +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_DECODER +/* +Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and +getting much more information about the PNG image and color mode. +*/ +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); + +/* +Read the PNG header, but not the actual data. This returns only the information +that is in the IHDR chunk of the PNG, such as width, height and color type. The +information is placed in the info_png field of the LodePNGState. +*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* +Reads one metadata chunk (other than IHDR) of the PNG file and outputs what it +read in the state. Returns error code on failure. +Use lodepng_inspect first with a new state, then e.g. lodepng_chunk_find_const +to find the desired chunk type, and if non null use lodepng_inspect_chunk (with +chunk_pointer - start_of_file as pos). +Supports most metadata chunks from the PNG standard (gAMA, bKGD, tEXt, ...). +Ignores unsupported, unknown, non-metadata or IHDR chunks (without error). +Requirements: &in[pos] must point to start of a chunk, must use regular +lodepng_inspect first since format of most other chunks depends on IHDR, and if +there is a PLTE chunk, that one must be inspected before tRNS or bKGD. +*/ +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_ENCODER +/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* +The lodepng_chunk functions are normally not needed, except to traverse the +unknown chunks stored in the LodePNGInfo struct, or add new ones to it. +It also allows traversing the chunks of an encoded PNG file yourself. + +The chunk pointer always points to the beginning of the chunk itself, that is +the first byte of the 4 length bytes. + +In the PNG file format, chunks have the following format: +-4 bytes length: length of the data of the chunk in bytes (chunk itself is 12 bytes longer) +-4 bytes chunk type (ASCII a-z,A-Z only, see below) +-length bytes of data (may be 0 bytes if length was 0) +-4 bytes of CRC, computed on chunk name + data + +The first chunk starts at the 8th byte of the PNG file, the entire rest of the file +exists out of concatenated chunks with the above format. + +PNG standard chunk ASCII naming conventions: +-First byte: uppercase = critical, lowercase = ancillary +-Second byte: uppercase = public, lowercase = private +-Third byte: must be uppercase +-Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy +*/ + +/* +Gets the length of the data of the chunk. Total chunk length has 12 bytes more. +There must be at least 4 bytes to read from. If the result value is too large, +it may be corrupt data. +*/ +unsigned lodepng_chunk_length(const unsigned char* chunk); + +/*puts the 4-byte type in null terminated string*/ +void lodepng_chunk_type(char type[5], const unsigned char* chunk); + +/*check if the type is the given type*/ +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); + +/*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); + +/*0: public, 1: private (see PNG standard)*/ +unsigned char lodepng_chunk_private(const unsigned char* chunk); + +/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); + +/*get pointer to the data of the chunk, where the input points to the header of the chunk*/ +unsigned char* lodepng_chunk_data(unsigned char* chunk); +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); + +/*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ +unsigned lodepng_chunk_check_crc(const unsigned char* chunk); + +/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +void lodepng_chunk_generate_crc(unsigned char* chunk); + +/* +Iterate to next chunks, allows iterating through all chunks of the PNG file. +Input must be at the beginning of a chunk (result of a previous lodepng_chunk_next call, +or the 8th byte of a PNG file which always has the first chunk), or alternatively may +point to the first byte of the PNG file (which is not a chunk but the magic header, the +function will then skip over it and return the first real chunk). +Will output pointer to the start of the next chunk, or at or beyond end of the file if there +is no more chunk after this or possibly if the chunk is corrupt. +Start this process at the 8th byte of the PNG file. +In a non-corrupt PNG file, the last chunk should have name "IEND". +*/ +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end); +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end); + +/*Finds the first chunk with the given type in the range [chunk, end), or returns NULL if not found.*/ +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]); +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]); + +/* +Appends chunk to the data in out. The given chunk should already have its chunk header. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returns error code (0 if it went ok) +*/ +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk); + +/* +Appends new chunk to out. The chunk to append is given by giving its length, type +and data separately. The type is a 4-letter string. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returne error code (0 if it went ok) +*/ +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data); + + +/*Calculate CRC32 of buffer*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len); +#endif /*LODEPNG_COMPILE_PNG*/ + + +#ifdef LODEPNG_COMPILE_ZLIB +/* +This zlib part can be used independently to zlib compress and decompress a +buffer. It cannot be used to create gzip files however, and it only supports the +part of zlib that is required for PNG, it does not support dictionaries. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); + +/* +Decompresses Zlib data. Reallocates the out buffer and appends the data. The +data must be according to the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Compresses data with Zlib. Reallocates the out buffer and appends the data. +Zlib adds a small header and trailer around the deflate data. +The data is output in the format of the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +/* +Find length-limited Huffman code for given frequencies. This function is in the +public interface only for tests, it's used internally by lodepng_deflate. +*/ +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen); + +/*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into buffer. The function allocates the out buffer, and +after usage you should free it. +out: output parameter, contains pointer to loaded buffer. +outsize: output parameter, size of the allocated out buffer +filename: the path to the file to load +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory. +*/ +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); + +/* +Save a file from buffer to disk. Warning, if it exists, this function overwrites +the file without warning! +buffer: the buffer to write +buffersize: size of the buffer to write +filename: the path to the file to save to +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory +*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef LODEPNG_COMPILE_CPP +/* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ +namespace lodepng { +#ifdef LODEPNG_COMPILE_PNG +class State : public LodePNGState { + public: + State(); + State(const State& other); + ~State(); + State& operator=(const State& other); +}; + +#ifdef LODEPNG_COMPILE_DECODER +/* Same as other lodepng::decode, but using a State for more settings and information. */ +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize); +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Same as other lodepng::encode, but using a State for more settings and information. */ +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state); +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into an std::vector. +return value: error code (0 means ok) + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and decode in-memory +*/ +unsigned load_file(std::vector& buffer, const std::string& filename); + +/* +Save the binary data in an std::vector to a file on disk. The file is overwritten +without warning. + +NOTE: Wide-character filenames are not supported, you can use an external method +to handle such files and encode in-memory +*/ +unsigned save_file(const std::vector& buffer, const std::string& filename); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_PNG */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +/* Zlib-decompress an unsigned char buffer */ +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); + +/* Zlib-decompress an std::vector */ +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Zlib-compress an unsigned char buffer */ +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); + +/* Zlib-compress an std::vector */ +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ + +/* +TODO: +[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[.] check compatibility with various compilers - done but needs to be redone for every newer version +[X] converting color to 16-bit per channel types +[X] support color profile chunk types (but never let them touch RGB values by default) +[ ] support all public PNG chunk types (almost done except sBIT, sPLT and hIST) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[X] let the "isFullyOpaque" function check color keys and transparent palettes too +[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] allow treating some errors like warnings, when image is recoverable (e.g. 69, 57, 58) +[ ] make warnings like: oob palette, checksum fail, data after iend, wrong/unknown crit chunk, no null terminator in text, ... +[ ] error messages with line numbers (and version) +[ ] errors in state instead of as return code? +[ ] new errors/warnings like suspiciously big decompressed ztxt or iccp chunk +[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes +[ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... +[ ] allow user to give data (void*) to custom allocator +[X] provide alternatives for C library functions not present on some platforms (memcpy, ...) +*/ + +#endif /*LODEPNG_H inclusion guard*/ + +/* +LodePNG Documentation +--------------------- + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. security + 4. decoding + 5. encoding + 6. color conversions + 6.1. PNG color types + 6.2. color conversions + 6.3. padding bits + 6.4. A note about 16-bits per channel and endianness + 7. error values + 8. chunks and PNG editing + 9. compiler support + 10. examples + 10.1. decoder C++ example + 10.2. decoder C example + 11. state settings reference + 12. changes + 13. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types and alpha channel. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://lodev.org/lodepng/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +examples from the LodePNG website to see how to use it in code, or check the +smaller examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. There are functions to decode and encode a PNG with +a single function call, and extended versions of these functions taking a +LodePNGState struct allowing to specify or get more information. By default +the colors of the raw image are always RGB or RGBA, no matter what color type +the PNG file uses. To read and write files, there are simple functions to +convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demos and small +programs, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, + or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) colorimetric color profile conversions: currently experimentally available in lodepng_util.cpp only, + plus alternatively ability to pass on chroma/gamma/ICC profile information to other color management system. +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + cHRM: RGB chromaticities + gAMA: RGB gamma correction + iCCP: ICC color profile + sRGB: rendering intent + +1.2. features not supported +--------------------------- + +The following features are _not_ supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The following public chunks are not (yet) supported but treated as unknown chunks by LodePNG: + sBIT + hIST + sPLT + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc that you need to free() +yourself. You need to use init and cleanup functions for each struct whenever +using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has extra functions with std::vectors in the interface and the +lodepng::State class which is a LodePNGState with constructor and destructor. + +These files work without modification for both C and C++ compilers because all +the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers +ignore it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp +(instead of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. Security +----------- + +Even if carefully designed, it's always possible that LodePNG contains possible +exploits. If you discover one, please let me know, and it will be fixed. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well +as the C-style structs when working with C++. The following conventions are used +for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". + The destination must also be inited already. + + +4. Decoding +----------- + +Decoding converts a PNG compressed image to a raw pixel buffer. + +Most documentation on using the decoder is at its declarations in the header +above. For C, simple decoding can be done with functions such as +lodepng_decode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_decode. For C++, all decoding can be done with the +various lodepng::decode functions, and lodepng::State can be used for advanced +features. + +When using the LodePNGState, it uses the following fields for decoding: +*) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here +*) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get +*) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use + +LodePNGInfo info_png +-------------------- + +After decoding, this contains extra information of the PNG image, except the actual +pixels, width and height because these are already gotten directly from the decoder +functions. + +It contains for example the original color type of the PNG image, text comments, +suggested background color, etc... More details about the LodePNGInfo struct are +at its declaration documentation. + +LodePNGColorMode info_raw +------------------------- + +When decoding, here you can specify which color type you want +the resulting raw image to be. If this is different from the colortype of the +PNG, then the decoder will automatically convert the result. This conversion +always works, except if you want it to convert a color PNG to grayscale or to +a palette with missing colors. + +By default, 32-bit color is used for the result. + +LodePNGDecoderSettings decoder +------------------------------ + +The settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNGInfo. + + +5. Encoding +----------- + +Encoding converts a raw pixel buffer to a PNG compressed image. + +Most documentation on using the encoder is at its declarations in the header +above. For C, simple encoding can be done with functions such as +lodepng_encode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_encode. For C++, all encoding can be done with the +various lodepng::encode functions, and lodepng::State can be used for advanced +features. + +Like the decoder, the encoder can also give errors. However it gives less errors +since the encoder input is trusted, the decoder input (a PNG image that could +be forged by anyone) is not trusted. + +When using the LodePNGState, it uses the following fields for encoding: +*) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. +*) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has +*) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use + +LodePNGInfo info_png +-------------------- + +When encoding, you use this the opposite way as when decoding: for encoding, +you fill in the values you want the PNG to have before encoding. By default it's +not needed to specify a color type for the PNG since it's automatically chosen, +but it's possible to choose it yourself given the right settings. + +The encoder will not always exactly match the LodePNGInfo struct you give, +it tries as close as possible. Some things are ignored by the encoder. The +encoder uses, for example, the following settings from it when applicable: +colortype and bitdepth, text chunks, time chunk, the color key, the palette, the +background color, the interlace method, unknown chunks, ... + +When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +LodePNGColorMode info_raw +------------------------- + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +LodePNGEncoderSettings encoder +------------------------------ + +The following settings are supported (some are in sub-structs): +*) auto_convert: when this option is enabled, the encoder will +automatically choose the smallest possible color mode (including color key) that +can encode the colors of all pixels without information loss. +*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, + 2 = dynamic huffman tree (best compression). Should be 2 for proper + compression. +*) use_lz77: whether or not to use LZ77 for compressed block types. Should be + true for proper compression. +*) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value + 2048 by default, but can be set to 32768 for better, but slow, compression. +*) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG " to the image. +*) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +6. color conversions +-------------------- + +An important thing to note about LodePNG, is that the color type of the PNG, and +the color type of the raw image, are completely independent. By default, when +you decode a PNG, you get the result as a raw image in the color type you want, +no matter whether the PNG was encoded with a palette, grayscale or RGBA color. +And if you encode an image, by default LodePNG will automatically choose the PNG +color type that gives good compression based on the values of colors and amount +of colors in the image. It can be configured to let you control it instead as +well, though. + +To be able to do this, LodePNG does conversions from one color mode to another. +It can convert from almost any color type to any other color type, except the +following conversions: RGB to grayscale is not supported, and converting to a +palette when the palette doesn't have a required color is not supported. This is +not supported on purpose: this is information loss which requires a color +reduction algorithm that is beyond the scope of a PNG encoder (yes, RGB to gray +is easy, but there are multiple ways if you want to give some channels more +weight). + +By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB +color, no matter what color type the PNG has. And by default when encoding, +LodePNG automatically picks the best color model for the output PNG, and expects +the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control +the color format of the images yourself, you can skip this chapter. + +6.1. PNG color types +-------------------- + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification gives the following color types: + +0: grayscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: grayscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per pixel per color channel. So the total amount +of bits per pixel is: amount of channels * bitdepth. + +6.2. color conversions +---------------------- + +As explained in the sections about the encoder and decoder, you can specify +color types and bit depths in info_png and info_raw to change the default +behaviour. + +If, when decoding, you want the raw image to be something else than the default, +you need to set the color type and bit depth you want in the LodePNGColorMode, +or the parameters colortype and bitdepth of the simple decoding function. + +If, when encoding, you use another color type than the default in the raw input +image, you need to specify its color type and bit depth in the LodePNGColorMode +of the raw image, or use the parameters colortype and bitdepth of the simple +encoding function. + +If, when encoding, you don't want LodePNG to choose the output PNG color type +but control it yourself, you need to set auto_convert in the encoder settings +to false, and specify the color type you want in the LodePNGInfo of the +encoder (including palette: it can generate a palette if auto_convert is true, +otherwise not). + +If the input and output color type differ (whether user chosen or auto chosen), +LodePNG will do a color conversion, which follows the rules below, and may +sometimes result in an error. + +To avoid some confusion: +-the decoder converts from PNG to raw image +-the encoder converts from raw image to PNG +-the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image +-the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG +-when encoding, the color type in LodePNGInfo is ignored if auto_convert + is enabled, it is automatically generated instead +-when decoding, the color type in LodePNGInfo is set by the decoder to that of the original + PNG image, but it can be ignored since the raw image has the color type you requested instead +-if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion + between the color types is done if the color types are supported. If it is not + supported, an error is returned. If the types are the same, no conversion is done. +-even though some conversions aren't supported, LodePNG supports loading PNGs from any + colortype and saving PNGs to any colortype, sometimes it just requires preparing + the raw image correctly before encoding. +-both encoder and decoder use the same color converter. + +The function lodepng_convert does the color conversion. It is available in the +interface but normally isn't needed since the encoder and decoder already call +it. + +Non supported color conversions: +-color to grayscale when non-gray pixels are present: no error is thrown, but +the result will look ugly because only the red channel is taken (it assumes all +three channels are the same in this case so ignores green and blue). The reason +no error is given is to allow converting from three-channel grayscale images to +one-channel even if there are numerical imprecisions. +-anything to palette when the palette does not have an exact match for a from-color +in it: in this case an error is thrown + +Supported color conversions: +-anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA +-any gray or gray+alpha, to gray or gray+alpha +-anything to a palette, as long as the palette has the requested colors in it +-removing alpha channel +-higher to smaller bitdepth, and vice versa + +If you want no color conversion to be done (e.g. for speed or control): +-In the encoder, you can make it save a PNG with any color type by giving the +raw color mode and LodePNGInfo the same color mode, and setting auto_convert to +false. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +info_raw are then ignored. + +6.3. padding bits +----------------- + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. +The raw input image you give to the encoder, and the raw output image you get from the decoder +will NOT have these padding bits, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the 8th bit of the first byte, +not the first bit of a new byte. + +6.4. A note about 16-bits per channel and endianness +---------------------------------------------------- + +LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like +for any other color format. The 16-bit values are stored in big endian (most +significant byte first) in these arrays. This is the opposite order of the +little endian used by x86 CPU's. + +LodePNG always uses big endian because the PNG file format does so internally. +Conversions to other formats than PNG uses internally are not supported by +LodePNG on purpose, there are myriads of formats, including endianness of 16-bit +colors, the order in which you store R, G, B and A, and so on. Supporting and +converting to/from all that is outside the scope of LodePNG. + +This may mean that, depending on your use case, you may want to convert the big +endian output of LodePNG to little endian with a for loop. This is certainly not +always needed, many applications and libraries support big endian 16-bit colors +anyway, but it means you cannot simply cast the unsigned char* buffer to an +unsigned short* buffer on x86 CPUs. + + +7. error values +--------------- + +All functions in LodePNG that return an error code, return 0 if everything went +OK, or a non-zero code if there was an error. + +The meaning of the LodePNG error values can be retrieved with the function +lodepng_error_text: given the numerical error code, it returns a description +of the error in English as a string. + +Check the implementation of lodepng_error_text to see the meaning of each code. + +It is not recommended to use the numerical values to programmatically make +different decisions based on error types as the numbers are not guaranteed to +stay backwards compatible. They are for human consumption only. Programmatically +only 0 or non-0 matter. + + +8. chunks and PNG editing +------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if your +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + +8.1. iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using lodepng_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned lodepng_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void lodepng_chunk_type(char type[5], const unsigned char* chunk): +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char lodepng_chunk_critical(const unsigned char* chunk): +unsigned char lodepng_chunk_private(const unsigned char* chunk): +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* lodepng_chunk_data(unsigned char* chunk): +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk): +void lodepng_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* lodepng_chunk_next(unsigned char* chunk): +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk): +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outsize. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + +8.2. chunks in info_png +----------------------- + +The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 +buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distinction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +info_png.unknown_chunks_data[0] is the chunks before PLTE +info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT +info_png.unknown_chunks_data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.remember_unknown_chunks to 1. By default, this +option is off (0). + +The encoder will always encode unknown chunks that are stored in the info_png. +If you need it to add a particular chunk that isn't known by LodePNG, you can +use lodepng_chunk_append or lodepng_chunk_create to the chunk data in +info_png.unknown_chunks_data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there instead. + + +9. compiler support +------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +It is compatible with C90 and up, and C++03 and up. + +If performance is important, use optimization when compiling! For both the +encoder and decoder, this makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.7.1 on Linux, 32-bit and 64-bit. + +*) Clang + +Fully supported and warning-free. + +*) Mingw + +The Mingw compiler (a port of gcc for Windows) should be fully supported by +LodePNG. + +*) Visual Studio and Visual C++ Express Edition + +LodePNG should be warning-free with warning level W4. Two warnings were disabled +with pragmas though: warning 4244 about implicit conversions, and warning 4996 +where it wants to use a non-standard function fopen_s instead of the standard C +fopen. + +Visual Studio may want "stdafx.h" files to be included in each source file and +give an error "unexpected end of file while looking for precompiled header". +This is not standard C++ and will not be added to the stock LodePNG. You can +disable it for lodepng.cpp only by right clicking it, Properties, C/C++, +Precompiled Headers, and set it to Not Using Precompiled Headers there. + +NOTE: Modern versions of VS should be fully supported, but old versions, e.g. +VS6, are not guaranteed to work. + +*) Compilers on Macintosh + +LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for +C and C++. + +*) Other Compilers + +If you encounter problems on any compilers, feel free to let me know and I may +try to fix it if the compiler is modern and standards compliant. + + +10. examples +------------ + +This decoder example shows the most basic usage of LodePNG. More complex +examples can be found on the LodePNG website. + +NOTE: these examples do not support wide-character filenames, you can use an +external method to handle such files and encode or decode in-memory + +10.1. decoder C++ example +------------------------- + +#include "lodepng.h" +#include + +int main(int argc, char *argv[]) { + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector image; + unsigned width, height; + unsigned error = lodepng::decode(image, width, height, filename); + + //if there's an error, display it + if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; + + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} + +10.2. decoder C example +----------------------- + +#include "lodepng.h" + +int main(int argc, char *argv[]) { + unsigned error; + unsigned char* image; + size_t width, height; + const char* filename = argc > 1 ? argv[1] : "test.png"; + + error = lodepng_decode32_file(&image, &width, &height, filename); + + if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); + + / * use image here * / + + free(image); + return 0; +} + +11. state settings reference +---------------------------- + +A quick reference of some settings to set on the LodePNGState + +For decoding: + +state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums +state.decoder.zlibsettings.custom_...: use custom inflate function +state.decoder.ignore_crc: ignore CRC checksums +state.decoder.ignore_critical: ignore unknown critical chunks +state.decoder.ignore_end: ignore missing IEND chunk. May fail if this corruption causes other errors +state.decoder.color_convert: convert internal PNG color to chosen one +state.decoder.read_text_chunks: whether to read in text metadata chunks +state.decoder.remember_unknown_chunks: whether to read in unknown chunks +state.info_raw.colortype: desired color type for decoded image +state.info_raw.bitdepth: desired bit depth for decoded image +state.info_raw....: more color settings, see struct LodePNGColorMode +state.info_png....: no settings for decoder but ouput, see struct LodePNGInfo + +For encoding: + +state.encoder.zlibsettings.btype: disable compression by setting it to 0 +state.encoder.zlibsettings.use_lz77: use LZ77 in compression +state.encoder.zlibsettings.windowsize: tweak LZ77 windowsize +state.encoder.zlibsettings.minmatch: tweak min LZ77 length to match +state.encoder.zlibsettings.nicematch: tweak LZ77 match where to stop searching +state.encoder.zlibsettings.lazymatching: try one more LZ77 matching +state.encoder.zlibsettings.custom_...: use custom deflate function +state.encoder.auto_convert: choose optimal PNG color type, if 0 uses info_png +state.encoder.filter_palette_zero: PNG filter strategy for palette +state.encoder.filter_strategy: PNG filter strategy to encode with +state.encoder.force_palette: add palette even if not encoding to one +state.encoder.add_id: add LodePNG identifier and version as a text chunk +state.encoder.text_compression: use compressed text chunks for metadata +state.info_raw.colortype: color type of raw input image you provide +state.info_raw.bitdepth: bit depth of raw input image you provide +state.info_raw: more color settings, see struct LodePNGColorMode +state.info_png.color.colortype: desired color type if auto_convert is false +state.info_png.color.bitdepth: desired bit depth if auto_convert is false +state.info_png.color....: more color settings, see struct LodePNGColorMode +state.info_png....: more PNG related settings, see struct LodePNGInfo + + +12. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +Not all changes are listed here, the commit history in github lists more: +https://github.com/lvandeve/lodepng + +*) 09 jan 2022: minor decoder speed improvements. +*) 27 jun 2021: added warnings that file reading/writing functions don't support + wide-character filenames (support for this is not planned, opening files is + not the core part of PNG decoding/decoding and is platform dependent). +*) 17 okt 2020: prevent decoding too large text/icc chunks by default. +*) 06 mar 2020: simplified some of the dynamic memory allocations. +*) 12 jan 2020: (!) added 'end' argument to lodepng_chunk_next to allow correct + overflow checks. +*) 14 aug 2019: around 25% faster decoding thanks to huffman lookup tables. +*) 15 jun 2019: (!) auto_choose_color API changed (for bugfix: don't use palette + if gray ICC profile) and non-ICC LodePNGColorProfile renamed to + LodePNGColorStats. +*) 30 dec 2018: code style changes only: removed newlines before opening braces. +*) 10 sep 2018: added way to inspect metadata chunks without full decoding. +*) 19 aug 2018: (!) fixed color mode bKGD is encoded with and made it use + palette index in case of palette. +*) 10 aug 2018: (!) added support for gAMA, cHRM, sRGB and iCCP chunks. This + change is backwards compatible unless you relied on unknown_chunks for those. +*) 11 jun 2018: less restrictive check for pixel size integer overflow +*) 14 jan 2018: allow optionally ignoring a few more recoverable errors +*) 17 sep 2017: fix memory leak for some encoder input error cases +*) 27 nov 2016: grey+alpha auto color model detection bugfix +*) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort). +*) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within + the limits of pure C90). +*) 08 dec 2015: Made load_file function return error if file can't be opened. +*) 24 okt 2015: Bugfix with decoding to palette output. +*) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding. +*) 24 aug 2014: Moved to github +*) 23 aug 2014: Reduced needless memory usage of decoder. +*) 28 jun 2014: Removed fix_png setting, always support palette OOB for + simplicity. Made ColorProfile public. +*) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization. +*) 22 dec 2013: Power of two windowsize required for optimization. +*) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. +*) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). +*) 11 mar 2013: (!) Bugfix with custom free. Changed from "my" to "lodepng_" + prefix for the custom allocators and made it possible with a new #define to + use custom ones in your project without needing to change lodepng's code. +*) 28 jan 2013: Bugfix with color key. +*) 27 okt 2012: Tweaks in text chunk keyword length error handling. +*) 8 okt 2012: (!) Added new filter strategy (entropy) and new auto color mode. + (no palette). Better deflate tree encoding. New compression tweak settings. + Faster color conversions while decoding. Some internal cleanups. +*) 23 sep 2012: Reduced warnings in Visual Studio a little bit. +*) 1 sep 2012: (!) Removed #define's for giving custom (de)compression functions + and made it work with function pointers instead. +*) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc + and free functions and toggle #defines from compiler flags. Small fixes. +*) 6 may 2012: (!) Made plugging in custom zlib/deflate functions more flexible. +*) 22 apr 2012: (!) Made interface more consistent, renaming a lot. Removed + redundant C++ codec classes. Reduced amount of structs. Everything changed, + but it is cleaner now imho and functionality remains the same. Also fixed + several bugs and shrunk the implementation code. Made new samples. +*) 6 nov 2011: (!) By default, the encoder now automatically chooses the best + PNG color model and bit depth, based on the amount and type of colors of the + raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. +*) 9 okt 2011: simpler hash chain implementation for the encoder. +*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. +*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. + A bug with the PNG filtertype heuristic was fixed, so that it chooses much + better ones (it's quite significant). A setting to do an experimental, slow, + brute force search for PNG filter types is added. +*) 17 aug 2011: (!) changed some C zlib related function names. +*) 16 aug 2011: made the code less wide (max 120 characters per line). +*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. +*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. +*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman + to optimize long sequences of zeros. +*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and + LodePNG_InfoColor_canHaveAlpha functions for convenience. +*) 7 nov 2010: added LodePNG_error_text function to get error code description. +*) 30 okt 2010: made decoding slightly faster +*) 26 okt 2010: (!) changed some C function and struct names (more consistent). + Reorganized the documentation and the declaration order in the header. +*) 08 aug 2010: only changed some comments and external samples. +*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. +*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also various fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release (C++, decoder only) + + +13. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2022 Lode Vandevenne +*/ diff --git a/github_source/minicv2/include/omv_boardconfig.h b/github_source/minicv2/include/omv_boardconfig.h new file mode 100644 index 0000000..fc8a59e --- /dev/null +++ b/github_source/minicv2/include/omv_boardconfig.h @@ -0,0 +1,502 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Board configuration and pin definitions. + */ +#ifndef __OMV_BOARDCONFIG_H__ +#define __OMV_BOARDCONFIG_H__ +#include "arm_compat.h" +#ifdef __cplusplus +extern "C" { +#endif +// // Architecture info +// #define OMV_ARCH_STR "PORTENTA H7 8192 SDRAM" // 33 chars max +// #define OMV_BOARD_TYPE "H7" +// #define OMV_UNIQUE_ID_ADDR 0x1FF1E800 +// #define OMV_UNIQUE_ID_SIZE 3 // 3 words + +// // Flash sectors for the bootloader. +// // Flash FS sector, main FW sector, max sector. +// #define OMV_FLASH_LAYOUT {1, 2, 15} + +// #define OMV_XCLK_MCO (0U) +// #define OMV_XCLK_TIM (1U) +// #define OMV_XCLK_OSC (2U) + +// // Sensor external clock source. +// #define OMV_XCLK_SOURCE (OMV_XCLK_TIM) + +// // Sensor external clock timer frequency. +// #define OMV_XCLK_FREQUENCY (6000000) + +// // Sensor PLL register value. +// #define OMV_OV7725_PLL_CONFIG (0x41) // x4 + +// // Sensor Banding Filter Value +// #define OMV_OV7725_BANDING (0x7F) + +// // Bootloader LED GPIO port/pin +// #define OMV_BOOTLDR_LED_PIN (GPIO_PIN_6) +// #define OMV_BOOTLDR_LED_PORT (GPIOK) + +// // RAW buffer size +// #define OMV_RAW_BUF_SIZE (4*1024*1024) + +// // Enable hardware JPEG +// // #define OMV_HARDWARE_JPEG (1) + +// // Enable MDMA sensor offload. +// // #define OMV_ENABLE_SENSOR_MDMA (1) + +// // Enable sensor drivers +// #define OMV_ENABLE_OV2640 (0) +// #define OMV_ENABLE_OV5640 (0) +// #define OMV_ENABLE_OV7690 (0) +// #define OMV_ENABLE_OV7725 (1) +// #define OMV_ENABLE_OV9650 (0) +// #define OMV_ENABLE_MT9M114 (0) +// #define OMV_ENABLE_MT9V034 (1) +// #define OMV_ENABLE_LEPTON (0) +// #define OMV_ENABLE_HM01B0 (1) +// #define OMV_ENABLE_GC2145 (1) + +// // Enable sensor features +// #define OMV_ENABLE_OV5640_AF (0) + +// // Enable WiFi debug +// #define OMV_ENABLE_WIFIDBG (0) + +// // Enable self-tests on first boot +// #define OMV_ENABLE_SELFTEST (0) + +// // If buffer size is bigger than this threshold, the quality is reduced. +// // This is only used for JPEG images sent to the IDE not normal compression. +// #define JPEG_QUALITY_THRESH (320*240*2) + +// // Low and high JPEG QS. +// #define JPEG_QUALITY_LOW 50 +// #define JPEG_QUALITY_HIGH 90 + +// FB Heap Block Size +// #define OMV_UMM_BLOCK_SIZE 256 + +// // Core VBAT for selftests +// #define OMV_CORE_VBAT "3.0" + +// // USB IRQn. +// #define OMV_USB_IRQN (OTG_HS_IRQn) + +// // Defined for cpu frequency scaling to override the revid. +// #define OMV_MAX_CPU_FREQ (400) + +// // PLL1 400MHz/40MHz for SDMMC and FDCAN +// // USB and RNG are clocked from the HSI48 +// #define OMV_OSC_PLL1M (5) +// #define OMV_OSC_PLL1N (160) +// #define OMV_OSC_PLL1P (2) +// #define OMV_OSC_PLL1Q (16) +// #define OMV_OSC_PLL1R (2) +// #define OMV_OSC_PLL1VCI (RCC_PLL1VCIRANGE_2) +// #define OMV_OSC_PLL1VCO (RCC_PLL1VCOWIDE) +// #define OMV_OSC_PLL1FRAC (0) + +// // PLL2 200MHz for FMC and QSPI. +// #define OMV_OSC_PLL2M (5) +// #define OMV_OSC_PLL2N (80) +// #define OMV_OSC_PLL2P (2) +// #define OMV_OSC_PLL2Q (2) +// #define OMV_OSC_PLL2R (2) +// #define OMV_OSC_PLL2VCI (RCC_PLL2VCIRANGE_2) +// #define OMV_OSC_PLL2VCO (RCC_PLL2VCOWIDE) +// #define OMV_OSC_PLL2FRAC (0) + +// // PLL3 160MHz for ADC and SPI123 +// #define OMV_OSC_PLL3M (5) +// #define OMV_OSC_PLL3N (160) +// #define OMV_OSC_PLL3P (2) +// #define OMV_OSC_PLL3Q (5) +// #define OMV_OSC_PLL3R (2) +// #define OMV_OSC_PLL3VCI (RCC_PLL3VCIRANGE_2) +// #define OMV_OSC_PLL3VCO (RCC_PLL3VCOWIDE) +// #define OMV_OSC_PLL3FRAC (0) + +// // Clock Sources +// #define OMV_OSC_PLL_CLKSOURCE RCC_PLLSOURCE_HSE +// #define OMV_OSC_USB_CLKSOURCE RCC_USBCLKSOURCE_HSI48 +// #define OMV_OSC_RNG_CLKSOURCE RCC_RNGCLKSOURCE_HSI48 +// #define OMV_OSC_ADC_CLKSOURCE RCC_ADCCLKSOURCE_PLL3 +// #define OMV_OSC_SPI123_CLKSOURCE RCC_SPI123CLKSOURCE_PLL3 + +// // HSE/HSI/CSI State +// #define OMV_OSC_HSE_STATE (RCC_HSE_BYPASS) +// #define OMV_OSC_HSI48_STATE (RCC_HSI48_ON) + +// // Flash Latency +// #define OMV_FLASH_LATENCY (FLASH_LATENCY_2) + +// // Power supply configuration +// #define OMV_PWR_SUPPLY (PWR_SMPS_1V8_SUPPLIES_LDO) + +// // Linker script constants (see the linker script template stm32fxxx.ld.S). +// // Note: fb_alloc is a stack-based, dynamically allocated memory on FB. +// // The maximum available fb_alloc memory = FB_ALLOC_SIZE + FB_SIZE - (w*h*bpp). +// #define OMV_MAIN_MEMORY SRAM1 // data, bss and heap +// #define OMV_STACK_MEMORY DTCM // stack memory +// #define OMV_DMA_MEMORY SRAM3 // DMA buffers memory. +// #define OMV_FB_MEMORY DRAM // Framebuffer, fb_alloc +// #define OMV_JPEG_MEMORY DRAM // JPEG buffer memory buffer. +// #define OMV_JPEG_MEMORY_OFFSET (7M) // JPEG buffer is placed after FB/fballoc memory. +// #define OMV_VOSPI_MEMORY SRAM4 // VoSPI buffer memory. +// #define OMV_FB_OVERLAY_MEMORY AXI_SRAM // Fast fb_alloc memory. +// #define OMV_FB_OVERLAY_MEMORY_OFFSET (480*1024) // Fast fb_alloc memory size. +// #define OMV_CYW43_MEMORY FLASH_EXT // CYW43 firmware in external flash mmap'd flash. +// #define OMV_CYW43_MEMORY_OFFSET (0x90F00000)// Last Mbyte. + +// #define OMV_FB_SIZE (4 * 1024 * 1024) // FB memory: header + VGA/GS image +// #define OMV_FB_ALLOC_SIZE (4 * 1024 * 1024) // minimum fb alloc size +// #define OMV_STACK_SIZE (64K) +// #define OMV_HEAP_SIZE (160K) +// #define OMV_SDRAM_SIZE (8 * 1024 * 1024) // This needs to be here for UVC firmware. + +// #define OMV_LINE_BUF_SIZE (11 * 1024) // Image line buffer round(2592 * 2BPP * 2 buffers). +// #define OMV_MSC_BUF_SIZE (2K) // USB MSC bot data +// #define OMV_VFS_BUF_SIZE (1K) // VFS sturct + FATFS file buffer (624 bytes) +// #define OMV_FIR_LEPTON_BUF_SIZE (1K) // FIR Lepton Packet Double Buffer (328 bytes) +// #define OMV_JPEG_BUF_SIZE (1024*1024) // IDE JPEG buffer (header + data). + +// #define OMV_BOOT_ORIGIN 0x08000000 +// #define OMV_BOOT_LENGTH 128K +// #define OMV_TEXT_ORIGIN 0x08040000 +// #define OMV_TEXT_LENGTH 1792K +// #define OMV_DTCM_ORIGIN 0x20000000 // Note accessible by CPU and MDMA only. +// #define OMV_DTCM_LENGTH 128K +// #define OMV_SRAM1_ORIGIN 0x30000000 +// #define OMV_SRAM1_LENGTH 256K // SRAM1 + SRAM2 +// #define OMV_SRAM3_ORIGIN 0x30040000 // Second half of SRAM3 reserved for M4. +// #define OMV_SRAM3_LENGTH 16K +// #define OMV_SRAM4_ORIGIN 0x38000000 +// #define OMV_SRAM4_LENGTH 64K +// #define OMV_AXI_SRAM_ORIGIN 0x24000000 +// #define OMV_AXI_SRAM_LENGTH 512K +// #define OMV_DRAM_ORIGIN 0xC0000000 +// #define OMV_DRAM_LENGTH 8M +// #define OMV_FLASH_EXT_ORIGIN 0x90000000 +// #define OMV_FLASH_EXT_LENGTH 16M +// #define OMV_CM4_RAM_ORIGIN 0x30044000 // Cortex-M4 memory. +// #define OMV_CM4_RAM_LENGTH 16K +// #define OMV_CM4_FLASH_ORIGIN 0x08020000 +// #define OMV_CM4_FLASH_LENGTH 128K + +// // Domain 1 DMA buffers region. +// #define OMV_DMA_MEMORY_D1 AXI_SRAM +// #define OMV_DMA_MEMORY_D1_SIZE (16*1024) // Reserved memory for DMA buffers +// #define OMV_DMA_REGION_D1_BASE (OMV_AXI_SRAM_ORIGIN+OMV_FB_OVERLAY_MEMORY_OFFSET) +// #define OMV_DMA_REGION_D1_SIZE MPU_REGION_SIZE_32KB + +// // Domain 2 DMA buffers region. +// #define OMV_DMA_MEMORY_D2 SRAM3 +// #define OMV_DMA_MEMORY_D2_SIZE (1*1024) // Reserved memory for DMA buffers +// #define OMV_DMA_REGION_D2_BASE (OMV_SRAM3_ORIGIN+(0*1024)) +// #define OMV_DMA_REGION_D2_SIZE MPU_REGION_SIZE_16KB + +// // Domain 3 DMA buffers region. +// #define OMV_DMA_MEMORY_D3 SRAM4 +// #define OMV_DMA_MEMORY_D3_SIZE (32*1024) // Reserved memory for DMA buffers +// #define OMV_DMA_REGION_D3_BASE (OMV_SRAM4_ORIGIN+(0*1024)) +// #define OMV_DMA_REGION_D3_SIZE MPU_REGION_SIZE_64KB + +// // AXI QoS - Low-High (0:15) - default 0 +// #define OMV_AXI_QOS_MDMA_R_PRI 14 // Max pri to move data. +// #define OMV_AXI_QOS_MDMA_W_PRI 15 // Max pri to move data. +// #define OMV_AXI_QOS_LTDC_R_PRI 15 // Max pri to read out the frame buffer. + +// // Image sensor I2C +// #define ISC_I2C (I2C3) +// #define ISC_I2C_ID (3) +// #define ISC_I2C_AF (GPIO_AF4_I2C3) +// #define ISC_I2C_CLK_ENABLE() __I2C3_CLK_ENABLE() +// #define ISC_I2C_CLK_DISABLE() __I2C3_CLK_DISABLE() +// #define ISC_I2C_PORT (GPIOH) +// #define ISC_I2C_SCL_PIN (GPIO_PIN_7) +// #define ISC_I2C_SDA_PIN (GPIO_PIN_8) +// #define ISC_I2C_SPEED (CAMBUS_SPEED_STANDARD) +// #define ISC_I2C_FORCE_RESET() __HAL_RCC_I2C3_FORCE_RESET() +// #define ISC_I2C_RELEASE_RESET() __HAL_RCC_I2C3_RELEASE_RESET() + +// // Alternate I2C bus for the Portenta breakout +// #define ISC_I2C_ALT (I2C4) +// #define ISC_I2C_ALT_ID (4) +// #define ISC_I2C_ALT_AF (GPIO_AF4_I2C4) +// #define ISC_I2C_ALT_CLK_ENABLE() __HAL_RCC_I2C4_CLK_ENABLE() +// #define ISC_I2C_ALT_CLK_DISABLE() __HAL_RCC_I2C4_CLK_DISABLE() +// #define ISC_I2C_ALT_PORT (GPIOH) +// #define ISC_I2C_ALT_SCL_PIN (GPIO_PIN_11) +// #define ISC_I2C_ALT_SDA_PIN (GPIO_PIN_12) +// #define ISC_I2C_ALT_SPEED (CAMBUS_SPEED_STANDARD) +// #define ISC_I2C_ALT_FORCE_RESET() __HAL_RCC_I2C4_FORCE_RESET() +// #define ISC_I2C_ALT_RELEASE_RESET() __HAL_RCC_I2C4_RELEASE_RESET() + +// // FIR I2C +// #define FIR_I2C (I2C3) +// #define FIR_I2C_ID (3) +// #define FIR_I2C_AF (GPIO_AF4_I2C3) +// #define FIR_I2C_CLK_ENABLE() __I2C3_CLK_ENABLE() +// #define FIR_I2C_CLK_DISABLE() __I2C3_CLK_DISABLE() +// #define FIR_I2C_PORT (GPIOH) +// #define FIR_I2C_SCL_PIN (GPIO_PIN_7) +// #define FIR_I2C_SDA_PIN (GPIO_PIN_8) +// #define FIR_I2C_SPEED (CAMBUS_SPEED_STANDARD) +// #define FIR_I2C_FORCE_RESET() __HAL_RCC_I2C3_FORCE_RESET() +// #define FIR_I2C_RELEASE_RESET() __HAL_RCC_I2C3_RELEASE_RESET() + +// // GPIO.0 is connected to the sensor module reset pin on the Portenta +// // breakout board and to the LDO's LDO_ENABLE pin on the Himax shield. +// // The sensor probing process will detect the right reset or powerdown +// // polarity, so it should be fine to enable it for both boards. +// #define DCMI_RESET_PIN (GPIO_PIN_13) +// #define DCMI_RESET_PORT (GPIOC) + +// // GPIO.1 is connected to the sensor module frame sync pin (OUTPUT) on +// // the Portenta breakout board and to the INT pin (OUTPUT) on the Himax +// // shield, so it can't be enabled for the two boards at the same time. +// //#define DCMI_FSYNC_PIN (GPIO_PIN_15) +// //#define DCMI_FSYNC_PORT (GPIOC) + +// // GPIO.3 is connected to the powerdown pin on the Portenta breakout board, +// // and to the STROBE pin on the Himax shield, however it's not actually +// // used on the Himax shield and can be safely enable for the two boards. +// #define DCMI_PWDN_PIN (GPIO_PIN_5) +// #define DCMI_PWDN_PORT (GPIOD) + +// /* DCMI */ +// #define DCMI_TIM (TIM1) +// #define DCMI_TIM_PIN (GPIO_PIN_1) +// #define DCMI_TIM_PORT (GPIOK) +// // Enable TIM1-CH1 on PA8 too for Portenta breakout. +// #define DCMI_TIM_EXT_PIN (GPIO_PIN_8) +// #define DCMI_TIM_EXT_PORT (GPIOA) +// #define DCMI_TIM_AF (GPIO_AF1_TIM1) +// #define DCMI_TIM_CHANNEL (TIM_CHANNEL_1) +// #define DCMI_TIM_CLK_ENABLE() __TIM1_CLK_ENABLE() +// #define DCMI_TIM_CLK_DISABLE() __TIM1_CLK_DISABLE() +// #define DCMI_TIM_PCLK_FREQ() HAL_RCC_GetPCLK2Freq() + +// #define DCMI_D0_PIN (GPIO_PIN_9) +// #define DCMI_D1_PIN (GPIO_PIN_10) +// #define DCMI_D2_PIN (GPIO_PIN_11) +// #define DCMI_D3_PIN (GPIO_PIN_12) +// #define DCMI_D4_PIN (GPIO_PIN_14) +// #define DCMI_D5_PIN (GPIO_PIN_4) +// #define DCMI_D6_PIN (GPIO_PIN_6) +// #define DCMI_D7_PIN (GPIO_PIN_7) + +// #define DCMI_D0_PORT (GPIOH) +// #define DCMI_D1_PORT (GPIOH) +// #define DCMI_D2_PORT (GPIOH) +// #define DCMI_D3_PORT (GPIOH) +// #define DCMI_D4_PORT (GPIOH) +// #define DCMI_D5_PORT (GPIOI) +// #define DCMI_D6_PORT (GPIOI) +// #define DCMI_D7_PORT (GPIOI) + +// #define DCMI_HSYNC_PIN (GPIO_PIN_4) +// #define DCMI_VSYNC_PIN (GPIO_PIN_5) +// #define DCMI_PXCLK_PIN (GPIO_PIN_6) + +// #define DCMI_HSYNC_PORT (GPIOA) +// #define DCMI_VSYNC_PORT (GPIOI) +// #define DCMI_PXCLK_PORT (GPIOA) + +// #if defined(DCMI_RESET_PIN) +// #define DCMI_RESET_LOW() HAL_GPIO_WritePin(DCMI_RESET_PORT, DCMI_RESET_PIN, GPIO_PIN_RESET) +// #define DCMI_RESET_HIGH() HAL_GPIO_WritePin(DCMI_RESET_PORT, DCMI_RESET_PIN, GPIO_PIN_SET) +// #else +// #define DCMI_RESET_LOW() +// #define DCMI_RESET_HIGH() +// #endif + +// #if defined(DCMI_PWDN_PIN) +// #define DCMI_PWDN_LOW() HAL_GPIO_WritePin(DCMI_PWDN_PORT, DCMI_PWDN_PIN, GPIO_PIN_RESET) +// #define DCMI_PWDN_HIGH() HAL_GPIO_WritePin(DCMI_PWDN_PORT, DCMI_PWDN_PIN, GPIO_PIN_SET) +// #else +// #define DCMI_PWDN_LOW() +// #define DCMI_PWDN_HIGH() +// #endif + +// #if defined(DCMI_FSYNC_PIN) +// #define DCMI_FSYNC_LOW() HAL_GPIO_WritePin(DCMI_FSYNC_PORT, DCMI_FSYNC_PIN, GPIO_PIN_RESET) +// #define DCMI_FSYNC_HIGH() HAL_GPIO_WritePin(DCMI_FSYNC_PORT, DCMI_FSYNC_PIN, GPIO_PIN_SET) +// #else +// #define DCMI_FSYNC_LOW() +// #define DCMI_FSYNC_HIGH() +// #endif + +// #define DCMI_VSYNC_IRQN EXTI9_5_IRQn +// #define DCMI_VSYNC_IRQ_LINE (7) + +// #define SOFT_I2C_PORT GPIOB +// #define SOFT_I2C_SIOC_PIN GPIO_PIN_10 +// #define SOFT_I2C_SIOD_PIN GPIO_PIN_11 + +// #define SOFT_I2C_SIOC_H() HAL_GPIO_WritePin(SOFT_I2C_PORT, SOFT_I2C_SIOC_PIN, GPIO_PIN_SET) +// #define SOFT_I2C_SIOC_L() HAL_GPIO_WritePin(SOFT_I2C_PORT, SOFT_I2C_SIOC_PIN, GPIO_PIN_RESET) + +// #define SOFT_I2C_SIOD_H() HAL_GPIO_WritePin(SOFT_I2C_PORT, SOFT_I2C_SIOD_PIN, GPIO_PIN_SET) +// #define SOFT_I2C_SIOD_L() HAL_GPIO_WritePin(SOFT_I2C_PORT, SOFT_I2C_SIOD_PIN, GPIO_PIN_RESET) + +// #define SOFT_I2C_SIOD_READ() HAL_GPIO_ReadPin (SOFT_I2C_PORT, SOFT_I2C_SIOD_PIN) +// #define SOFT_I2C_SIOD_WRITE(bit) HAL_GPIO_WritePin(SOFT_I2C_PORT, SOFT_I2C_SIOD_PIN, bit); + +// #define SOFT_I2C_SPIN_DELAY 64 + +// // SAI4 +// #define AUDIO_SAI (SAI4_Block_A) +// // SCKx frequency = SAI_KER_CK / MCKDIV / 2 +// #define AUDIO_SAI_MCKDIV (12) +// #define AUDIO_SAI_FREQKHZ (2048U) // 2048KHz +// #define AUDIO_SAI_NBR_CHANNELS (2) // Default number of channels. + +// #define AUDIO_SAI_CK_PORT (GPIOE) +// #define AUDIO_SAI_CK_PIN (GPIO_PIN_2) +// #define AUDIO_SAI_CK_AF (GPIO_AF10_SAI4) + +// #define AUDIO_SAI_D1_PORT (GPIOB) +// #define AUDIO_SAI_D1_PIN (GPIO_PIN_2) +// #define AUDIO_SAI_D1_AF (GPIO_AF10_SAI4) + +// #define AUDIO_SAI_DMA_STREAM BDMA_Channel1 +// #define AUDIO_SAI_DMA_REQUEST BDMA_REQUEST_SAI4_A +// #define AUDIO_SAI_DMA_IRQ BDMA_Channel1_IRQn +// #define AUDIO_SAI_DMA_IRQHandler BDMA_Channel1_IRQHandler + +// #define AUDIO_SAI_CLK_ENABLE() __HAL_RCC_SAI4_CLK_ENABLE() +// #define AUDIO_SAI_CLK_DISABLE() __HAL_RCC_SAI4_CLK_DISABLE() +// #define AUDIO_SAI_DMA_CLK_ENABLE() __HAL_RCC_BDMA_CLK_ENABLE() + +// // SAI1 +// // Set SAI1 clock source in system ex: Sai1ClockSelection = RCC_SAI1CLKSOURCE_PLL; +// // #define AUDIO_SAI (SAI1_Block_A) +// // #define AUDIO_SAI_MCKDIV (12) +// // #define AUDIO_SAI_FREQKHZ (2048U) // 2048KHz +// // #define AUDIO_SAI_NBR_CHANNELS (2) // Default number of channels. +// // +// // #define AUDIO_SAI_CK_PORT (GPIOE) +// // #define AUDIO_SAI_CK_PIN (GPIO_PIN_2) +// // #define AUDIO_SAI_CK_AF (GPIO_AF2_SAI1) +// // +// // #define AUDIO_SAI_D1_PORT (GPIOB) +// // #define AUDIO_SAI_D1_PIN (GPIO_PIN_2) +// // #define AUDIO_SAI_D1_AF (GPIO_AF2_SAI1) +// // +// // #define AUDIO_SAI_DMA_STREAM DMA2_Stream6 +// // #define AUDIO_SAI_DMA_REQUEST DMA_REQUEST_SAI1_A +// // #define AUDIO_SAI_DMA_IRQ DMA2_Stream6_IRQn +// // #define AUDIO_SAI_DMA_IRQHandler DMA2_Stream6_IRQHandler +// // +// // #define AUDIO_SAI_CLK_ENABLE() __HAL_RCC_SAI1_CLK_ENABLE() +// // #define AUDIO_SAI_CLK_DISABLE() __HAL_RCC_SAI1_CLK_DISABLE() +// // #define AUDIO_SAI_DMA_CLK_ENABLE() __HAL_RCC_DMA2_CLK_ENABLE() + +// // LCD Interface +// #define OMV_LCD_CONTROLLER (LTDC) +// #define OMV_LCD_CLK_ENABLE() __HAL_RCC_LTDC_CLK_ENABLE() +// #define OMV_LCD_CLK_DISABLE() __HAL_RCC_LTDC_CLK_DISABLE() +// #define OMV_LCD_FORCE_RESET() __HAL_RCC_LTDC_FORCE_RESET() +// #define OMV_LCD_RELEASE_RESET() __HAL_RCC_LTDC_RELEASE_RESET() + +// // DSI Interface +// #define OMV_DSI_CONTROLLER (DSI) +// #define OMV_DSI_CLK_ENABLE() __HAL_RCC_DSI_CLK_ENABLE() +// #define OMV_DSI_CLK_DISABLE() __HAL_RCC_DSI_CLK_DISABLE() +// #define OMV_DSI_FORCE_RESET() __HAL_RCC_DSI_FORCE_RESET() +// #define OMV_DSI_RELEASE_RESET() __HAL_RCC_DSI_RELEASE_RESET() + +// // SPI LCD Interface +// #define OMV_SPI_LCD_CONTROLLER (&spi_obj[1]) +// #define OMV_SPI_LCD_CONTROLLER_INSTANCE (SPI2) + +// #define OMV_SPI_LCD_MOSI_PIN (GPIO_PIN_3) +// #define OMV_SPI_LCD_MOSI_PORT (GPIOC) +// #define OMV_SPI_LCD_MOSI_ALT (GPIO_AF5_SPI2) + +// #define OMV_SPI_LCD_MISO_PIN (GPIO_PIN_2) +// #define OMV_SPI_LCD_MISO_PORT (GPIOC) +// #define OMV_SPI_LCD_MISO_ALT (GPIO_AF5_SPI2) + +// #define OMV_SPI_LCD_SCLK_PIN (GPIO_PIN_1) +// #define OMV_SPI_LCD_SCLK_PORT (GPIOI) +// #define OMV_SPI_LCD_SCLK_ALT (GPIO_AF5_SPI2) + +// #define OMV_SPI_LCD_RST_PIN (GPIO_PIN_15) +// #define OMV_SPI_LCD_RST_PORT (GPIOH) +// #define OMV_SPI_LCD_RST_OFF() HAL_GPIO_WritePin(OMV_SPI_LCD_RST_PORT, OMV_SPI_LCD_RST_PIN, GPIO_PIN_SET) +// #define OMV_SPI_LCD_RST_ON() HAL_GPIO_WritePin(OMV_SPI_LCD_RST_PORT, OMV_SPI_LCD_RST_PIN, GPIO_PIN_RESET) + +// #define OMV_SPI_LCD_RS_PIN (GPIO_PIN_1) +// #define OMV_SPI_LCD_RS_PORT (GPIOK) +// #define OMV_SPI_LCD_RS_OFF() HAL_GPIO_WritePin(OMV_SPI_LCD_RS_PORT, OMV_SPI_LCD_RS_PIN, GPIO_PIN_SET) +// #define OMV_SPI_LCD_RS_ON() HAL_GPIO_WritePin(OMV_SPI_LCD_RS_PORT, OMV_SPI_LCD_RS_PIN, GPIO_PIN_RESET) + +// #define OMV_SPI_LCD_CS_PIN (GPIO_PIN_0) +// #define OMV_SPI_LCD_CS_PORT (GPIOI) +// #define OMV_SPI_LCD_CS_HIGH() HAL_GPIO_WritePin(OMV_SPI_LCD_CS_PORT, OMV_SPI_LCD_CS_PIN, GPIO_PIN_SET) +// #define OMV_SPI_LCD_CS_LOW() HAL_GPIO_WritePin(OMV_SPI_LCD_CS_PORT, OMV_SPI_LCD_CS_PIN, GPIO_PIN_RESET) + +// #define OMV_SPI_LCD_BL_PIN (GPIO_PIN_4) +// #define OMV_SPI_LCD_BL_PORT (GPIOA) +// #define OMV_SPI_LCD_BL_ON() HAL_GPIO_WritePin(OMV_SPI_LCD_BL_PORT, OMV_SPI_LCD_BL_PIN, GPIO_PIN_SET) +// #define OMV_SPI_LCD_BL_OFF() HAL_GPIO_WritePin(OMV_SPI_LCD_BL_PORT, OMV_SPI_LCD_BL_PIN, GPIO_PIN_RESET) + +// #define OMV_SPI_LCD_BL_DAC (DAC1) +// #define OMV_SPI_LCD_BL_DAC_CHANNEL (DAC_CHANNEL_1) +// #define OMV_SPI_LCD_BL_DAC_CLK_ENABLE() __HAL_RCC_DAC12_CLK_ENABLE() +// #define OMV_SPI_LCD_BL_DAC_CLK_DISABLE() __HAL_RCC_DAC12_CLK_DISABLE() +// #define OMV_SPI_LCD_BL_DAC_FORCE_RESET() __HAL_RCC_DAC12_FORCE_RESET() +// #define OMV_SPI_LCD_BL_DAC_RELEASE_RESET() __HAL_RCC_DAC12_RELEASE_RESET() + +// // FIR Module +// #define OMV_ENABLE_FIR_MLX90621 (1) +// #define OMV_ENABLE_FIR_MLX90640 (1) +// #define OMV_ENABLE_FIR_MLX90641 (1) +// #define OMV_ENABLE_FIR_AMG8833 (1) +// #define OMV_ENABLE_FIR_LEPTON (1) + +// // FIR Lepton +// #define OMV_FIR_LEPTON_I2C_BUS (FIR_I2C_ID) +// #define OMV_FIR_LEPTON_I2C_BUS_SPEED (FIR_I2C_SPEED) +// #define OMV_FIR_LEPTON_CONTROLLER (&spi_obj[1]) +// #define OMV_FIR_LEPTON_CONTROLLER_INSTANCE (SPI2) + +// #define OMV_FIR_LEPTON_MOSI_PIN (GPIO_PIN_3) +// #define OMV_FIR_LEPTON_MOSI_PORT (GPIOC) +// #define OMV_FIR_LEPTON_MOSI_ALT (GPIO_AF5_SPI2) + +// #define OMV_FIR_LEPTON_MISO_PIN (GPIO_PIN_2) +// #define OMV_FIR_LEPTON_MISO_PORT (GPIOC) +// #define OMV_FIR_LEPTON_MISO_ALT (GPIO_AF5_SPI2) + +// #define OMV_FIR_LEPTON_SCLK_PIN (GPIO_PIN_1) +// #define OMV_FIR_LEPTON_SCLK_PORT (GPIOI) +// #define OMV_FIR_LEPTON_SCLK_ALT (GPIO_AF5_SPI2) + +// #define OMV_FIR_LEPTON_CS_PIN (GPIO_PIN_0) +// #define OMV_FIR_LEPTON_CS_PORT (GPIOI) +// #define OMV_FIR_LEPTON_CS_HIGH() HAL_GPIO_WritePin(OMV_FIR_LEPTON_CS_PORT, OMV_FIR_LEPTON_CS_PIN, GPIO_PIN_SET) +// #define OMV_FIR_LEPTON_CS_LOW() HAL_GPIO_WritePin(OMV_FIR_LEPTON_CS_PORT, OMV_FIR_LEPTON_CS_PIN, GPIO_PIN_RESET) + +// // Enable additional GPIO banks for DRAM... +// #define OMV_ENABLE_GPIO_BANK_F +// #define OMV_ENABLE_GPIO_BANK_G +// #define OMV_ENABLE_GPIO_BANK_H +// #define OMV_ENABLE_GPIO_BANK_I +// #define OMV_ENABLE_GPIO_BANK_J +// #define OMV_ENABLE_GPIO_BANK_K +#ifdef __cplusplus +} +#endif +#endif //__OMV_BOARDCONFIG_H__ diff --git a/github_source/minicv2/include/ringbuf.h b/github_source/minicv2/include/ringbuf.h new file mode 100644 index 0000000..e371bec --- /dev/null +++ b/github_source/minicv2/include/ringbuf.h @@ -0,0 +1,32 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Simple Ring Buffer implementation. + */ +#ifndef __RING_BUFFER_H__ +#define __RING_BUFFER_H__ +#include +#ifdef __cplusplus +extern "C" { +#endif +#define BUFFER_SIZE (1024) + +typedef struct ring_buffer { + volatile uint32_t head; + volatile uint32_t tail; + uint8_t data[BUFFER_SIZE]; +} ring_buf_t; + +void ring_buf_init(ring_buf_t *buf); +int ring_buf_empty(ring_buf_t *buf); +void ring_buf_put(ring_buf_t *buf, uint8_t c); +uint8_t ring_buf_get(ring_buf_t *buf); +#ifdef __cplusplus +} +#endif +#endif /* __RING_BUFFER_H__ */ diff --git a/github_source/minicv2/include/umm_malloc.h b/github_source/minicv2/include/umm_malloc.h new file mode 100644 index 0000000..4eeca93 --- /dev/null +++ b/github_source/minicv2/include/umm_malloc.h @@ -0,0 +1,70 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * UMM memory allocator. + */ +#ifndef __UMM_MALLOC_H__ +#define __UMM_MALLOC_H__ +#include +#ifdef __cplusplus +extern "C" { +#endif +void umm_alloc_fail(); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "umm_malloc.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//The MIT License (MIT) + +//Copyright (c) 2015 Ralph Hempel + +//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. + +/* ---------------------------------------------------------------------------- + * umm_malloc.h - a memory allocator for embedded systems (microcontrollers) + * + * See copyright notice in LICENSE.TXT + * ---------------------------------------------------------------------------- + */ + + +/* ------------------------------------------------------------------------ */ + +void umm_init_x( size_t size ); // Min of 2.5KB - Max of 640 KB. +void *umm_malloc( size_t size ); +void *umm_calloc( size_t num, size_t size ); +void *umm_realloc( void *ptr, size_t size ); +void umm_free( void *ptr ); + + +/* ------------------------------------------------------------------------ */ + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +} +#endif +#endif /* __UMM_MALLOC_H__ */ diff --git a/github_source/minicv2/include/unaligned_memcpy.h b/github_source/minicv2/include/unaligned_memcpy.h new file mode 100644 index 0000000..6e8af7d --- /dev/null +++ b/github_source/minicv2/include/unaligned_memcpy.h @@ -0,0 +1,22 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Fast unaligned memcpy functions. + */ +#ifndef __UNALIGNED_MEMCPY_H__ +#define __UNALIGNED_MEMCPY_H__ +#ifdef __cplusplus +extern "C" { +#endif +void *unaligned_memcpy(void *dest, void *src, size_t n); +void *unaligned_memcpy_rev16(void *dest, void *src, size_t n); +void *unaligned_2_to_1_memcpy(void *dest, void *src, size_t n); +#ifdef __cplusplus +} +#endif +#endif //__UNALIGNED_MEMCPY_H__ diff --git a/github_source/minicv2/include/x_vfs.h b/github_source/minicv2/include/x_vfs.h new file mode 100644 index 0000000..58ef6d7 --- /dev/null +++ b/github_source/minicv2/include/x_vfs.h @@ -0,0 +1,12 @@ +#ifndef __X_VFS__ +#define __X_VFS__ + +#ifdef __cplusplus +extern "C" { +#endif + + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/github_source/minicv2/include/xalloc.h b/github_source/minicv2/include/xalloc.h new file mode 100644 index 0000000..df0100d --- /dev/null +++ b/github_source/minicv2/include/xalloc.h @@ -0,0 +1,34 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Memory allocation functions. + */ +#ifndef __XALLOC_H__ +#define __XALLOC_H__ +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +void *xalloc(size_t size); +void *xalloc_try_alloc(size_t size); +void *xalloc0(size_t size); +void xfree(void *mem); +void *xrealloc(void *mem, size_t size); + +void *xcalloc(size_t nitems, size_t size); + + +// #define xcalloc(num, item_size) calloc(num, item_size) + +#ifdef __cplusplus +} +#endif + +#endif // __XALLOC_H__ diff --git a/github_source/minicv2/openmv_unpdate.md b/github_source/minicv2/openmv_unpdate.md new file mode 100644 index 0000000..7f56bbe --- /dev/null +++ b/github_source/minicv2/openmv_unpdate.md @@ -0,0 +1,78 @@ +# openmv更新追踪 + +- 2022年2月8号更新记录 +更新log +~~~ bash +更新 be2aad13..9eb48bae +Fast-forward + .github/workflows/firmware.yml | 128 +++++ + .travis.yml | 150 ------ + scripts/libraries/rpc.py | 15 +- + src/Makefile | 2 + + src/lib/libtf/Makefile | 32 ++ + src/lib/libtf/cortex-m0plus/LICENSE | 248 +++++++++ + src/lib/libtf/cortex-m0plus/README | 1 + + src/lib/libtf/cortex-m0plus/libtf.a | Bin 0 -> 1095954 bytes + src/lib/libtf/cortex-m4/LICENSE | 45 ++ + src/lib/libtf/cortex-m55/LICENSE | 45 ++ + src/lib/libtf/cortex-m55/libtf.h | 83 --- + src/lib/libtf/cortex-m7/LICENSE | 45 ++ + src/lib/libtf/cortex-m7/libtf.h | 83 --- + src/lib/libtf/{cortex-m4 => }/libtf.h | 4 - + src/lib/libtf/models/person_detection.tflite | Bin 0 -> 300568 bytes + src/lib/libtf/models/person_detection.txt | 2 + + src/micropython | 2 +- + src/omv/Makefile | 2 + + src/omv/boards/{BORMIO => NICLAV}/imlib_config.h | 8 +- + src/omv/boards/{BORMIO => NICLAV}/manifest.py | 0 + src/omv/boards/{BORMIO => NICLAV}/omv_boardconfig.h | 4 +- + src/omv/boards/{BORMIO => NICLAV}/omv_boardconfig.mk | 0 + src/omv/boards/NICLAV/readme_txt.h | 19 + + src/omv/boards/{BORMIO => NICLAV}/ulab_config.h | 0 + src/omv/boards/OPENMV3/imlib_config.h | 6 +- + src/omv/boards/OPENMV4/imlib_config.h | 6 +- + src/omv/boards/OPENMV4P/imlib_config.h | 6 +- + src/omv/boards/OPENMVPT/imlib_config.h | 6 +- + src/omv/boards/PORTENTA/imlib_config.h | 4 + + src/omv/common/ini.c | 2 +- + src/omv/imlib/draw.c | 14 +- + src/omv/imlib/framebuffer.c | 4 +- + src/omv/imlib/imlib.c | 33 +- + src/omv/imlib/imlib.h | 35 +- + src/omv/imlib/jpeg.c | 10 + + src/omv/imlib/lodepng.c | 6503 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + src/omv/imlib/lodepng.h | 2026 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + src/omv/imlib/png.c | 337 ++++++++++++ + src/omv/modules/py_image.c | 256 ++++----- + src/omv/modules/py_tf.c | 62 ++- + src/omv/ports/nrf/omv_portconfig.mk | 2 + + src/omv/ports/rp2/omv_portconfig.cmake | 2 + + src/omv/ports/stm32/omv_portconfig.mk | 46 +- + tools/ci.sh | 51 ++ + tools/tflite2c.py | 77 +++ + 45 files changed, 9864 insertions(+), 542 deletions(-) + create mode 100644 .github/workflows/firmware.yml + delete mode 100644 .travis.yml + create mode 100644 src/lib/libtf/Makefile + create mode 100644 src/lib/libtf/cortex-m0plus/LICENSE + create mode 100644 src/lib/libtf/cortex-m0plus/README + create mode 100644 src/lib/libtf/cortex-m0plus/libtf.a + delete mode 100644 src/lib/libtf/cortex-m55/libtf.h + delete mode 100644 src/lib/libtf/cortex-m7/libtf.h + rename src/lib/libtf/{cortex-m4 => }/libtf.h (96%) + create mode 100644 src/lib/libtf/models/person_detection.tflite + create mode 100644 src/lib/libtf/models/person_detection.txt + rename src/omv/boards/{BORMIO => NICLAV}/imlib_config.h (94%) + rename src/omv/boards/{BORMIO => NICLAV}/manifest.py (100%) + rename src/omv/boards/{BORMIO => NICLAV}/omv_boardconfig.h (99%) + rename src/omv/boards/{BORMIO => NICLAV}/omv_boardconfig.mk (100%) + create mode 100644 src/omv/boards/NICLAV/readme_txt.h + rename src/omv/boards/{BORMIO => NICLAV}/ulab_config.h (100%) + create mode 100644 src/omv/imlib/lodepng.c + create mode 100644 src/omv/imlib/lodepng.h + create mode 100644 src/omv/imlib/png.c + create mode 100755 tools/ci.sh + create mode 100755 tools/tflite2c.py +~~~ +添加了 png 图片格式的支持 +等待合并到 minicv2 中! \ No newline at end of file diff --git a/github_source/minicv2/reference/py_assert.h b/github_source/minicv2/reference/py_assert.h new file mode 100644 index 0000000..edc8d00 --- /dev/null +++ b/github_source/minicv2/reference/py_assert.h @@ -0,0 +1,64 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * MP assertions. + */ +#ifndef __PY_ASSERT_H__ +#define __PY_ASSERT_H__ +#define PY_ASSERT_TRUE(cond) \ + do { \ + if ((cond) == 0) { \ + mp_raise_msg(&mp_type_OSError, \ + MP_ERROR_TEXT( \ + "Operation not supported")); \ + } \ + } while(0) + +#define PY_ASSERT_TRUE_MSG(cond, msg) \ + do { \ + if ((cond) == 0) { \ + mp_raise_msg(&mp_type_OSError, \ + MP_ERROR_TEXT(msg)); \ + } \ + } while(0) + +#define PY_ASSERT_FALSE_MSG(cond, msg) \ + do { \ + if ((cond) == 1) { \ + mp_raise_msg(&mp_type_OSError, \ + MP_ERROR_TEXT(msg)); \ + } \ + } while(0) + +#define PY_ASSERT_TYPE(obj, type) \ + do { \ + __typeof__ (obj) _a = (obj); \ + __typeof__ (type) _b = (type); \ + if (!MP_OBJ_IS_TYPE(_a, _b)) { \ + mp_raise_msg_varg(&mp_type_TypeError, \ + MP_ERROR_TEXT( \ + "Can't convert %s to %s"), \ + mp_obj_get_type_str(_a), \ + mp_obj_get_type_str(_b)); \ + } \ + } while(0) +/* IS_TYPE doesn't work for str objs */ +#define PY_ASSERT_STR(obj) \ + do { \ + __typeof__ (obj) _a = (obj); \ + if (!MP_OBJ_IS_STR(_a)) { \ + mp_raise_msg_varg( \ + &mp_type_TypeError, \ + MP_ERROR_TEXT( \ + "Can't convert %s to %s"), \ + mp_obj_get_type_str(_a), \ + str_type.name); \ + } \ + } while(0) + +#endif // __PY_ASSERT_H__ diff --git a/github_source/minicv2/reference/py_clock.c b/github_source/minicv2/reference/py_clock.c new file mode 100644 index 0000000..a684b54 --- /dev/null +++ b/github_source/minicv2/reference/py_clock.c @@ -0,0 +1,92 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Clock Python module. + */ +#include "py/obj.h" +#include "py/mphal.h" +#include "py_clock.h" + +/* Clock Type */ +typedef struct _py_clock_obj_t { + mp_obj_base_t base; + uint32_t t_start; + uint32_t t_ticks; + uint32_t t_frame; +} py_clock_obj_t; + +mp_obj_t py_clock_tick(mp_obj_t clock_obj) +{ + py_clock_obj_t *clock = (py_clock_obj_t*) clock_obj; + clock->t_start = mp_hal_ticks_ms(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_clock_tick_obj, py_clock_tick); + +mp_obj_t py_clock_fps(mp_obj_t clock_obj) +{ + py_clock_obj_t *clock = (py_clock_obj_t*) clock_obj; + clock->t_frame++; + clock->t_ticks += (mp_hal_ticks_ms() - clock->t_start); + float fps = 1000.0f / (clock->t_ticks/(float)clock->t_frame); + return mp_obj_new_float(fps); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_clock_fps_obj, py_clock_fps); + +mp_obj_t py_clock_avg(mp_obj_t clock_obj) +{ + py_clock_obj_t *clock = (py_clock_obj_t*) clock_obj; + clock->t_frame++; + clock->t_ticks += (mp_hal_ticks_ms() - clock->t_start); + return mp_obj_new_float(clock->t_ticks/(float)clock->t_frame); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_clock_avg_obj, py_clock_avg); + +mp_obj_t py_clock_reset(mp_obj_t clock_obj) +{ + py_clock_obj_t *clock = (py_clock_obj_t*) clock_obj; + clock->t_start = 0; + clock->t_ticks = 0; + clock->t_frame = 0; + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_clock_reset_obj, py_clock_reset); + +STATIC void py_clock_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_clock_obj_t *self = self_in; + mp_printf(print, "t_start:%d t_ticks:%d t_frame:%d\n", self->t_start, self->t_ticks, self->t_frame); +} + +mp_obj_t py_clock_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) +{ + py_clock_obj_t *clock =NULL; + clock = m_new_obj(py_clock_obj_t); + clock->base.type = &py_clock_type; + clock->t_start = 0; + clock->t_ticks = 0; + clock->t_frame = 0; + return MP_OBJ_FROM_PTR(clock); +} + +STATIC const mp_rom_map_elem_t py_clock_locals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR_tick), MP_ROM_PTR(&py_clock_tick_obj)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_fps), MP_ROM_PTR(&py_clock_fps_obj)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_avg), MP_ROM_PTR(&py_clock_avg_obj)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_reset), MP_ROM_PTR(&py_clock_reset_obj)}, + { NULL, NULL }, +}; +STATIC MP_DEFINE_CONST_DICT(py_clock_locals_dict, py_clock_locals_dict_table); + +const mp_obj_type_t py_clock_type = { + { &mp_type_type }, + .name = MP_QSTR_Clock, + .print = py_clock_print, + .make_new = py_clock_make_new, + .locals_dict = (mp_obj_t)&py_clock_locals_dict, +}; diff --git a/github_source/minicv2/reference/py_clock.h b/github_source/minicv2/reference/py_clock.h new file mode 100644 index 0000000..6945dd9 --- /dev/null +++ b/github_source/minicv2/reference/py_clock.h @@ -0,0 +1,14 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Clock Python module. + */ +#ifndef __PY_CLOCK_H__ +#define __PY_CLOCK_H__ +extern const mp_obj_type_t py_clock_type; +#endif // __PY_CLOCK_H__ diff --git a/github_source/minicv2/reference/py_fir.c b/github_source/minicv2/reference/py_fir.c new file mode 100644 index 0000000..ac11f07 --- /dev/null +++ b/github_source/minicv2/reference/py_fir.c @@ -0,0 +1,1269 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * FIR Python module. + */ +#include "py/runtime.h" +#include "py/objlist.h" + +#include "omv_boardconfig.h" +#include "cambus.h" +#if (OMV_ENABLE_FIR_MLX90621 == 1) +#include "MLX90621_API.h" +#include "MLX90621_I2C_Driver.h" +#endif +#if (OMV_ENABLE_FIR_MLX90640 == 1) +#include "MLX90640_API.h" +#include "MLX90640_I2C_Driver.h" +#endif +#if (OMV_ENABLE_FIR_MLX90641 == 1) +#include "MLX90641_API.h" +#include "MLX90641_I2C_Driver.h" +#endif +#include "framebuffer.h" + +#include "py_assert.h" +#include "py_helper.h" +#include "py_image.h" + +#if (OMV_ENABLE_FIR_LEPTON == 1) +#include "py_fir_lepton.h" +#endif + +#define MLX90621_ADDR 0x50 +#define MLX90621_WIDTH 16 +#define MLX90621_HEIGHT 4 +#define MLX90621_EEPROM_DATA_SIZE 256 +#define MLX90621_FRAME_DATA_SIZE 66 + +#define MLX90640_ADDR 0x33 +#define MLX90640_WIDTH 32 +#define MLX90640_HEIGHT 24 +#define MLX90640_EEPROM_DATA_SIZE 832 +#define MLX90640_FRAME_DATA_SIZE 834 + +#define MLX90641_ADDR 0x33 +#define MLX90641_WIDTH 16 +#define MLX90641_HEIGHT 12 +#define MLX90641_EEPROM_DATA_SIZE 832 +#define MLX90641_FRAME_DATA_SIZE 242 + +#define AMG8833_ADDR 0xD2 +#define AMG8833_WIDTH 8 +#define AMG8833_HEIGHT 8 +#define AMG8833_RESET_REGISTER 0x01 +#define AMG8833_THERMISTOR_REGISTER 0x0E +#define AMG8833_TEMPERATURE_REGISTER 0x80 +#define AMG8833_INITIAL_RESET_VALUE 0x3F + +#define LEPTON_ADDR 0x54 + +#define AMG8833_12_TO_16(value) \ +({ \ + __typeof__ (value) __value = (value); \ + if ((__value >> 11) & 1) { \ + __value |= 1 << 15; \ + } \ + __value & 0x87FF; \ +}) + +static cambus_t fir_bus = {}; +#if ((OMV_ENABLE_FIR_MLX90621 == 1) || (OMV_ENABLE_FIR_MLX90640 == 1) || (OMV_ENABLE_FIR_MLX90641 == 1)) +static void *fir_mlx_data = NULL; +#endif + +static enum { + FIR_NONE, +#if (OMV_ENABLE_FIR_MLX90621 == 1) + FIR_MLX90621, +#endif +#if (OMV_ENABLE_FIR_MLX90640 == 1) + FIR_MLX90640, +#endif +#if (OMV_ENABLE_FIR_MLX90641 == 1) + FIR_MLX90641, +#endif +#if (OMV_ENABLE_FIR_AMG8833 == 1) + FIR_AMG8833, +#endif +#if (OMV_ENABLE_FIR_LEPTON == 1) + FIR_LEPTON +#endif +} fir_sensor = FIR_NONE; + +static int fir_width = 0; +static int fir_height = 0; +static int fir_ir_fresh_rate = 0; +static int fir_adc_resolution = 0; +static bool fir_transposed = false; + +// img->w == data_w && img->h == data_h && img->pixfmt == PIXFORMAT_GRAYSCALE +static void fir_fill_image_float_obj(image_t *img, mp_obj_t *data, float min, float max) +{ + float tmp = min; + min = (min < max) ? min : max; + max = (max > tmp) ? max : tmp; + + float diff = 255.f / (max - min); + + for (int y = 0; y < img->h; y++) { + int row_offset = y * img->w; + mp_obj_t *raw_row = data + row_offset; + uint8_t *row_pointer = ((uint8_t *) img->data) + row_offset; + + for (int x = 0; x < img->w; x++) { + float raw = mp_obj_get_float(raw_row[x]); + + if (raw < min) { + raw = min; + } + + if (raw > max) { + raw = max; + } + + int pixel = fast_roundf((raw - min) * diff); + row_pointer[x] = __USAT(pixel, 8); + } + } +} + +#if (OMV_ENABLE_FIR_MLX90621 == 1) +static void fir_MLX90621_get_frame(float *Ta, float *To) +{ + uint16_t *data = fb_alloc(MLX90621_FRAME_DATA_SIZE * sizeof(uint16_t), FB_ALLOC_NO_HINT); + + PY_ASSERT_TRUE_MSG(MLX90621_GetFrameData(data) >= 0, + "Failed to read the MLX90621 sensor data!"); + *Ta = MLX90621_GetTa(data, fir_mlx_data); + MLX90621_CalculateTo(data, fir_mlx_data, 0.95f, *Ta - 8, To); + + fb_free(); +} +#endif + +#if (OMV_ENABLE_FIR_MLX90640 == 1) +static void fir_MLX90640_get_frame(float *Ta, float *To) +{ + uint16_t *data = fb_alloc(MLX90640_FRAME_DATA_SIZE * sizeof(uint16_t), FB_ALLOC_NO_HINT); + + // Calculate 1st sub-frame... + PY_ASSERT_TRUE_MSG(MLX90640_GetFrameData(MLX90640_ADDR, data) >= 0, + "Failed to read the MLX90640 sensor data!"); + *Ta = MLX90640_GetTa(data, fir_mlx_data); + MLX90640_CalculateTo(data, fir_mlx_data, 0.95f, *Ta - 8, To); + + // Calculate 2nd sub-frame... + PY_ASSERT_TRUE_MSG(MLX90640_GetFrameData(MLX90640_ADDR, data) >= 0, + "Failed to read the MLX90640 sensor data!"); + *Ta = MLX90640_GetTa(data, fir_mlx_data); + MLX90640_CalculateTo(data, fir_mlx_data, 0.95f, *Ta - 8, To); + + fb_free(); +} +#endif + +#if (OMV_ENABLE_FIR_MLX90641 == 1) +static void fir_MLX90641_get_frame(float *Ta, float *To) +{ + uint16_t *data = fb_alloc(MLX90641_FRAME_DATA_SIZE * sizeof(uint16_t), FB_ALLOC_NO_HINT); + + PY_ASSERT_TRUE_MSG(MLX90641_GetFrameData(MLX90641_ADDR, data) >= 0, + "Failed to read the MLX90641 sensor data!"); + *Ta = MLX90641_GetTa(data, fir_mlx_data); + MLX90641_CalculateTo(data, fir_mlx_data, 0.95f, *Ta - 8, To); + + fb_free(); +} +#endif + +#if (OMV_ENABLE_FIR_AMG8833 == 1) +static void fir_AMG8833_get_frame(float *Ta, float *To) +{ + int16_t temp; + int error = 0; + error |= cambus_write_bytes(&fir_bus, AMG8833_ADDR, (uint8_t [1]){AMG8833_THERMISTOR_REGISTER}, 1, CAMBUS_XFER_NO_STOP); + error |= cambus_read_bytes(&fir_bus, AMG8833_ADDR, (uint8_t *) &temp, sizeof(temp), CAMBUS_XFER_NO_FLAGS); + PY_ASSERT_TRUE_MSG((error == 0), "Failed to read the AMG8833 sensor data!"); + + *Ta = AMG8833_12_TO_16(temp) * 0.0625f; + + int16_t *data = fb_alloc(AMG8833_WIDTH * AMG8833_HEIGHT * sizeof(int16_t), FB_ALLOC_NO_HINT); + error |= cambus_write_bytes(&fir_bus, AMG8833_ADDR, (uint8_t [1]){AMG8833_TEMPERATURE_REGISTER}, 1, CAMBUS_XFER_NO_STOP); + error |= cambus_read_bytes(&fir_bus, AMG8833_ADDR, (uint8_t *) data, AMG8833_WIDTH * AMG8833_HEIGHT * 2, CAMBUS_XFER_NO_FLAGS); + PY_ASSERT_TRUE_MSG((error == 0), "Failed to read the AMG8833 sensor data!"); + + for (int i = 0, ii = AMG8833_WIDTH * AMG8833_HEIGHT; i < ii; i++) { + To[i] = AMG8833_12_TO_16(data[i]) * 0.25f; + } + + fb_free(); +} +#endif + +static mp_obj_t fir_get_ir(int w, int h, float Ta, float *To, bool mirror, bool flip, bool dst_transpose, bool src_transpose) +{ + mp_obj_list_t *list = (mp_obj_list_t *) mp_obj_new_list(w * h, NULL); + float min = FLT_MAX; + float max = -FLT_MAX; + int w_1 = w - 1; + int h_1 = h - 1; + + if (!src_transpose) { + for (int y = 0; y < h; y++) { + int y_dst = flip ? (h_1 - y) : y; + float *raw_row = To + (y * w); + mp_obj_t *list_row = list->items + (y_dst * w); + mp_obj_t *t_list_row = list->items + y_dst; + + for (int x = 0; x < w; x++) { + int x_dst = mirror ? (w_1 - x) : x; + float raw = raw_row[x]; + + if (raw < min) { + min = raw; + } + + if (raw > max) { + max = raw; + } + + mp_obj_t f = mp_obj_new_float(raw); + + if (!dst_transpose) { + list_row[x_dst] = f; + } else { + t_list_row[x_dst * h] = f; + } + } + } + } else { + for (int x = 0; x < w; x++) { + int x_dst = mirror ? (w_1 - x) : x; + float *raw_row = To + (x * h); + mp_obj_t *t_list_row = list->items + (x_dst * h); + mp_obj_t *list_row = list->items + x_dst; + + for (int y = 0; y < h; y++) { + int y_dst = flip ? (h_1 - y) : y; + float raw = raw_row[y]; + + if (raw < min) { + min = raw; + } + + if (raw > max) { + max = raw; + } + + mp_obj_t f = mp_obj_new_float(raw); + + if (!dst_transpose) { + list_row[y_dst * w] = f; + } else { + t_list_row[y_dst] = f; + } + } + } + } + + mp_obj_t tuple[4]; + tuple[0] = mp_obj_new_float(Ta); + tuple[1] = MP_OBJ_FROM_PTR(list); + tuple[2] = mp_obj_new_float(min); + tuple[3] = mp_obj_new_float(max); + return mp_obj_new_tuple(4, tuple); +} + +static mp_obj_t py_fir_deinit() +{ + #if (OMV_ENABLE_FIR_LEPTON == 1) + if (fir_sensor == FIR_LEPTON) { + fir_lepton_deinit(); + } + #endif + + if (fir_sensor != FIR_NONE) { + cambus_deinit(&fir_bus); + fir_sensor = FIR_NONE; + } + + #if ((OMV_ENABLE_FIR_MLX90621 == 1) || (OMV_ENABLE_FIR_MLX90640 == 1) || (OMV_ENABLE_FIR_MLX90641 == 1)) + if (fir_mlx_data != NULL) { + xfree(fir_mlx_data); + fir_mlx_data = NULL; + } + #endif + + fir_width = 0; + fir_height = 0; + fir_ir_fresh_rate = 0; + fir_adc_resolution = 0; + fir_transposed = false; + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_fir_deinit_obj, py_fir_deinit); + +mp_obj_t py_fir_init(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + py_fir_deinit(); + bool first_init = true; + int type = py_helper_keyword_int(n_args, args, 0, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_type), -1); + if (type == -1) { + FIR_SCAN_RETRY: + cambus_init(&fir_bus, FIR_I2C_ID, CAMBUS_SPEED_STANDARD); + switch (cambus_scan(&fir_bus)) { + #if (OMV_ENABLE_FIR_MLX90621 == 1) + case (MLX90621_ADDR << 1): { + type = FIR_MLX90621; + break; + } + #endif + #if (OMV_ENABLE_FIR_MLX90640 == 1) + case (MLX90640_ADDR << 1): { + type = FIR_MLX90640; + break; + } + #endif + #if (OMV_ENABLE_FIR_MLX90640 == 0) && (OMV_ENABLE_FIR_MLX90641 == 1) + case (MLX90641_ADDR << 1): { + type = FIR_MLX90641; + break; + } + #endif + #if (OMV_ENABLE_FIR_AMG8833 == 1) + case AMG8833_ADDR: { + type = FIR_AMG8833; + break; + } + #endif + #if (OMV_ENABLE_FIR_LEPTON == 1) + case LEPTON_ADDR: { + type = FIR_LEPTON; + break; + } + #endif + default: { + if (first_init) { + first_init = false; + // Try to recover the bus once as it may be stuck from an interrupted run. + // This is mostly needed for the AMG8833 + cambus_pulse_scl(&fir_bus); + goto FIR_SCAN_RETRY; + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Failed to detect any FIR sensor")); + } + } + } + cambus_deinit(&fir_bus); + } + + // Initialize the detected sensor. + first_init = true; + switch (type) { + case FIR_NONE: { + return mp_const_none; + } + #if (OMV_ENABLE_FIR_MLX90621 == 1) + case FIR_MLX90621: { + // parse refresh rate and ADC resolution + int ir_fresh_rate = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_refresh), 64); + int adc_resolution = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_resolution), 18); + + // sanitize values + ir_fresh_rate = 14 - __CLZ(__RBIT((ir_fresh_rate > 512) ? 512 : ((ir_fresh_rate < 1) ? 1 : ir_fresh_rate))); + adc_resolution = ((adc_resolution > 18) ? 18 : ((adc_resolution < 15) ? 15 : adc_resolution)) - 15; + + fir_mlx_data = xalloc(sizeof(paramsMLX90621)); + + fir_sensor = FIR_MLX90621; + FIR_MLX90621_RETRY: + cambus_init(&fir_bus, FIR_I2C_ID, CAMBUS_SPEED_FULL); // The EEPROM must be read at <= 400KHz. + MLX90621_I2CInit(&fir_bus); + + fb_alloc_mark(); + uint8_t *eeprom = fb_alloc(MLX90621_EEPROM_DATA_SIZE * sizeof(uint8_t), FB_ALLOC_NO_HINT); + int error = MLX90621_DumpEE(eeprom); + error |= MLX90621_Configure(eeprom); + error |= MLX90621_SetRefreshRate(ir_fresh_rate); + error |= MLX90621_SetResolution(adc_resolution); + error |= MLX90621_ExtractParameters(eeprom, fir_mlx_data); + fb_alloc_free_till_mark(); + + if (error != 0) { + if (first_init) { + first_init = false; + cambus_pulse_scl(&fir_bus); + goto FIR_MLX90621_RETRY; + } else { + py_fir_deinit(); + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Failed to init the MLX90621!")); + } + } + + // Switch to FAST speed + cambus_deinit(&fir_bus); + cambus_init(&fir_bus, FIR_I2C_ID, CAMBUS_SPEED_FAST); + fir_width = MLX90621_WIDTH; + fir_height = MLX90621_HEIGHT; + fir_ir_fresh_rate = ir_fresh_rate; + fir_adc_resolution = adc_resolution; + return mp_const_none; + } + #endif + #if (OMV_ENABLE_FIR_MLX90640 == 1) + case FIR_MLX90640: { + // parse refresh rate and ADC resolution + int ir_fresh_rate = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_refresh), 32); + int adc_resolution = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_resolution), 19); + + // sanitize values + ir_fresh_rate = __CLZ(__RBIT((ir_fresh_rate > 64) ? 64 : ((ir_fresh_rate < 1) ? 1 : ir_fresh_rate))) + 1; + adc_resolution = ((adc_resolution > 19) ? 19 : ((adc_resolution < 16) ? 16 : adc_resolution)) - 16; + + fir_mlx_data = xalloc(sizeof(paramsMLX90640)); + + fir_sensor = FIR_MLX90640; + FIR_MLX90640_RETRY: + cambus_init(&fir_bus, FIR_I2C_ID, CAMBUS_SPEED_FULL); // The EEPROM must be read at <= 400KHz. + MLX90640_I2CInit(&fir_bus); + + fb_alloc_mark(); + uint16_t *eeprom = fb_alloc(MLX90640_EEPROM_DATA_SIZE * sizeof(uint16_t), FB_ALLOC_NO_HINT); + int error = MLX90640_DumpEE(MLX90640_ADDR, eeprom); + error |= MLX90640_SetRefreshRate(MLX90640_ADDR, ir_fresh_rate); + error |= MLX90640_SetResolution(MLX90640_ADDR, adc_resolution); + error |= MLX90640_ExtractParameters(eeprom, fir_mlx_data); + fb_alloc_free_till_mark(); + + if (error != 0) { + if (first_init) { + first_init = false; + cambus_pulse_scl(&fir_bus); + goto FIR_MLX90640_RETRY; + } else { + py_fir_deinit(); + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Failed to init the MLX90640!")); + } + } + + // Switch to FAST speed + cambus_deinit(&fir_bus); + cambus_init(&fir_bus, FIR_I2C_ID, CAMBUS_SPEED_FAST); + fir_width = MLX90640_WIDTH; + fir_height = MLX90640_HEIGHT; + fir_ir_fresh_rate = ir_fresh_rate; + fir_adc_resolution = adc_resolution; + return mp_const_none; + } + #endif + #if (OMV_ENABLE_FIR_MLX90641 == 1) + case FIR_MLX90641: { + // parse refresh rate and ADC resolution + int ir_fresh_rate = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_refresh), 32); + int adc_resolution = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_resolution), 19); + + // sanitize values + ir_fresh_rate = __CLZ(__RBIT((ir_fresh_rate > 64) ? 64 : ((ir_fresh_rate < 1) ? 1 : ir_fresh_rate))) + 1; + adc_resolution = ((adc_resolution > 19) ? 19 : ((adc_resolution < 16) ? 16 : adc_resolution)) - 16; + + fir_mlx_data = xalloc(sizeof(paramsMLX90641)); + + fir_sensor = FIR_MLX90641; + FIR_MLX90641_RETRY: + cambus_init(&fir_bus, FIR_I2C_ID, CAMBUS_SPEED_FULL); // The EEPROM must be read at <= 400KHz. + MLX90641_I2CInit(&fir_bus); + + fb_alloc_mark(); + uint16_t *eeprom = fb_alloc(MLX90641_EEPROM_DATA_SIZE * sizeof(uint16_t), FB_ALLOC_NO_HINT); + int error = MLX90641_DumpEE(MLX90641_ADDR, eeprom); + error |= MLX90641_SetRefreshRate(MLX90641_ADDR, ir_fresh_rate); + error |= MLX90641_SetResolution(MLX90641_ADDR, adc_resolution); + error |= MLX90641_ExtractParameters(eeprom, fir_mlx_data); + fb_alloc_free_till_mark(); + + if (error != 0) { + if (first_init) { + first_init = false; + cambus_pulse_scl(&fir_bus); + goto FIR_MLX90641_RETRY; + } else { + py_fir_deinit(); + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Failed to init the MLX90641!")); + } + } + + // Switch to FAST speed + cambus_deinit(&fir_bus); + cambus_init(&fir_bus, FIR_I2C_ID, CAMBUS_SPEED_FAST); + fir_width = MLX90641_WIDTH; + fir_height = MLX90641_HEIGHT; + fir_ir_fresh_rate = ir_fresh_rate; + fir_adc_resolution = adc_resolution; + return mp_const_none; + } + #endif + #if (OMV_ENABLE_FIR_AMG8833 == 1) + case FIR_AMG8833: { + fir_sensor = FIR_AMG8833; + FIR_AMG8833_RETRY: + cambus_init(&fir_bus, FIR_I2C_ID, CAMBUS_SPEED_STANDARD); + + int error = cambus_write_bytes(&fir_bus, AMG8833_ADDR, + (uint8_t [2]){AMG8833_RESET_REGISTER, AMG8833_INITIAL_RESET_VALUE}, 2, 0); + if (error != 0) { + if (first_init) { + first_init = false; + cambus_pulse_scl(&fir_bus); + goto FIR_AMG8833_RETRY; + } else { + py_fir_deinit(); + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Failed to init the AMG8833!")); + } + } + + fir_width = AMG8833_WIDTH; + fir_height = AMG8833_HEIGHT; + fir_ir_fresh_rate = 10; + fir_adc_resolution = 12; + return mp_const_none; + } + #endif + #if (OMV_ENABLE_FIR_LEPTON == 1) + case FIR_LEPTON: { + fir_sensor = FIR_LEPTON; + FIR_LEPTON_RETRY: + cambus_init(&fir_bus, OMV_FIR_LEPTON_I2C_BUS, OMV_FIR_LEPTON_I2C_BUS_SPEED); + + int error = fir_lepton_init(&fir_bus, &fir_width, &fir_height, &fir_ir_fresh_rate, &fir_adc_resolution); + + if (error != 0) { + if (first_init) { + first_init = false; + cambus_pulse_scl(&fir_bus); + goto FIR_LEPTON_RETRY; + } else { + py_fir_deinit(); + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Failed to init the Lepton!")); + } + } + + return mp_const_none; + } + #endif + default: { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid sensor type!")); + } + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_fir_init_obj, 0, py_fir_init); + +static mp_obj_t py_fir_type() +{ + if (fir_sensor == FIR_NONE) return mp_const_none; + return mp_obj_new_int(fir_sensor); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_fir_type_obj, py_fir_type); + +static mp_obj_t py_fir_width() +{ + if (fir_sensor == FIR_NONE) return mp_const_none; + return mp_obj_new_int(fir_width); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_fir_width_obj, py_fir_width); + +static mp_obj_t py_fir_height() +{ + if (fir_sensor == FIR_NONE) return mp_const_none; + return mp_obj_new_int(fir_height); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_fir_height_obj, py_fir_height); + +static mp_obj_t py_fir_refresh() +{ + #if (OMV_ENABLE_FIR_MLX90621 == 1) + const int mlx_90621_refresh_rates[16] = {512, 512, 512, 512, 512, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 0}; + #endif + #if (OMV_ENABLE_FIR_MLX90640 == 1) || (OMV_ENABLE_FIR_MLX90641 == 1) + const int mlx_90640_1_refresh_rates[8] = {0, 1, 2, 4, 8, 16, 32, 64}; + #endif + switch (fir_sensor) { + case FIR_NONE: + return mp_const_none; + #if (OMV_ENABLE_FIR_MLX90621 == 1) + case FIR_MLX90621: + return mp_obj_new_int(mlx_90621_refresh_rates[fir_ir_fresh_rate]); + #endif + #if (OMV_ENABLE_FIR_MLX90640 == 1) || (OMV_ENABLE_FIR_MLX90640 == 1) + #if (OMV_ENABLE_FIR_MLX90640 == 1) + case FIR_MLX90640: + #endif + #if (OMV_ENABLE_FIR_MLX90641 == 1) + case FIR_MLX90641: + #endif + return mp_obj_new_int(mlx_90640_1_refresh_rates[fir_ir_fresh_rate]); + #endif + #if (OMV_ENABLE_FIR_AMG8833 == 1) + case FIR_AMG8833: + return mp_obj_new_int(fir_ir_fresh_rate); + #endif + #if (OMV_ENABLE_FIR_LEPTON == 1) + case FIR_LEPTON: + return mp_obj_new_int(fir_ir_fresh_rate); + #endif + default: + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_fir_refresh_obj, py_fir_refresh); + +static mp_obj_t py_fir_resolution() +{ + switch (fir_sensor) { + case FIR_NONE: + return mp_const_none; + #if (OMV_ENABLE_FIR_MLX90621 == 1) + case FIR_MLX90621: + return mp_obj_new_int(fir_adc_resolution + 15); + #endif + #if (OMV_ENABLE_FIR_MLX90640 == 1) || (OMV_ENABLE_FIR_MLX90640 == 1) + #if (OMV_ENABLE_FIR_MLX90640 == 1) + case FIR_MLX90640: + #endif + #if (OMV_ENABLE_FIR_MLX90641 == 1) + case FIR_MLX90641: + #endif + return mp_obj_new_int(fir_adc_resolution + 16); + #endif + #if (OMV_ENABLE_FIR_AMG8833 == 1) + case FIR_AMG8833: + return mp_obj_new_int(fir_adc_resolution); + #endif + #if (OMV_ENABLE_FIR_LEPTON == 1) + case FIR_LEPTON: + return mp_obj_new_int(fir_adc_resolution); + #endif + default: + return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_fir_resolution_obj, py_fir_resolution); + +#if (OMV_ENABLE_FIR_LEPTON == 1) +static mp_obj_t py_fir_radiometric() +{ + if (fir_sensor == FIR_LEPTON) { + return fir_lepton_get_radiometry(); + } + + return mp_const_true; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_fir_radiometric_obj, py_fir_radiometric); + +#if defined(OMV_FIR_LEPTON_VSYNC_PRESENT) +static mp_obj_t py_fir_register_vsync_cb(mp_obj_t cb) +{ + if (fir_sensor == FIR_LEPTON) { + fir_lepton_register_vsync_cb(cb); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_fir_register_vsync_cb_obj, py_fir_register_vsync_cb); +#endif + +static mp_obj_t py_fir_register_frame_cb(mp_obj_t cb) +{ + if (fir_sensor == FIR_LEPTON) { + fir_lepton_register_frame_cb(cb); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_fir_register_frame_cb_obj, py_fir_register_frame_cb); + +static mp_obj_t py_fir_get_frame_available() +{ + if (fir_sensor == FIR_LEPTON) { + return fir_lepton_get_frame_available(); + } + + return mp_const_true; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_fir_get_frame_available_obj, py_fir_get_frame_available); + +static mp_obj_t py_fir_trigger_ffc(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + if (fir_sensor == FIR_LEPTON) { + fir_lepton_trigger_ffc(n_args, args, kw_args); + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_fir_trigger_ffc_obj, 0, py_fir_trigger_ffc); +#endif + +mp_obj_t py_fir_read_ta() +{ + switch(fir_sensor) { + case FIR_NONE: { + return mp_const_none; + } + #if (OMV_ENABLE_FIR_MLX90621 == 1) + case FIR_MLX90621: { + fb_alloc_mark(); + uint16_t *data = fb_alloc(MLX90621_FRAME_DATA_SIZE * sizeof(uint16_t), FB_ALLOC_NO_HINT); + PY_ASSERT_TRUE_MSG(MLX90621_GetFrameData(data) >= 0, + "Failed to read the MLX90640 sensor data!"); + mp_obj_t result = mp_obj_new_float(MLX90621_GetTa(data, fir_mlx_data)); + fb_alloc_free_till_mark(); + return result; + } + #endif + #if (OMV_ENABLE_FIR_MLX90640 == 1) + case FIR_MLX90640: { + fb_alloc_mark(); + uint16_t *data = fb_alloc(MLX90640_FRAME_DATA_SIZE * sizeof(uint16_t), FB_ALLOC_NO_HINT); + PY_ASSERT_TRUE_MSG(MLX90640_GetFrameData(MLX90640_ADDR, data) >= 0, + "Failed to read the MLX90640 sensor data!"); + mp_obj_t result = mp_obj_new_float(MLX90640_GetTa(data, fir_mlx_data)); + fb_alloc_free_till_mark(); + return result; + } + #endif + #if (OMV_ENABLE_FIR_MLX90641 == 1) + case FIR_MLX90641: { + fb_alloc_mark(); + uint16_t *data = fb_alloc(MLX90641_FRAME_DATA_SIZE * sizeof(uint16_t), FB_ALLOC_NO_HINT); + PY_ASSERT_TRUE_MSG(MLX90641_GetFrameData(MLX90641_ADDR, data) >= 0, + "Failed to read the MLX90641 sensor data!"); + mp_obj_t result = mp_obj_new_float(MLX90641_GetTa(data, fir_mlx_data)); + fb_alloc_free_till_mark(); + return result; + } + #endif + #if (OMV_ENABLE_FIR_AMG8833 == 1) + case FIR_AMG8833: { + int16_t temp; + int error = 0; + error |= cambus_write_bytes(&fir_bus, AMG8833_ADDR, (uint8_t [1]){AMG8833_THERMISTOR_REGISTER}, 1, CAMBUS_XFER_NO_STOP); + error |= cambus_read_bytes(&fir_bus, AMG8833_ADDR, (uint8_t *) &temp, sizeof(temp), CAMBUS_XFER_NO_FLAGS); + PY_ASSERT_TRUE_MSG((error == 0), "Failed to read the AMG8833 sensor data!"); + return mp_obj_new_float(AMG8833_12_TO_16(temp) * 0.0625f); + } + #endif + #if (OMV_ENABLE_FIR_LEPTON == 1) + case FIR_LEPTON: { + return fir_lepton_read_ta(); + } + #endif + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_fir_read_ta_obj, py_fir_read_ta); + +mp_obj_t py_fir_read_ir(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + bool arg_hmirror = py_helper_keyword_int(n_args, args, 0, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_hmirror), false); + bool arg_vflip = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_vflip), false); + fir_transposed = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_transpose), false); + #if (OMV_ENABLE_FIR_LEPTON == 1) + int arg_timeout = py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_timeout), -1); + #endif + + switch(fir_sensor) { + case FIR_NONE: { + return mp_const_none; + } + #if (OMV_ENABLE_FIR_MLX90621 == 1) + case FIR_MLX90621: { + fb_alloc_mark(); + float Ta, *To = fb_alloc(MLX90621_WIDTH * MLX90621_HEIGHT * sizeof(float), FB_ALLOC_NO_HINT); + fir_MLX90621_get_frame(&Ta, To); + mp_obj_t result = fir_get_ir(MLX90621_WIDTH, MLX90621_HEIGHT, Ta, To, + arg_hmirror ^ true, arg_vflip, fir_transposed, true); + fb_alloc_free_till_mark(); + return result; + } + #endif + #if (OMV_ENABLE_FIR_MLX90640 == 1) + case FIR_MLX90640: { + fb_alloc_mark(); + float Ta, *To = fb_alloc(MLX90640_WIDTH * MLX90640_HEIGHT * sizeof(float), FB_ALLOC_NO_HINT); + fir_MLX90640_get_frame(&Ta, To); + mp_obj_t result = fir_get_ir(MLX90640_WIDTH, MLX90640_HEIGHT, Ta, To, + arg_hmirror ^ true, arg_vflip, fir_transposed, false); + fb_alloc_free_till_mark(); + return result; + } + #endif + #if (OMV_ENABLE_FIR_MLX90641 == 1) + case FIR_MLX90641: { + fb_alloc_mark(); + float Ta, *To = fb_alloc(MLX90641_WIDTH * MLX90641_HEIGHT * sizeof(float), FB_ALLOC_NO_HINT); + fir_MLX90641_get_frame(&Ta, To); + mp_obj_t result = fir_get_ir(MLX90641_WIDTH, MLX90641_HEIGHT, Ta, To, + arg_hmirror ^ true, arg_vflip, fir_transposed, false); + fb_alloc_free_till_mark(); + return result; + } + #endif + #if (OMV_ENABLE_FIR_AMG8833 == 1) + case FIR_AMG8833: { + fb_alloc_mark(); + float Ta, *To = fb_alloc(AMG8833_WIDTH * AMG8833_HEIGHT * sizeof(float), FB_ALLOC_NO_HINT); + fir_AMG8833_get_frame(&Ta, To); + mp_obj_t result = fir_get_ir(AMG8833_WIDTH, AMG8833_HEIGHT, Ta, To, + arg_hmirror ^ true, arg_vflip, fir_transposed, true); + fb_alloc_free_till_mark(); + return result; + } + #endif + #if (OMV_ENABLE_FIR_LEPTON == 1) + case FIR_LEPTON: { + return fir_lepton_read_ir(fir_width, fir_height, arg_hmirror, arg_vflip, fir_transposed, arg_timeout); + } + #endif + } + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_fir_read_ir_obj, 0, py_fir_read_ir); + +mp_obj_t py_fir_draw_ir(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *dst_img = py_helper_arg_to_image_mutable(args[0]); + + image_t src_img; + src_img.pixfmt = PIXFORMAT_GRAYSCALE; + + size_t len; + mp_obj_t *items, *arg_to; + mp_obj_get_array(args[1], &len, &items); + + if (len == 3) { + src_img.w = mp_obj_get_int(items[0]); + src_img.h = mp_obj_get_int(items[1]); + mp_obj_get_array_fixed_n(items[2], src_img.w * src_img.h, &arg_to); + } else if (fir_sensor != FIR_NONE) { + src_img.w = fir_transposed ? fir_height : fir_width; + src_img.h = fir_transposed ? fir_width : fir_height; + // Handle if the user passed an array of the array. + if (len == 1) { + mp_obj_get_array_fixed_n(*items, src_img.w * src_img.h, &arg_to); + } else { + mp_obj_get_array_fixed_n(args[1], src_img.w * src_img.h, &arg_to); + } + } else { + mp_raise_msg(&mp_type_TypeError, MP_ERROR_TEXT("Invalid IR array!")); + } + + int arg_x_off = 0; + int arg_y_off = 0; + uint offset = 2; + if (n_args > 2) { + if (MP_OBJ_IS_TYPE(args[2], &mp_type_tuple) || MP_OBJ_IS_TYPE(args[2], &mp_type_list)) { + mp_obj_t *arg_vec; + mp_obj_get_array_fixed_n(args[2], 2, &arg_vec); + arg_x_off = mp_obj_get_int(arg_vec[0]); + arg_y_off = mp_obj_get_int(arg_vec[1]); + offset = 3; + } else if (n_args > 3) { + arg_x_off = mp_obj_get_int(args[2]); + arg_y_off = mp_obj_get_int(args[3]); + offset = 4; + } else if (n_args > 2) { + mp_raise_msg(&mp_type_TypeError, MP_ERROR_TEXT("Expected x and y offset!")); + } + } + + float arg_x_scale = 1.f; + bool got_x_scale = py_helper_keyword_float_maybe(n_args, args, + offset + 0, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_scale), &arg_x_scale); + + float arg_y_scale = 1.f; + bool got_y_scale = py_helper_keyword_float_maybe(n_args, args, + offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_scale), &arg_y_scale); + + rectangle_t arg_roi; + py_helper_keyword_rectangle_roi(&src_img, n_args, args, offset + 2, kw_args, &arg_roi); + + float tmp_x_scale = dst_img->w / ((float) arg_roi.w); + float tmp_y_scale = dst_img->h / ((float) arg_roi.h); + float tmp_scale = IM_MIN(tmp_x_scale, tmp_y_scale); + + if (n_args == 2) { + arg_x_off = fast_floorf((dst_img->w - (arg_roi.w * tmp_scale)) / 2.f); + arg_y_off = fast_floorf((dst_img->h - (arg_roi.h * tmp_scale)) / 2.f); + } + + if (!got_x_scale) { + arg_x_scale = tmp_scale; + } + + if (!got_y_scale) { + arg_y_scale = tmp_scale; + } + + int arg_rgb_channel = py_helper_keyword_int(n_args, args, offset + 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_rgb_channel), -1); + if ((arg_rgb_channel < -1) || (2 < arg_rgb_channel)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("-1 <= rgb_channel <= 2!")); + } + + int arg_alpha = py_helper_keyword_int(n_args, args, offset + 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_alpha), 128); + if ((arg_alpha < 0) || (256 < arg_alpha)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= alpha <= 256!")); + } + + const uint16_t *color_palette = py_helper_keyword_color_palette(n_args, args, offset + 5, kw_args, rainbow_table); + const uint8_t *alpha_palette = py_helper_keyword_alpha_palette(n_args, args, offset + 6, kw_args, NULL); + + image_hint_t hint = py_helper_keyword_int(n_args, args, offset + 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_hint), 0); + + int arg_x_size; + bool got_x_size = py_helper_keyword_int_maybe(n_args, args, offset + 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_size), &arg_x_size); + + int arg_y_size; + bool got_y_size = py_helper_keyword_int_maybe(n_args, args, offset + 9, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_size), &arg_y_size); + + if (got_x_scale && got_x_size) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Choose either x_scale or x_size not both!")); + } + + if (got_y_scale && got_y_size) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Choose either y_scale or y_size not both!")); + } + + if (got_x_size) { + arg_x_scale = arg_x_size / ((float) arg_roi.w); + } + + if (got_y_size) { + arg_y_scale = arg_y_size / ((float) arg_roi.h); + } + + if ((!got_x_scale) && (!got_x_size) && got_y_size) { + arg_x_scale = arg_y_scale; + } + + if ((!got_y_scale) && (!got_y_size) && got_x_size) { + arg_y_scale = arg_x_scale; + } + + mp_obj_t scale_obj = py_helper_keyword_object(n_args, args, offset + 10, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_scale), NULL); + float min = FLT_MAX, max = -FLT_MAX; + + if (scale_obj) { + mp_obj_t *arg_scale; + mp_obj_get_array_fixed_n(scale_obj, 2, &arg_scale); + min = mp_obj_get_float(arg_scale[0]); + max = mp_obj_get_float(arg_scale[1]); + } else { + for (int i = 0, ii = src_img.w * src_img.h; i < ii; i++) { + float temp = mp_obj_get_float(arg_to[i]); + if (temp < min) { + min = temp; + } + if (temp > max) { + max = temp; + } + } + } + + fb_alloc_mark(); + + src_img.data = fb_alloc(src_img.w * src_img.h * sizeof(uint8_t), FB_ALLOC_NO_HINT); + fir_fill_image_float_obj(&src_img, arg_to, min, max); + + imlib_draw_image(dst_img, &src_img, arg_x_off, arg_y_off, arg_x_scale, arg_y_scale, &arg_roi, + arg_rgb_channel, arg_alpha, color_palette, alpha_palette, hint, NULL, NULL); + + fb_alloc_free_till_mark(); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_fir_draw_ir_obj, 2, py_fir_draw_ir); + +mp_obj_t py_fir_snapshot(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + if (fir_sensor == FIR_NONE) return mp_const_none; + + bool arg_hmirror = py_helper_keyword_int(n_args, args, 0, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_hmirror), false); + bool arg_vflip = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_vflip), false); + bool arg_transpose = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_transpose), false); + + image_t src_img; + src_img.w = arg_transpose ? fir_height : fir_width; + src_img.h = arg_transpose ? fir_width : fir_height; + src_img.pixfmt = PIXFORMAT_GRAYSCALE; + + float arg_x_scale = 1.f; + bool got_x_scale = py_helper_keyword_float_maybe(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_scale), &arg_x_scale); + + float arg_y_scale = 1.f; + bool got_y_scale = py_helper_keyword_float_maybe(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_scale), &arg_y_scale); + + rectangle_t arg_roi; + py_helper_keyword_rectangle_roi(&src_img, n_args, args, 5, kw_args, &arg_roi); + + int arg_rgb_channel = py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_rgb_channel), -1); + if ((arg_rgb_channel < -1) || (2 < arg_rgb_channel)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("-1 <= rgb_channel <= 2!")); + } + + int arg_alpha = py_helper_keyword_int(n_args, args, 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_alpha), 128); + if ((arg_alpha < 0) || (256 < arg_alpha)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= alpha <= 256!")); + } + + const uint16_t *color_palette = py_helper_keyword_color_palette(n_args, args, 8, kw_args, rainbow_table); + const uint8_t *alpha_palette = py_helper_keyword_alpha_palette(n_args, args, 9, kw_args, NULL); + + image_hint_t hint = py_helper_keyword_int(n_args, args, 10, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_hint), 0); + + int arg_x_size; + bool got_x_size = py_helper_keyword_int_maybe(n_args, args, 11, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_size), &arg_x_size); + + int arg_y_size; + bool got_y_size = py_helper_keyword_int_maybe(n_args, args, 12, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_size), &arg_y_size); + + if (got_x_scale && got_x_size) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Choose either x_scale or x_size not both!")); + } + + if (got_y_scale && got_y_size) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Choose either y_scale or y_size not both!")); + } + + if (got_x_size) { + arg_x_scale = arg_x_size / ((float) arg_roi.w); + } + + if (got_y_size) { + arg_y_scale = arg_y_size / ((float) arg_roi.h); + } + + if ((!got_x_scale) && (!got_x_size) && got_y_size) { + arg_x_scale = arg_y_scale; + } + + if ((!got_y_scale) && (!got_y_size) && got_x_size) { + arg_y_scale = arg_x_scale; + } + + mp_obj_t scale_obj = py_helper_keyword_object(n_args, args, 13, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_scale), NULL); + float min, max; + + if (scale_obj) { + mp_obj_t *arg_scale; + mp_obj_get_array_fixed_n(scale_obj, 2, &arg_scale); + min = mp_obj_get_float(arg_scale[0]); + max = mp_obj_get_float(arg_scale[1]); + } + + int arg_pixformat = py_helper_keyword_int(n_args, args, 14, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_pixformat), PIXFORMAT_RGB565); + if ((arg_pixformat != PIXFORMAT_GRAYSCALE) && (arg_pixformat != PIXFORMAT_RGB565)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid pixformat!")); + } + + mp_obj_t copy_to_fb_obj = py_helper_keyword_object(n_args, args, 15, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_copy_to_fb), NULL); + bool copy_to_fb = false; + image_t *arg_other = NULL; + + if (copy_to_fb_obj) { + if (mp_obj_is_integer(copy_to_fb_obj)) { + copy_to_fb = mp_obj_get_int(copy_to_fb_obj); + } else { + arg_other = py_helper_arg_to_image_not_compressed(copy_to_fb_obj); + } + } + + if (copy_to_fb) { + framebuffer_update_jpeg_buffer(); + } + + image_t dst_img; + dst_img.w = fast_floorf(arg_roi.w * arg_x_scale); + dst_img.h = fast_floorf(arg_roi.h * arg_y_scale); + dst_img.pixfmt = (arg_pixformat == PIXFORMAT_RGB565) ? PIXFORMAT_RGB565 : PIXFORMAT_GRAYSCALE; + + size_t size = image_size(&dst_img); + + if (copy_to_fb) { + py_helper_set_to_framebuffer(&dst_img); + } else if (arg_other) { + bool fb = py_helper_is_equal_to_framebuffer(arg_other); + size_t buf_size = fb ? framebuffer_get_buffer_size() : image_size(arg_other); + PY_ASSERT_TRUE_MSG((size <= buf_size), + "The new image won't fit in the target frame buffer!"); + dst_img.data = arg_other->data; + memcpy(arg_other, &dst_img, sizeof(image_t)); + py_helper_update_framebuffer(&dst_img); + } else { + dst_img.data = xalloc(size); + } + + #if (OMV_ENABLE_FIR_LEPTON == 1) + int arg_timeout = py_helper_keyword_int(n_args, args, 16, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_timeout), -1); + #endif + + fb_alloc_mark(); + src_img.data = fb_alloc(src_img.w * src_img.h * sizeof(uint8_t), FB_ALLOC_NO_HINT); + + switch(fir_sensor) { + #if (OMV_ENABLE_FIR_MLX90621 == 1) + case FIR_MLX90621: { + float Ta, *To = fb_alloc(MLX90621_WIDTH * MLX90621_HEIGHT * sizeof(float), FB_ALLOC_NO_HINT); + fir_MLX90621_get_frame(&Ta, To); + + if (!scale_obj) { + fast_get_min_max(To, MLX90621_WIDTH * MLX90621_HEIGHT, &min, &max); + } + + imlib_fill_image_from_float(&src_img, MLX90621_WIDTH, MLX90621_HEIGHT, To, min, max, + arg_hmirror ^ true, arg_vflip, arg_transpose, true); + fb_free(); + break; + } + #endif + #if (OMV_ENABLE_FIR_MLX90640 == 1) + case FIR_MLX90640: { + float Ta, *To = fb_alloc(MLX90640_WIDTH * MLX90640_HEIGHT * sizeof(float), FB_ALLOC_NO_HINT); + fir_MLX90640_get_frame(&Ta, To); + + if (!scale_obj) { + fast_get_min_max(To, MLX90640_WIDTH * MLX90640_HEIGHT, &min, &max); + } + + imlib_fill_image_from_float(&src_img, MLX90640_WIDTH, MLX90640_HEIGHT, To, min, max, + arg_hmirror ^ true, arg_vflip, arg_transpose, false); + fb_free(); + break; + } + #endif + #if (OMV_ENABLE_FIR_MLX90641 == 1) + case FIR_MLX90641: { + float Ta, *To = fb_alloc(MLX90641_WIDTH * MLX90641_HEIGHT * sizeof(float), FB_ALLOC_NO_HINT); + fir_MLX90641_get_frame(&Ta, To); + + if (!scale_obj) { + fast_get_min_max(To, MLX90641_WIDTH * MLX90641_HEIGHT, &min, &max); + } + + imlib_fill_image_from_float(&src_img, MLX90641_WIDTH, MLX90641_HEIGHT, To, min, max, + arg_hmirror ^ true, arg_vflip, arg_transpose, false); + fb_free(); + break; + } + #endif + #if (OMV_ENABLE_FIR_AMG8833 == 1) + case FIR_AMG8833: { + float Ta, *To = fb_alloc(AMG8833_WIDTH * AMG8833_HEIGHT * sizeof(float), FB_ALLOC_NO_HINT); + fir_AMG8833_get_frame(&Ta, To); + + if (!scale_obj) { + fast_get_min_max(To, AMG8833_WIDTH * AMG8833_HEIGHT, &min, &max); + } + + imlib_fill_image_from_float(&src_img, AMG8833_WIDTH, AMG8833_HEIGHT, To, min, max, + arg_hmirror ^ true, arg_vflip, arg_transpose, true); + fb_free(); + break; + } + #endif + #if (OMV_ENABLE_FIR_LEPTON == 1) + case FIR_LEPTON: { + fir_lepton_fill_image(&src_img, fir_width, fir_height, !scale_obj, min, max, + arg_hmirror, arg_vflip, arg_transpose, arg_timeout); + break; + } + #endif + default: { + break; + } + } + + imlib_draw_image(&dst_img, &src_img, 0, 0, arg_x_scale, arg_y_scale, + &arg_roi, arg_rgb_channel, arg_alpha, color_palette, alpha_palette, + (hint & (~IMAGE_HINT_CENTER)) | IMAGE_HINT_BLACK_BACKGROUND, NULL, NULL); + + fb_alloc_free_till_mark(); + + return py_image_from_struct(&dst_img); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_fir_snapshot_obj, 0, py_fir_snapshot); + +STATIC const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_fir) }, + { MP_ROM_QSTR(MP_QSTR_FIR_NONE), MP_ROM_INT(FIR_NONE) }, +#if (OMV_ENABLE_FIR_MLX90621 == 1) + { MP_ROM_QSTR(MP_QSTR_FIR_SHIELD), MP_ROM_INT(FIR_MLX90621) }, + { MP_ROM_QSTR(MP_QSTR_FIR_MLX90621), MP_ROM_INT(FIR_MLX90621) }, +#endif +#if (OMV_ENABLE_FIR_MLX90640 == 1) + { MP_ROM_QSTR(MP_QSTR_FIR_MLX90640), MP_ROM_INT(FIR_MLX90640) }, +#endif +#if (OMV_ENABLE_FIR_MLX90641 == 1) + { MP_ROM_QSTR(MP_QSTR_FIR_MLX90641), MP_ROM_INT(FIR_MLX90641) }, +#endif +#if (OMV_ENABLE_FIR_AMG8833 == 1) + { MP_ROM_QSTR(MP_QSTR_FIR_AMG8833), MP_ROM_INT(FIR_AMG8833) }, +#endif +#if (OMV_ENABLE_FIR_LEPTON == 1) + { MP_ROM_QSTR(MP_QSTR_FIR_LEPTON), MP_ROM_INT(FIR_LEPTON) }, +#endif + { MP_ROM_QSTR(MP_QSTR_PALETTE_RAINBOW), MP_ROM_INT(COLOR_PALETTE_RAINBOW) }, + { MP_ROM_QSTR(MP_QSTR_PALETTE_IRONBOW), MP_ROM_INT(COLOR_PALETTE_IRONBOW) }, + { MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT(PIXFORMAT_GRAYSCALE) }, + { MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT(PIXFORMAT_RGB565) }, + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&py_fir_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&py_fir_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_type), MP_ROM_PTR(&py_fir_type_obj) }, + { MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_fir_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_fir_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_refresh), MP_ROM_PTR(&py_fir_refresh_obj) }, + { MP_ROM_QSTR(MP_QSTR_resolution), MP_ROM_PTR(&py_fir_resolution_obj) }, +#if (OMV_ENABLE_FIR_LEPTON == 1) + { MP_ROM_QSTR(MP_QSTR_radiometric), MP_ROM_PTR(&py_fir_radiometric_obj) }, +#if defined(OMV_FIR_LEPTON_VSYNC_PRESENT) + { MP_ROM_QSTR(MP_QSTR_register_vsync_cb), MP_ROM_PTR(&py_fir_register_vsync_cb_obj) }, +#else + { MP_ROM_QSTR(MP_QSTR_register_vsync_cb), MP_ROM_PTR(&py_func_unavailable_obj) }, +#endif + { MP_ROM_QSTR(MP_QSTR_register_frame_cb), MP_ROM_PTR(&py_fir_register_frame_cb_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_frame_available), MP_ROM_PTR(&py_fir_get_frame_available_obj) }, + { MP_ROM_QSTR(MP_QSTR_trigger_ffc), MP_ROM_PTR(&py_fir_trigger_ffc_obj) }, +#else + { MP_ROM_QSTR(MP_QSTR_radiometric), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_register_vsync_cb), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_register_frame_cb), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_frame_available), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_trigger_ffc), MP_ROM_PTR(&py_func_unavailable_obj) }, +#endif + { MP_ROM_QSTR(MP_QSTR_read_ta), MP_ROM_PTR(&py_fir_read_ta_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_ir), MP_ROM_PTR(&py_fir_read_ir_obj) }, + { MP_ROM_QSTR(MP_QSTR_draw_ir), MP_ROM_PTR(&py_fir_draw_ir_obj) }, + { MP_ROM_QSTR(MP_QSTR_snapshot), MP_ROM_PTR(&py_fir_snapshot_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t fir_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t) &globals_dict, +}; + +void py_fir_init0() +{ + py_fir_deinit(); +} + +MP_REGISTER_MODULE(MP_QSTR_fir, fir_module, 1); diff --git a/github_source/minicv2/reference/py_fir.h b/github_source/minicv2/reference/py_fir.h new file mode 100644 index 0000000..3f5a12b --- /dev/null +++ b/github_source/minicv2/reference/py_fir.h @@ -0,0 +1,14 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * FIR Python module. + */ +#ifndef __PY_FIR_H__ +#define __PY_FIR_H__ +void py_fir_init0(); +#endif // __PY_FIR_H__ diff --git a/github_source/minicv2/reference/py_gif.c b/github_source/minicv2/reference/py_gif.c new file mode 100644 index 0000000..f443687 --- /dev/null +++ b/github_source/minicv2/reference/py_gif.c @@ -0,0 +1,159 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * GIF Python module. + */ +#include "py/mphal.h" +#include "py/runtime.h" + +#include "ff_wrapper.h" +#include "framebuffer.h" +#include "imlib.h" +#include "py_assert.h" +#include "py_helper.h" +#include "py_image.h" + +static const mp_obj_type_t py_gif_type; // forward declare +// Gif class +typedef struct py_gif_obj { + mp_obj_base_t base; + int width; + int height; + bool color; + bool loop; + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + FIL fp; + #endif +} py_gif_obj_t; + +static mp_obj_t py_gif_open(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + py_gif_obj_t *gif = m_new_obj(py_gif_obj_t); + gif->width = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_width), framebuffer_get_width()); + gif->height = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_height), framebuffer_get_height()); + gif->color = py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color), framebuffer_get_depth()>=2); + gif->loop = py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_loop), true); + gif->base.type = &py_gif_type; + + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + file_write_open(&gif->fp, mp_obj_str_get_str(args[0])); + gif_open(&gif->fp, gif->width, gif->height, gif->color, gif->loop); + #else + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image I/O is not supported")); + #endif + return gif; +} + +static mp_obj_t py_gif_width(mp_obj_t gif_obj) +{ + py_gif_obj_t *arg_gif = gif_obj; + return mp_obj_new_int(arg_gif->width); +} + +static mp_obj_t py_gif_height(mp_obj_t gif_obj) +{ + py_gif_obj_t *arg_gif = gif_obj; + return mp_obj_new_int(arg_gif->height); +} + +static mp_obj_t py_gif_format(mp_obj_t gif_obj) +{ + py_gif_obj_t *arg_gif = gif_obj; + return mp_obj_new_int(arg_gif->color ? PIXFORMAT_RGB565 : PIXFORMAT_GRAYSCALE); +} + +static mp_obj_t py_gif_size(mp_obj_t gif_obj) +{ + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + py_gif_obj_t *arg_gif = gif_obj; + return mp_obj_new_int(file_size_w_buf(&arg_gif->fp)); + #else + return mp_obj_new_int(0); + #endif +} + +static mp_obj_t py_gif_loop(mp_obj_t gif_obj) +{ + py_gif_obj_t *arg_gif = gif_obj; + return mp_obj_new_int(arg_gif->loop); +} + +static mp_obj_t py_gif_add_frame(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + py_gif_obj_t *arg_gif = args[0]; + image_t *arg_img = py_image_cobj(args[1]); + PY_ASSERT_FALSE_MSG(IM_IS_JPEG(arg_img), "Operation not supported on JPEG images."); + PY_ASSERT_FALSE_MSG(IM_IS_BINARY(arg_img), "Operation not supported on binary images."); + PY_ASSERT_FALSE_MSG((arg_gif->width != arg_img->w) || + (arg_gif->height != arg_img->h), "Unexpected image geometry!"); + + int delay = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_delay), 10); + + gif_add_frame(&arg_gif->fp, arg_img, delay); + #else + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image I/O is not supported")); + #endif + return mp_const_none; +} + +static mp_obj_t py_gif_close(mp_obj_t gif_obj) +{ + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + py_gif_obj_t *arg_gif = gif_obj; + gif_close(&arg_gif->fp); + #endif + return mp_const_none; +} + +static void py_gif_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_gif_obj_t *self = self_in; + mp_printf(print, "", self->width, self->height, self->color, self->loop); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_width_obj, py_gif_width); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_height_obj, py_gif_height); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_format_obj, py_gif_format); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_size_obj, py_gif_size); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_loop_obj, py_gif_loop); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_gif_add_frame_obj, 2, py_gif_add_frame); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_gif_close_obj, py_gif_close); +static const mp_map_elem_t locals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR_width), (mp_obj_t)&py_gif_width_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_height), (mp_obj_t)&py_gif_height_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_format), (mp_obj_t)&py_gif_format_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_size), (mp_obj_t)&py_gif_size_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_loop), (mp_obj_t)&py_gif_loop_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_add_frame), (mp_obj_t)&py_gif_add_frame_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_close), (mp_obj_t)&py_gif_close_obj }, + { NULL, NULL }, +}; +STATIC MP_DEFINE_CONST_DICT(locals_dict, locals_dict_table); + +static const mp_obj_type_t py_gif_type = { + { &mp_type_type }, + .name = MP_QSTR_Gif, + .print = py_gif_print, + .locals_dict = (mp_obj_t)&locals_dict, +}; + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_gif_open_obj, 1, py_gif_open); +static const mp_map_elem_t globals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_gif) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_Gif), (mp_obj_t)&py_gif_open_obj }, + { NULL, NULL }, +}; +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t gif_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t)&globals_dict, +}; + +MP_REGISTER_MODULE(MP_QSTR_gif, gif_module, 1); diff --git a/github_source/minicv2/reference/py_helper.c b/github_source/minicv2/reference/py_helper.c new file mode 100644 index 0000000..f86805b --- /dev/null +++ b/github_source/minicv2/reference/py_helper.c @@ -0,0 +1,495 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Python helper functions. + */ +#include "py/obj.h" +#include "py/runtime.h" +#include "framebuffer.h" +#include "sensor.h" +#include "py_helper.h" +#include "py_assert.h" + +extern void *py_image_cobj(mp_obj_t img_obj); + +mp_obj_t py_func_unavailable(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + PY_ASSERT_TRUE_MSG(false, "This function is unavailable on your OpenMV Cam."); + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(py_func_unavailable_obj, 0, py_func_unavailable); + +image_t *py_helper_arg_to_image_mutable(const mp_obj_t arg) +{ + image_t *arg_img = py_image_cobj(arg); + PY_ASSERT_TRUE_MSG(arg_img->is_mutable, "Image is not mutable!"); + return arg_img; +} + +image_t *py_helper_arg_to_image_not_compressed(const mp_obj_t arg) +{ + image_t *arg_img = py_image_cobj(arg); + PY_ASSERT_FALSE_MSG(arg_img->is_compressed, "Image is compressed!"); + return arg_img; +} + +image_t *py_helper_arg_to_image_grayscale(const mp_obj_t arg) +{ + image_t *arg_img = py_image_cobj(arg); + PY_ASSERT_TRUE_MSG(arg_img->pixfmt == PIXFORMAT_GRAYSCALE, "Image is not grayscale!"); + return arg_img; +} + +image_t *py_helper_keyword_to_image_mutable(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, image_t *default_val) +{ + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); + + if (kw_arg) { + default_val = py_helper_arg_to_image_mutable(kw_arg->value); + } else if (n_args > arg_index) { + default_val = py_helper_arg_to_image_mutable(args[arg_index]); + } + + return default_val; +} + +image_t *py_helper_keyword_to_image_mutable_mask(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args) +{ + return py_helper_keyword_to_image_mutable(n_args, args, arg_index, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mask), NULL); +} + +image_t *py_helper_keyword_to_image_mutable_color_palette(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args) +{ + return py_helper_keyword_to_image_mutable(n_args, args, arg_index, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color_palette), NULL); +} + +image_t *py_helper_keyword_to_image_mutable_alpha_palette(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args) +{ + return py_helper_keyword_to_image_mutable(n_args, args, arg_index, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_alpha_palette), NULL); +} + +void py_helper_keyword_rectangle(image_t *img, uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, rectangle_t *r) +{ + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); + + if (kw_arg) { + mp_obj_t *arg_rectangle; + mp_obj_get_array_fixed_n(kw_arg->value, 4, &arg_rectangle); + r->x = mp_obj_get_int(arg_rectangle[0]); + r->y = mp_obj_get_int(arg_rectangle[1]); + r->w = mp_obj_get_int(arg_rectangle[2]); + r->h = mp_obj_get_int(arg_rectangle[3]); + } else if (n_args > arg_index) { + mp_obj_t *arg_rectangle; + mp_obj_get_array_fixed_n(args[arg_index], 4, &arg_rectangle); + r->x = mp_obj_get_int(arg_rectangle[0]); + r->y = mp_obj_get_int(arg_rectangle[1]); + r->w = mp_obj_get_int(arg_rectangle[2]); + r->h = mp_obj_get_int(arg_rectangle[3]); + } else { + r->x = 0; + r->y = 0; + r->w = img->w; + r->h = img->h; + } + + PY_ASSERT_TRUE_MSG((r->w >= 1) && (r->h >= 1), "Invalid ROI dimensions!"); + rectangle_t temp; + temp.x = 0; + temp.y = 0; + temp.w = img->w; + temp.h = img->h; + + PY_ASSERT_TRUE_MSG(rectangle_overlap(r, &temp), "ROI does not overlap on the image!"); + rectangle_intersected(r, &temp); +} + +void py_helper_keyword_rectangle_roi(image_t *img, uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, rectangle_t *r) +{ + py_helper_keyword_rectangle(img, n_args, args, arg_index, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_roi), r); +} + +int py_helper_keyword_int(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, int default_val) +{ + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); + + if (kw_arg) { + default_val = mp_obj_get_int(kw_arg->value); + } else if (n_args > arg_index) { + default_val = mp_obj_get_int(args[arg_index]); + } + + return default_val; +} + +bool py_helper_keyword_int_maybe(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, int* value) +{ + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); + + if (kw_arg) { + return mp_obj_get_int_maybe(kw_arg->value, value); + } else if (n_args > arg_index) { + return mp_obj_get_int_maybe(args[arg_index], value); + } + + return false; +} + +float py_helper_keyword_float(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, float default_val) +{ + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); + + if (kw_arg) { + default_val = mp_obj_get_float(kw_arg->value); + } else if (n_args > arg_index) { + default_val = mp_obj_get_float(args[arg_index]); + } + + return default_val; +} + +bool py_helper_keyword_float_maybe(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, float *value) +{ + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); + + if (kw_arg) { + return mp_obj_get_float_maybe(kw_arg->value, value); + } else if (n_args > arg_index) { + return mp_obj_get_float_maybe(args[arg_index], value); + } + + return false; +} + +void py_helper_keyword_int_array(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, int *x, int size) +{ + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); + + if (kw_arg) { + mp_obj_t *arg_array; + mp_obj_get_array_fixed_n(kw_arg->value, size, &arg_array); + for (int i = 0; i < size; i++) x[i] = mp_obj_get_int(arg_array[i]); + } else if (n_args > arg_index) { + mp_obj_t *arg_array; + mp_obj_get_array_fixed_n(args[arg_index], size, &arg_array); + for (int i = 0; i < size; i++) x[i] = mp_obj_get_int(arg_array[i]); + } +} + +void py_helper_keyword_float_array(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, float *x, int size) +{ + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); + + if (kw_arg) { + mp_obj_t *arg_array; + mp_obj_get_array_fixed_n(kw_arg->value, size, &arg_array); + for (int i = 0; i < size; i++) x[i] = mp_obj_get_float(arg_array[i]); + } else if (n_args > arg_index) { + mp_obj_t *arg_array; + mp_obj_get_array_fixed_n(args[arg_index], size, &arg_array); + for (int i = 0; i < size; i++) x[i] = mp_obj_get_float(arg_array[i]); + } +} + +float *py_helper_keyword_corner_array(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw) +{ + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); + + if (kw_arg) { + mp_obj_t *arg_array; + mp_obj_get_array_fixed_n(kw_arg->value, 4, &arg_array); + float *corners = xalloc(sizeof(float) * 8); + for (int i = 0; i < 4; i++) { + mp_obj_t *arg_point; + mp_obj_get_array_fixed_n(arg_array[i], 2, &arg_point); + corners[(i*2)+0] = mp_obj_get_float(arg_point[0]); + corners[(i*2)+1] = mp_obj_get_float(arg_point[1]); + } + return corners; + } else if (n_args > arg_index) { + mp_obj_t *arg_array; + mp_obj_get_array_fixed_n(args[arg_index], 4, &arg_array); + float *corners = xalloc(sizeof(float) * 8); + for (int i = 0; i < 4; i++) { + mp_obj_t *arg_point; + mp_obj_get_array_fixed_n(arg_array[i], 2, &arg_point); + corners[(i*2)+0] = mp_obj_get_float(arg_point[0]); + corners[(i*2)+1] = mp_obj_get_float(arg_point[1]); + } + return corners; + } + + return NULL; +} + +mp_obj_t *py_helper_keyword_iterable(uint n_args, const mp_obj_t *args, + uint arg_index, mp_map_t *kw_args, mp_obj_t kw, size_t *len) +{ + mp_obj_t itr = NULL; + mp_obj_t *items = NULL; + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); + + if (kw_arg) { + itr = kw_arg->value; + } else if (n_args > arg_index) { + itr = args[arg_index]; + } + + if (itr && (MP_OBJ_IS_TYPE(itr, &mp_type_tuple) || + MP_OBJ_IS_TYPE(itr, &mp_type_list))) { + mp_obj_get_array(itr, len, &items); + } + return items; +} + +uint py_helper_consume_array(uint n_args, const mp_obj_t *args, uint arg_index, size_t len, const mp_obj_t **items) +{ + if (MP_OBJ_IS_TYPE(args[arg_index], &mp_type_tuple) || MP_OBJ_IS_TYPE(args[arg_index], &mp_type_list)) { + mp_obj_get_array_fixed_n(args[arg_index], len, (mp_obj_t **) items); + return arg_index + 1; + } else { + PY_ASSERT_TRUE_MSG((n_args - arg_index) >= len, "Not enough positional arguments!"); + *items = args + arg_index; + return arg_index + len; + } +} + +int py_helper_keyword_color(image_t *img, uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, int default_val) +{ + mp_map_elem_t *kw_arg = kw_args ? mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color), MP_MAP_LOOKUP) : NULL; + + if (kw_arg) { + if (mp_obj_is_integer(kw_arg->value)) { + default_val = mp_obj_get_int(kw_arg->value); + } else { + mp_obj_t *arg_color; + mp_obj_get_array_fixed_n(kw_arg->value, 3, &arg_color); + default_val = COLOR_R8_G8_B8_TO_RGB565(IM_MAX(IM_MIN(mp_obj_get_int(arg_color[0]), COLOR_R8_MAX), COLOR_R8_MIN), + IM_MAX(IM_MIN(mp_obj_get_int(arg_color[1]), COLOR_G8_MAX), COLOR_G8_MIN), + IM_MAX(IM_MIN(mp_obj_get_int(arg_color[2]), COLOR_B8_MAX), COLOR_B8_MIN)); + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + default_val = COLOR_RGB565_TO_BINARY(default_val); + break; + } + case PIXFORMAT_GRAYSCALE: { + default_val = COLOR_RGB565_TO_GRAYSCALE(default_val); + break; + } + default: { + break; + } + } + } + } else if (n_args > arg_index) { + if (mp_obj_is_integer(args[arg_index])) { + default_val = mp_obj_get_int(args[arg_index]); + } else { + mp_obj_t *arg_color; + mp_obj_get_array_fixed_n(args[arg_index], 3, &arg_color); + default_val = COLOR_R8_G8_B8_TO_RGB565(IM_MAX(IM_MIN(mp_obj_get_int(arg_color[0]), COLOR_R8_MAX), COLOR_R8_MIN), + IM_MAX(IM_MIN(mp_obj_get_int(arg_color[1]), COLOR_G8_MAX), COLOR_G8_MIN), + IM_MAX(IM_MIN(mp_obj_get_int(arg_color[2]), COLOR_B8_MAX), COLOR_B8_MIN)); + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + default_val = COLOR_RGB565_TO_BINARY(default_val); + break; + } + case PIXFORMAT_GRAYSCALE: { + default_val = COLOR_RGB565_TO_GRAYSCALE(default_val); + break; + } + default: { + break; + } + } + } + } + + return default_val; +} + +void py_helper_arg_to_thresholds(const mp_obj_t arg, list_t *thresholds) +{ + mp_uint_t arg_thresholds_len; + mp_obj_t *arg_thresholds; + mp_obj_get_array(arg, &arg_thresholds_len, &arg_thresholds); + if (!arg_thresholds_len) return; + for(mp_uint_t i = 0; i < arg_thresholds_len; i++) { + mp_uint_t arg_threshold_len; + mp_obj_t *arg_threshold; + mp_obj_get_array(arg_thresholds[i], &arg_threshold_len, &arg_threshold); + if (arg_threshold_len) { + color_thresholds_list_lnk_data_t lnk_data; + lnk_data.LMin = (arg_threshold_len > 0) ? IM_MAX(IM_MIN(mp_obj_get_int(arg_threshold[0]), + IM_MAX(COLOR_L_MAX, COLOR_GRAYSCALE_MAX)), IM_MIN(COLOR_L_MIN, COLOR_GRAYSCALE_MIN)) : + IM_MIN(COLOR_L_MIN, COLOR_GRAYSCALE_MIN); + lnk_data.LMax = (arg_threshold_len > 1) ? IM_MAX(IM_MIN(mp_obj_get_int(arg_threshold[1]), + IM_MAX(COLOR_L_MAX, COLOR_GRAYSCALE_MAX)), IM_MIN(COLOR_L_MIN, COLOR_GRAYSCALE_MIN)) : + IM_MAX(COLOR_L_MAX, COLOR_GRAYSCALE_MAX); + lnk_data.AMin = (arg_threshold_len > 2) ? IM_MAX(IM_MIN(mp_obj_get_int(arg_threshold[2]), COLOR_A_MAX), COLOR_A_MIN) : COLOR_A_MIN; + lnk_data.AMax = (arg_threshold_len > 3) ? IM_MAX(IM_MIN(mp_obj_get_int(arg_threshold[3]), COLOR_A_MAX), COLOR_A_MIN) : COLOR_A_MAX; + lnk_data.BMin = (arg_threshold_len > 4) ? IM_MAX(IM_MIN(mp_obj_get_int(arg_threshold[4]), COLOR_B_MAX), COLOR_B_MIN) : COLOR_B_MIN; + lnk_data.BMax = (arg_threshold_len > 5) ? IM_MAX(IM_MIN(mp_obj_get_int(arg_threshold[5]), COLOR_B_MAX), COLOR_B_MIN) : COLOR_B_MAX; + color_thresholds_list_lnk_data_t lnk_data_tmp; + memcpy(&lnk_data_tmp, &lnk_data, sizeof(color_thresholds_list_lnk_data_t)); + lnk_data.LMin = IM_MIN(lnk_data_tmp.LMin, lnk_data_tmp.LMax); + lnk_data.LMax = IM_MAX(lnk_data_tmp.LMin, lnk_data_tmp.LMax); + lnk_data.AMin = IM_MIN(lnk_data_tmp.AMin, lnk_data_tmp.AMax); + lnk_data.AMax = IM_MAX(lnk_data_tmp.AMin, lnk_data_tmp.AMax); + lnk_data.BMin = IM_MIN(lnk_data_tmp.BMin, lnk_data_tmp.BMax); + lnk_data.BMax = IM_MAX(lnk_data_tmp.BMin, lnk_data_tmp.BMax); + list_push_back(thresholds, &lnk_data); + } + } +} + +void py_helper_keyword_thresholds(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, list_t *thresholds) +{ + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thresholds), MP_MAP_LOOKUP); + + if (kw_arg) { + py_helper_arg_to_thresholds(kw_arg->value, thresholds); + } else if (n_args > arg_index) { + py_helper_arg_to_thresholds(args[arg_index], thresholds); + } +} + +int py_helper_arg_to_ksize(const mp_obj_t arg) +{ + int ksize = mp_obj_get_int(arg); + PY_ASSERT_TRUE_MSG(ksize >= 0, "KernelSize must be >= 0!"); + return ksize; +} + +int py_helper_ksize_to_n(int ksize) +{ + return ((ksize * 2) + 1) * ((ksize * 2) + 1); +} + +mp_obj_t py_helper_keyword_object(uint n_args, const mp_obj_t *args, + uint arg_index, mp_map_t *kw_args, mp_obj_t kw, mp_obj_t default_val) +{ + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, kw, MP_MAP_LOOKUP); + + if (kw_arg) { + return kw_arg->value; + } else if (n_args > arg_index) { + return args[arg_index]; + } else { + return default_val; + } +} + +const uint16_t *py_helper_keyword_color_palette(uint n_args, const mp_obj_t *args, + uint arg_index, mp_map_t *kw_args, const uint16_t *default_color_palette) +{ + int palette; + + mp_map_elem_t *kw_arg = + mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color_palette), MP_MAP_LOOKUP); + + if (kw_arg && (kw_arg->value == mp_const_none)) { + default_color_palette = NULL; + } else if ((n_args > arg_index) && (args[arg_index] == mp_const_none)) { + default_color_palette = NULL; + } else if (py_helper_keyword_int_maybe(n_args, args, arg_index, kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_color_palette), &palette)) { + if (palette == COLOR_PALETTE_RAINBOW) { + default_color_palette = rainbow_table; + } else if (palette == COLOR_PALETTE_IRONBOW) { + default_color_palette = ironbow_table; + } else { + mp_raise_msg(&mp_type_ValueError, + MP_ERROR_TEXT("Invalid pre-defined color palette!")); + } + } else { + image_t *arg_color_palette = + py_helper_keyword_to_image_mutable_color_palette(n_args, args, arg_index, kw_args); + + if (arg_color_palette) { + if (arg_color_palette->pixfmt != PIXFORMAT_RGB565) { + mp_raise_msg(&mp_type_ValueError, + MP_ERROR_TEXT("Color palette must be RGB565!")); + } + + if ((arg_color_palette->w * arg_color_palette->h) != 256) { + mp_raise_msg(&mp_type_ValueError, + MP_ERROR_TEXT("Color palette must be 256 pixels!")); + } + + default_color_palette = (uint16_t *) arg_color_palette->data; + } + } + + return default_color_palette; +} + +const uint8_t *py_helper_keyword_alpha_palette(uint n_args, const mp_obj_t *args, + uint arg_index, mp_map_t *kw_args, const uint8_t *default_alpha_palette) +{ + image_t *arg_alpha_palette = + py_helper_keyword_to_image_mutable_alpha_palette(n_args, args, 9, kw_args); + + if (arg_alpha_palette) { + if (arg_alpha_palette->pixfmt != PIXFORMAT_GRAYSCALE) { + mp_raise_msg(&mp_type_ValueError, + MP_ERROR_TEXT("Alpha palette must be GRAYSCALE!")); + } + + if ((arg_alpha_palette->w * arg_alpha_palette->h) != 256) { + mp_raise_msg(&mp_type_ValueError, + MP_ERROR_TEXT("Alpha palette must be 256 pixels!")); + } + + default_alpha_palette = (uint8_t *) arg_alpha_palette->data; + } + + return default_alpha_palette; +} + +bool py_helper_is_equal_to_framebuffer(image_t *img) +{ + return framebuffer_get_buffer(framebuffer->head)->data == img->data; +} + +void py_helper_update_framebuffer(image_t *img) +{ + if (py_helper_is_equal_to_framebuffer(img)) { + framebuffer_init_from_image(img); + } +} + +void py_helper_set_to_framebuffer(image_t *img) +{ + #if MICROPY_PY_SENSOR + sensor_set_framebuffers(1); + #else + framebuffer_set_buffers(1); + #endif + + PY_ASSERT_TRUE_MSG((image_size(img) <= framebuffer_get_buffer_size()), + "The image doesn't fit in the frame buffer!"); + framebuffer_init_from_image(img); + img->data = framebuffer_get_buffer(framebuffer->head)->data; +} diff --git a/github_source/minicv2/reference/py_helper.h b/github_source/minicv2/reference/py_helper.h new file mode 100644 index 0000000..134737b --- /dev/null +++ b/github_source/minicv2/reference/py_helper.h @@ -0,0 +1,63 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Python helper functions. + */ +#ifndef __PY_HELPER_H__ +#define __PY_HELPER_H__ +#include "imlib.h" +extern const mp_obj_fun_builtin_var_t py_func_unavailable_obj; +image_t *py_helper_arg_to_image_mutable(const mp_obj_t arg); +image_t *py_helper_arg_to_image_not_compressed(const mp_obj_t arg); +image_t *py_helper_arg_to_image_grayscale(const mp_obj_t arg); +image_t *py_helper_keyword_to_image_mutable(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, image_t *default_val); +image_t *py_helper_keyword_to_image_mutable_mask(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args); +image_t *py_helper_keyword_to_image_mutable_color_palette(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args); +image_t *py_helper_keyword_to_image_mutable_alpha_palette(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args); +void py_helper_keyword_rectangle(image_t *img, uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, rectangle_t *r); +void py_helper_keyword_rectangle_roi(image_t *img, uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, rectangle_t *r); +int py_helper_keyword_int(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, int default_val); +bool py_helper_keyword_int_maybe(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, int* value); +float py_helper_keyword_float(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, float default_val); +bool py_helper_keyword_float_maybe(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, float* value); +void py_helper_keyword_int_array(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, int *x, int size); +void py_helper_keyword_float_array(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, float *x, int size); +float *py_helper_keyword_corner_array(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw); +mp_obj_t *py_helper_keyword_iterable(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, mp_obj_t kw, size_t *len); +uint py_helper_consume_array(uint n_args, const mp_obj_t *args, uint arg_index, size_t len, const mp_obj_t **items); +int py_helper_keyword_color(image_t *img, uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, int default_val); +void py_helper_arg_to_thresholds(const mp_obj_t arg, list_t *thresholds); +void py_helper_keyword_thresholds(uint n_args, const mp_obj_t *args, uint arg_index, + mp_map_t *kw_args, list_t *thresholds); +int py_helper_arg_to_ksize(const mp_obj_t arg); +int py_helper_ksize_to_n(int ksize); +mp_obj_t py_helper_keyword_object(uint n_args, const mp_obj_t *args, + uint arg_index, mp_map_t *kw_args, mp_obj_t kw, mp_obj_t default_val); +const uint16_t *py_helper_keyword_color_palette(uint n_args, const mp_obj_t *args, + uint arg_index, mp_map_t *kw_args, const uint16_t *default_color_palette); +const uint8_t *py_helper_keyword_alpha_palette(uint n_args, const mp_obj_t *args, + uint arg_index, mp_map_t *kw_args, const uint8_t *default_alpha_palette); +bool py_helper_is_equal_to_framebuffer(image_t *img); +void py_helper_update_framebuffer(image_t *img); +void py_helper_set_to_framebuffer(image_t *img); +#endif // __PY_HELPER__ diff --git a/github_source/minicv2/reference/py_image.c b/github_source/minicv2/reference/py_image.c new file mode 100644 index 0000000..a32c191 --- /dev/null +++ b/github_source/minicv2/reference/py_image.c @@ -0,0 +1,7069 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Image Python module. + */ +#include +#include +#include +#include +#include "py/nlr.h" +#include "py/obj.h" +#include "py/objlist.h" +#include "py/objstr.h" +#include "py/objtuple.h" +#include "py/objtype.h" +#include "py/runtime.h" +#include "py/mphal.h" + +#include "imlib.h" +#include "array.h" +#include "ff_wrapper.h" +#include "xalloc.h" +#include "fb_alloc.h" +#include "framebuffer.h" +#include "py_assert.h" +#include "py_helper.h" +#include "py_image.h" +#include "omv_boardconfig.h" +#if defined(IMLIB_ENABLE_IMAGE_IO) +#include "py_imageio.h" +#endif + +static const mp_obj_type_t py_cascade_type; +static const mp_obj_type_t py_image_type; + +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) +extern const char *ffs_strerror(FRESULT res); +#endif + +// Haar Cascade /////////////////////////////////////////////////////////////// + +typedef struct _py_cascade_obj_t { + mp_obj_base_t base; + struct cascade _cobj; +} py_cascade_obj_t; + +void *py_cascade_cobj(mp_obj_t cascade) +{ + PY_ASSERT_TYPE(cascade, &py_cascade_type); + return &((py_cascade_obj_t *)cascade)->_cobj; +} + +static void py_cascade_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_cascade_obj_t *self = self_in; + mp_printf(print, "{\"width\":%d, \"height\":%d, \"n_stages\":%d, \"n_features\":%d, \"n_rectangles\":%d}", + self->_cobj.window.w, self->_cobj.window.h, self->_cobj.n_stages, + self->_cobj.n_features, self->_cobj.n_rectangles); +} + +static const mp_obj_type_t py_cascade_type = { + { &mp_type_type }, + .name = MP_QSTR_Cascade, + .print = py_cascade_print, +}; + +// Keypoints object /////////////////////////////////////////////////////////// + +#ifdef IMLIB_ENABLE_FIND_KEYPOINTS + +typedef struct _py_kp_obj_t { + mp_obj_base_t base; + array_t *kpts; + int threshold; + bool normalized; +} py_kp_obj_t; + +static void py_kp_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_kp_obj_t *self = self_in; + mp_printf(print, "{\"size\":%d, \"threshold\":%d, \"normalized\":%d}", array_length(self->kpts), self->threshold, self->normalized); +} + +mp_obj_t py_kp_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + py_kp_obj_t *self = MP_OBJ_TO_PTR(self_in); + switch (op) { + case MP_UNARY_OP_LEN: + return MP_OBJ_NEW_SMALL_INT(array_length(self->kpts)); + + default: + return MP_OBJ_NULL; // op not supported + } +} + +static mp_obj_t py_kp_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_kp_obj_t *self = self_in; + int size = array_length(self->kpts); + int i = mp_get_index(self->base.type, size, index, false); + kp_t *kp = array_at(self->kpts, i); + return mp_obj_new_tuple(5, (mp_obj_t []) {mp_obj_new_int(kp->x), + mp_obj_new_int(kp->y), + mp_obj_new_int(kp->score), + mp_obj_new_int(kp->octave), + mp_obj_new_int(kp->angle)}); + } + + return MP_OBJ_NULL; // op not supported +} + +static const mp_obj_type_t py_kp_type = { + { &mp_type_type }, + .name = MP_QSTR_kp_desc, + .print = py_kp_print, + .subscr = py_kp_subscr, + .unary_op = py_kp_unary_op, +}; + +py_kp_obj_t *py_kpts_obj(mp_obj_t kpts_obj) +{ + PY_ASSERT_TYPE(kpts_obj, &py_kp_type); + return kpts_obj; +} + +#endif // IMLIB_ENABLE_FIND_KEYPOINTS + +// LBP descriptor ///////////////////////////////////////////////////////////// + +#ifdef IMLIB_ENABLE_FIND_LBP + +typedef struct _py_lbp_obj_t { + mp_obj_base_t base; + uint8_t *hist; +} py_lbp_obj_t; + +static void py_lbp_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + mp_printf(print, "{}"); +} + +static const mp_obj_type_t py_lbp_type = { + { &mp_type_type }, + .name = MP_QSTR_lbp_desc, + .print = py_lbp_print, +}; + +#endif // IMLIB_ENABLE_FIND_LBP + +// Keypoints Match Object ///////////////////////////////////////////////////// + +#if defined(IMLIB_ENABLE_DESCRIPTOR) && defined(IMLIB_ENABLE_FIND_KEYPOINTS) + +#define kptmatch_obj_size 9 +typedef struct _py_kptmatch_obj_t { + mp_obj_base_t base; + mp_obj_t cx, cy; + mp_obj_t x, y, w, h; + mp_obj_t count; + mp_obj_t theta; + mp_obj_t match; +} py_kptmatch_obj_t; + +static void py_kptmatch_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_kptmatch_obj_t *self = self_in; + mp_printf(print, "{\"cx\":%d, \"cy\":%d, \"x\":%d, \"y\":%d, \"w\":%d, \"h\":%d, \"count\":%d, \"theta\":%d}", + mp_obj_get_int(self->cx), mp_obj_get_int(self->cy), mp_obj_get_int(self->x), mp_obj_get_int(self->y), + mp_obj_get_int(self->w), mp_obj_get_int(self->h), mp_obj_get_int(self->count), mp_obj_get_int(self->theta)); +} + +static mp_obj_t py_kptmatch_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_kptmatch_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(kptmatch_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->x) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, kptmatch_obj_size, index, false)) { + case 0: return self->cx; + case 1: return self->cy; + case 2: return self->x; + case 3: return self->y; + case 4: return self->w; + case 5: return self->h; + case 6: return self->count; + case 7: return self->theta; + case 8: return self->match; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_kptmatch_cx(mp_obj_t self_in) { return ((py_kptmatch_obj_t *) self_in)->cx; } +mp_obj_t py_kptmatch_cy(mp_obj_t self_in) { return ((py_kptmatch_obj_t *) self_in)->cy; } +mp_obj_t py_kptmatch_x (mp_obj_t self_in) { return ((py_kptmatch_obj_t *) self_in)->x; } +mp_obj_t py_kptmatch_y (mp_obj_t self_in) { return ((py_kptmatch_obj_t *) self_in)->y; } +mp_obj_t py_kptmatch_w (mp_obj_t self_in) { return ((py_kptmatch_obj_t *) self_in)->w; } +mp_obj_t py_kptmatch_h (mp_obj_t self_in) { return ((py_kptmatch_obj_t *) self_in)->h; } +mp_obj_t py_kptmatch_count(mp_obj_t self_in) { return ((py_kptmatch_obj_t *) self_in)->count; } +mp_obj_t py_kptmatch_theta(mp_obj_t self_in) { return ((py_kptmatch_obj_t *) self_in)->theta; } +mp_obj_t py_kptmatch_match(mp_obj_t self_in) { return ((py_kptmatch_obj_t *) self_in)->match; } +mp_obj_t py_kptmatch_rect(mp_obj_t self_in) { + return mp_obj_new_tuple(4, (mp_obj_t []) {((py_kptmatch_obj_t *) self_in)->x, + ((py_kptmatch_obj_t *) self_in)->y, + ((py_kptmatch_obj_t *) self_in)->w, + ((py_kptmatch_obj_t *) self_in)->h}); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_cx_obj, py_kptmatch_cx); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_cy_obj, py_kptmatch_cy); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_x_obj, py_kptmatch_x); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_y_obj, py_kptmatch_y); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_w_obj, py_kptmatch_w); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_h_obj, py_kptmatch_h); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_count_obj, py_kptmatch_count); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_theta_obj, py_kptmatch_theta); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_match_obj, py_kptmatch_match); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_kptmatch_rect_obj, py_kptmatch_rect); + +STATIC const mp_rom_map_elem_t py_kptmatch_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_cx), MP_ROM_PTR(&py_kptmatch_cx_obj) }, + { MP_ROM_QSTR(MP_QSTR_cy), MP_ROM_PTR(&py_kptmatch_cy_obj) }, + { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_kptmatch_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_kptmatch_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_w), MP_ROM_PTR(&py_kptmatch_w_obj) }, + { MP_ROM_QSTR(MP_QSTR_h), MP_ROM_PTR(&py_kptmatch_h_obj) }, + { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&py_kptmatch_count_obj) }, + { MP_ROM_QSTR(MP_QSTR_theta), MP_ROM_PTR(&py_kptmatch_theta_obj) }, + { MP_ROM_QSTR(MP_QSTR_match), MP_ROM_PTR(&py_kptmatch_match_obj) }, + { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&py_kptmatch_rect_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_kptmatch_locals_dict, py_kptmatch_locals_dict_table); + +static const mp_obj_type_t py_kptmatch_type = { + { &mp_type_type }, + .name = MP_QSTR_kptmatch, + .print = py_kptmatch_print, + .subscr = py_kptmatch_subscr, + .locals_dict = (mp_obj_t) &py_kptmatch_locals_dict +}; + + +#endif //IMLIB_ENABLE_DESCRIPTOR && IMLIB_ENABLE_FIND_KEYPOINTS + +// Image ////////////////////////////////////////////////////////////////////// + +typedef struct _py_image_obj_t { + mp_obj_base_t base; + image_t _cobj; +} py_image_obj_t; + +typedef struct _mp_obj_py_image_it_t { + mp_obj_base_t base; + mp_fun_1_t iternext; + mp_obj_t py_image; + size_t cur; +} mp_obj_py_image_it_t; + +void *py_image_cobj(mp_obj_t img_obj) +{ + PY_ASSERT_TYPE(img_obj, &py_image_type); + return &((py_image_obj_t *)img_obj)->_cobj; +} + +mp_obj_t py_image_unary_op(mp_unary_op_t op, mp_obj_t self_in) { + py_image_obj_t *self = MP_OBJ_TO_PTR(self_in); + switch (op) { + case MP_UNARY_OP_LEN: { + image_t *img = &self->_cobj; + if (img->pixfmt == PIXFORMAT_JPEG) { + // For JPEG images we create a 1D array. + return mp_obj_new_int(img->size); + } else { + // For other formats, 2D array is created. + return mp_obj_new_int(img->h); + } + } + default: + return MP_OBJ_NULL; // op not supported + } +} + +// image iterator +STATIC mp_obj_t py_image_it_iternext(mp_obj_t self_in) +{ + mp_obj_py_image_it_t *self = MP_OBJ_TO_PTR(self_in); + py_image_obj_t *image = MP_OBJ_TO_PTR(self->py_image); + image_t *img = &image->_cobj; + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + if (self->cur >= img->h) { + return MP_OBJ_STOP_ITERATION; + } else { + mp_obj_t row = mp_obj_new_list(0, NULL); + for (int i=0; iw; i++) { + mp_obj_list_append(row, mp_obj_new_int(IMAGE_GET_BINARY_PIXEL(img, i, self->cur))); + } + self->cur++; + return row; + } + } + case PIXFORMAT_GRAYSCALE: + case PIXFORMAT_BAYER_ANY: { + if (self->cur >= img->h) { + return MP_OBJ_STOP_ITERATION; + } else { + mp_obj_t row = mp_obj_new_list(0, NULL); + for (int i=0; iw; i++) { + mp_obj_list_append(row, mp_obj_new_int(IMAGE_GET_GRAYSCALE_PIXEL(img, i, self->cur))); + } + self->cur++; + return row; + } + } + case PIXFORMAT_RGB565: + case PIXFORMAT_YUV_ANY: { + if (self->cur >= img->h) { + return MP_OBJ_STOP_ITERATION; + } else { + mp_obj_t row = mp_obj_new_list(0, NULL); + for (int i=0; iw; i++) { + mp_obj_list_append(row, mp_obj_new_int(IMAGE_GET_RGB565_PIXEL(img, i, self->cur))); + } + self->cur++; + return row; + } + } + default: {// JPEG + if (self->cur >= img->size) { + return MP_OBJ_STOP_ITERATION; + } else { + return mp_obj_new_int(img->pixels[self->cur++]); + } + } + } +} + +STATIC mp_obj_t py_image_getiter(mp_obj_t o_in, mp_obj_iter_buf_t *iter_buf) +{ + assert(sizeof(mp_obj_py_image_it_t) <= sizeof(mp_obj_iter_buf_t)); + mp_obj_py_image_it_t *o = (mp_obj_py_image_it_t*)iter_buf; + o->base.type = &mp_type_polymorph_iter; + o->iternext = py_image_it_iternext; + o->py_image = o_in; + o->cur = 0; + return MP_OBJ_FROM_PTR(o); +} + +static void py_image_print(const mp_print_t *print, mp_obj_t self, mp_print_kind_t kind) +{ + image_t *image = py_image_cobj(self); + if (image->pixfmt == PIXFORMAT_JPEG + && image->pixels[0] == 0xFE + && image->pixels[image->size-1] == 0xFE) { + // print for ide. + print->print_strn(print->data, (const char *) image->pixels, image->size); + } else { + mp_printf(print, "{\"w\":%d, \"h\":%d, \"type\":\"%s\", \"size\":%d}", + image->w, + image->h, + (image->pixfmt == PIXFORMAT_BINARY) ? "binary" : + (image->pixfmt == PIXFORMAT_GRAYSCALE) ? "grayscale" : + (image->pixfmt == PIXFORMAT_RGB565) ? "rgb565" : + (image->pixfmt == PIXFORMAT_BAYER_BGGR) ? "bayer_bggr" : + (image->pixfmt == PIXFORMAT_BAYER_GBRG) ? "bayer_gbrg" : + (image->pixfmt == PIXFORMAT_BAYER_GRBG) ? "bayer_grbg" : + (image->pixfmt == PIXFORMAT_BAYER_RGGB) ? "bayer_rggb" : + (image->pixfmt == PIXFORMAT_YUV422) ? "yuv422" : + (image->pixfmt == PIXFORMAT_YVU422) ? "yvu422" : + (image->pixfmt == PIXFORMAT_JPEG) ? "jpeg" : "unknown", + image_size(image)); + } +} + +static mp_obj_t py_image_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + py_image_obj_t *self = self_in; + image_t *image = py_image_cobj(self); + if (value == MP_OBJ_NULL) { // delete + } else if (value == MP_OBJ_SENTINEL) { // load + switch (image->pixfmt) { + case PIXFORMAT_BINARY: { + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(image->w * image->h, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + for (mp_uint_t i = 0; i < result->len; i++) { + result->items[i] = mp_obj_new_int(IMAGE_GET_BINARY_PIXEL(image, (slice.start + i) % image->w, (slice.start + i) / image->w)); + } + return result; + } + mp_uint_t i = mp_get_index(self->base.type, image->w * image->h, index, false); + return mp_obj_new_int(IMAGE_GET_BINARY_PIXEL(image, i % image->w, i / image->w)); + } + case PIXFORMAT_GRAYSCALE: + case PIXFORMAT_BAYER_ANY: { + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(image->w * image->h, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + for (mp_uint_t i = 0; i < result->len; i++) { + uint8_t p = IMAGE_GET_GRAYSCALE_PIXEL(image, (slice.start + i) % image->w, (slice.start + i) / image->w); + result->items[i] = mp_obj_new_int(p); + } + return result; + } + mp_uint_t i = mp_get_index(self->base.type, image->w * image->h, index, false); + uint8_t p = IMAGE_GET_GRAYSCALE_PIXEL(image, i % image->w, i / image->w); + return mp_obj_new_int(p); + } + case PIXFORMAT_RGB565: + case PIXFORMAT_YUV_ANY: { + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(image->w * image->h, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + for (mp_uint_t i = 0; i < result->len; i++) { + uint16_t p = IMAGE_GET_RGB565_PIXEL(image, (slice.start + i) % image->w, (slice.start + i) / image->w); + if (image->is_yuv) { + result->items[i] = mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(p & 0xff), + mp_obj_new_int((p >> 8) & 0xff)}); + } else { + result->items[i] = mp_obj_new_tuple(3, (mp_obj_t []) {mp_obj_new_int(COLOR_RGB565_TO_R8(p)), + mp_obj_new_int(COLOR_RGB565_TO_G8(p)), + mp_obj_new_int(COLOR_RGB565_TO_B8(p))}); + } + } + return result; + } + mp_uint_t i = mp_get_index(self->base.type, image->w * image->h, index, false); + uint16_t p = IMAGE_GET_RGB565_PIXEL(image, i % image->w, i / image->w); + if (image->is_yuv) { + return mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(p & 0xff), + mp_obj_new_int((p >> 8) & 0xff)}); + } else { + return mp_obj_new_tuple(3, (mp_obj_t []) {mp_obj_new_int(COLOR_RGB565_TO_R8(p)), + mp_obj_new_int(COLOR_RGB565_TO_G8(p)), + mp_obj_new_int(COLOR_RGB565_TO_B8(p))}); + } + } + case PIXFORMAT_JPEG: { + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(image->size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + for (mp_uint_t i = 0; i < result->len; i++) { + result->items[i] = mp_obj_new_int(image->data[slice.start + i]); + } + return result; + } + mp_uint_t i = mp_get_index(self->base.type, image->size, index, false); + return mp_obj_new_int(image->data[i]); + } + default: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Invalid pixel format")); + } + } else { // store + switch (image->pixfmt) { + case PIXFORMAT_BINARY: { + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(image->w * image->h, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + if (MP_OBJ_IS_TYPE(value, &mp_type_list)) { + mp_uint_t value_l_len; + mp_obj_t *value_l; + mp_obj_get_array(value, &value_l_len, &value_l); + PY_ASSERT_TRUE_MSG(value_l_len == (slice.stop - slice.start), "cannot grow or shrink image"); + for (mp_uint_t i = 0; i < (slice.stop - slice.start); i++) { + IMAGE_PUT_BINARY_PIXEL(image, (slice.start + i) % image->w, (slice.start + i) / image->w, mp_obj_get_int(value_l[i])); + } + } else { + mp_int_t v = mp_obj_get_int(value); + for (mp_uint_t i = 0; i < (slice.stop - slice.start); i++) { + IMAGE_PUT_BINARY_PIXEL(image, (slice.start + i) % image->w, (slice.start + i) / image->w, v); + } + } + return mp_const_none; + } + mp_uint_t i = mp_get_index(self->base.type, image->w * image->h, index, false); + IMAGE_PUT_BINARY_PIXEL(image, i % image->w, i / image->w, mp_obj_get_int(value)); + return mp_const_none; + } + case PIXFORMAT_GRAYSCALE: + case PIXFORMAT_BAYER_ANY: { + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(image->w * image->h, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + if (MP_OBJ_IS_TYPE(value, &mp_type_list)) { + mp_uint_t value_l_len; + mp_obj_t *value_l; + mp_obj_get_array(value, &value_l_len, &value_l); + PY_ASSERT_TRUE_MSG(value_l_len == (slice.stop - slice.start), "cannot grow or shrink image"); + for (mp_uint_t i = 0; i < (slice.stop - slice.start); i++) { + uint8_t p = mp_obj_get_int(value_l[i]); + IMAGE_PUT_GRAYSCALE_PIXEL(image, (slice.start + i) % image->w, (slice.start + i) / image->w, p); + } + } else { + uint8_t p = mp_obj_get_int(value); + for (mp_uint_t i = 0; i < (slice.stop - slice.start); i++) { + IMAGE_PUT_GRAYSCALE_PIXEL(image, (slice.start + i) % image->w, (slice.start + i) / image->w, p); + } + } + return mp_const_none; + } + mp_uint_t i = mp_get_index(self->base.type, image->w * image->h, index, false); + uint8_t p = mp_obj_get_int(value); + IMAGE_PUT_GRAYSCALE_PIXEL(image, i % image->w, i / image->w, p); + return mp_const_none; + } + case PIXFORMAT_RGB565: + case PIXFORMAT_YUV_ANY: { + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(image->w * image->h, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + if (MP_OBJ_IS_TYPE(value, &mp_type_list)) { + mp_uint_t value_l_len; + mp_obj_t *value_l; + mp_obj_get_array(value, &value_l_len, &value_l); + PY_ASSERT_TRUE_MSG(value_l_len == (slice.stop - slice.start), "cannot grow or shrink image"); + for (mp_uint_t i = 0; i < (slice.stop - slice.start); i++) { + mp_obj_t *value_2; + uint16_t p; + if (image->is_yuv) { + mp_obj_get_array_fixed_n(value_l[i], 2, &value_2); + p = (mp_obj_get_int(value_2[0]) & 0xff) | + (mp_obj_get_int(value_2[1]) & 0xff) << 8; + } else { + mp_obj_get_array_fixed_n(value_l[i], 3, &value_2); + p = COLOR_R8_G8_B8_TO_RGB565(mp_obj_get_int(value_2[0]), + mp_obj_get_int(value_2[1]), + mp_obj_get_int(value_2[2])); + } + IMAGE_PUT_RGB565_PIXEL(image, (slice.start + i) % image->w, + (slice.start + i) / image->w, p); + } + } else { + mp_obj_t *value_2; + uint16_t p; + if (image->is_yuv) { + mp_obj_get_array_fixed_n(value, 2, &value_2); + p = (mp_obj_get_int(value_2[0]) & 0xff) | + (mp_obj_get_int(value_2[1]) & 0xff) << 8; + } else { + mp_obj_get_array_fixed_n(value, 3, &value_2); + p = COLOR_R8_G8_B8_TO_RGB565(mp_obj_get_int(value_2[0]), + mp_obj_get_int(value_2[1]), + mp_obj_get_int(value_2[2])); + } + for (mp_uint_t i = 0; i < (slice.stop - slice.start); i++) { + IMAGE_PUT_RGB565_PIXEL(image, (slice.start + i) % image->w, + (slice.start + i) / image->w, p); + } + } + return mp_const_none; + } + mp_uint_t i = mp_get_index(self->base.type, image->w * image->h, index, false); + mp_obj_t *value_2; + mp_obj_get_array_fixed_n(value, 3, &value_2); + uint16_t p = COLOR_R8_G8_B8_TO_RGB565(mp_obj_get_int(value_2[0]), mp_obj_get_int(value_2[1]), mp_obj_get_int(value_2[2])); + IMAGE_PUT_RGB565_PIXEL(image, i % image->w, i / image->w, p); + return mp_const_none; + } + case PIXFORMAT_JPEG: { + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(image->size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + if (MP_OBJ_IS_TYPE(value, &mp_type_list)) { + mp_uint_t value_l_len; + mp_obj_t *value_l; + mp_obj_get_array(value, &value_l_len, &value_l); + PY_ASSERT_TRUE_MSG(value_l_len == (slice.stop - slice.start), "cannot grow or shrink image"); + for (mp_uint_t i = 0; i < (slice.stop - slice.start); i++) { + image->data[slice.start + i] = mp_obj_get_int(value_l[i]); + } + } else { + mp_int_t v = mp_obj_get_int(value); + for (mp_uint_t i = 0; i < (slice.stop - slice.start); i++) { + image->data[slice.start + i] = v; + } + } + return mp_const_none; + } + mp_uint_t i = mp_get_index(self->base.type, image->size, index, false); + image->data[i] = mp_obj_get_int(value); + return mp_const_none; + } + default: + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Invalid pixel format")); + } + } + return MP_OBJ_NULL; // op not supported +} + +static mp_int_t py_image_get_buffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags) +{ + py_image_obj_t *self = self_in; + if (flags == MP_BUFFER_READ) { + bufinfo->buf = self->_cobj.data; + bufinfo->len = image_size(&self->_cobj); + bufinfo->typecode = 'b'; + return 0; + } else { // Can't write to an image! + bufinfo->buf = NULL; + bufinfo->len = 0; + bufinfo->typecode = -1; + return 1; + } +} + +//////////////// +// Basic Methods +//////////////// + +static mp_obj_t py_image_width(mp_obj_t img_obj) +{ + return mp_obj_new_int(((image_t *) py_image_cobj(img_obj))->w); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_width_obj, py_image_width); + +static mp_obj_t py_image_height(mp_obj_t img_obj) +{ + return mp_obj_new_int(((image_t *) py_image_cobj(img_obj))->h); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_height_obj, py_image_height); + +static mp_obj_t py_image_format(mp_obj_t img_obj) +{ + image_t *image = py_image_cobj(img_obj); + switch (image->pixfmt) { + case PIXFORMAT_BINARY: + return mp_obj_new_int(PIXFORMAT_BINARY); + case PIXFORMAT_GRAYSCALE: + return mp_obj_new_int(PIXFORMAT_GRAYSCALE); + case PIXFORMAT_RGB565: + return mp_obj_new_int(PIXFORMAT_RGB565); + case PIXFORMAT_BAYER_ANY: + return mp_obj_new_int(PIXFORMAT_BAYER); + case PIXFORMAT_YUV_ANY: + return mp_obj_new_int(PIXFORMAT_YUV422); + default: + return mp_obj_new_int(PIXFORMAT_JPEG); + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_format_obj, py_image_format); + +static mp_obj_t py_image_size(mp_obj_t img_obj) +{ + return mp_obj_new_int(image_size((image_t *) py_image_cobj(img_obj))); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_size_obj, py_image_size); + +static mp_obj_t py_image_bytearray(mp_obj_t img_obj) +{ + image_t *arg_img = (image_t *) py_image_cobj(img_obj); + return mp_obj_new_bytearray_by_ref(image_size(arg_img), arg_img->data); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_bytearray_obj, py_image_bytearray); + +STATIC mp_obj_t py_image_get_pixel(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_not_compressed(args[0]); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 2, &arg_vec); + int arg_x = mp_obj_get_int(arg_vec[0]); + int arg_y = mp_obj_get_int(arg_vec[1]); + + bool arg_rgbtuple = py_helper_keyword_int(n_args, args, offset, kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_rgbtuple), arg_img->pixfmt == PIXFORMAT_RGB565); + + if ((!IM_X_INSIDE(arg_img, arg_x)) || (!IM_Y_INSIDE(arg_img, arg_y))) { + return mp_const_none; + } + + switch (arg_img->pixfmt) { + case PIXFORMAT_BINARY: { + if (arg_rgbtuple) { + int pixel = IMAGE_GET_BINARY_PIXEL(arg_img, arg_x, arg_y); + mp_obj_t pixel_tuple[3]; + pixel_tuple[0] = mp_obj_new_int(COLOR_RGB565_TO_R8(COLOR_BINARY_TO_RGB565(pixel))); + pixel_tuple[1] = mp_obj_new_int(COLOR_RGB565_TO_G8(COLOR_BINARY_TO_RGB565(pixel))); + pixel_tuple[2] = mp_obj_new_int(COLOR_RGB565_TO_B8(COLOR_BINARY_TO_RGB565(pixel))); + return mp_obj_new_tuple(3, pixel_tuple); + } else { + return mp_obj_new_int(IMAGE_GET_BINARY_PIXEL(arg_img, arg_x, arg_y)); + } + } + case PIXFORMAT_GRAYSCALE: { + if (arg_rgbtuple) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL(arg_img, arg_x, arg_y); + mp_obj_t pixel_tuple[3]; + pixel_tuple[0] = mp_obj_new_int(COLOR_RGB565_TO_R8(COLOR_GRAYSCALE_TO_RGB565(pixel))); + pixel_tuple[1] = mp_obj_new_int(COLOR_RGB565_TO_G8(COLOR_GRAYSCALE_TO_RGB565(pixel))); + pixel_tuple[2] = mp_obj_new_int(COLOR_RGB565_TO_B8(COLOR_GRAYSCALE_TO_RGB565(pixel))); + return mp_obj_new_tuple(3, pixel_tuple); + } else { + return mp_obj_new_int(IMAGE_GET_GRAYSCALE_PIXEL(arg_img, arg_x, arg_y)); + } + } + case PIXFORMAT_RGB565: { + if (arg_rgbtuple) { + int pixel = IMAGE_GET_RGB565_PIXEL(arg_img, arg_x, arg_y); + mp_obj_t pixel_tuple[3]; + pixel_tuple[0] = mp_obj_new_int(COLOR_RGB565_TO_R8(pixel)); + pixel_tuple[1] = mp_obj_new_int(COLOR_RGB565_TO_G8(pixel)); + pixel_tuple[2] = mp_obj_new_int(COLOR_RGB565_TO_B8(pixel)); + return mp_obj_new_tuple(3, pixel_tuple); + } else { + return mp_obj_new_int(IMAGE_GET_RGB565_PIXEL(arg_img, arg_x, arg_y)); + } + } + case PIXFORMAT_BAYER_ANY: + if (arg_rgbtuple) { + uint16_t pixel; imlib_debayer_line(arg_x, arg_x + 1, arg_y, &pixel, PIXFORMAT_RGB565, arg_img); + mp_obj_t pixel_tuple[3]; + pixel_tuple[0] = mp_obj_new_int(COLOR_RGB565_TO_R8(pixel)); + pixel_tuple[1] = mp_obj_new_int(COLOR_RGB565_TO_G8(pixel)); + pixel_tuple[2] = mp_obj_new_int(COLOR_RGB565_TO_B8(pixel)); + return mp_obj_new_tuple(3, pixel_tuple); + } else { + return mp_obj_new_int(IMAGE_GET_BAYER_PIXEL(arg_img, arg_x, arg_y)); + } + case PIXFORMAT_YUV_ANY: + if (arg_rgbtuple) { + uint16_t pixel; imlib_deyuv_line(arg_x, arg_x + 1, arg_y, &pixel, PIXFORMAT_RGB565, arg_img); + mp_obj_t pixel_tuple[3]; + pixel_tuple[0] = mp_obj_new_int(COLOR_RGB565_TO_R8(pixel)); + pixel_tuple[1] = mp_obj_new_int(COLOR_RGB565_TO_G8(pixel)); + pixel_tuple[2] = mp_obj_new_int(COLOR_RGB565_TO_B8(pixel)); + return mp_obj_new_tuple(3, pixel_tuple); + } else { + return mp_obj_new_int(IMAGE_GET_YUV_PIXEL(arg_img, arg_x, arg_y)); + } + default: return mp_const_none; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_get_pixel_obj, 2, py_image_get_pixel); + +STATIC mp_obj_t py_image_set_pixel(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_not_compressed(args[0]); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 2, &arg_vec); + int arg_x = mp_obj_get_int(arg_vec[0]); + int arg_y = mp_obj_get_int(arg_vec[1]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset, kw_args, -1); // White. + + if ((!IM_X_INSIDE(arg_img, arg_x)) || (!IM_Y_INSIDE(arg_img, arg_y))) { + return args[0]; + } + + switch (arg_img->pixfmt) { + case PIXFORMAT_BINARY: { + IMAGE_PUT_BINARY_PIXEL(arg_img, arg_x, arg_y, arg_c); + return args[0]; + } + case PIXFORMAT_GRAYSCALE: + case PIXFORMAT_BAYER_ANY: { // re-use + IMAGE_PUT_GRAYSCALE_PIXEL(arg_img, arg_x, arg_y, arg_c); + return args[0]; + } + case PIXFORMAT_RGB565: + case PIXFORMAT_YUV_ANY: { // re-use + IMAGE_PUT_RGB565_PIXEL(arg_img, arg_x, arg_y, arg_c); + return args[0]; + } + default: return args[0]; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_set_pixel_obj, 2, py_image_set_pixel); + +#ifdef IMLIB_ENABLE_MEAN_POOLING +static mp_obj_t py_image_mean_pool(mp_obj_t img_obj, mp_obj_t x_div_obj, mp_obj_t y_div_obj) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(img_obj); + + int arg_x_div = mp_obj_get_int(x_div_obj); + PY_ASSERT_TRUE_MSG(arg_x_div >= 1, "Width divisor must be greater than >= 1"); + PY_ASSERT_TRUE_MSG(arg_x_div <= arg_img->w, "Width divisor must be less than <= img width"); + int arg_y_div = mp_obj_get_int(y_div_obj); + PY_ASSERT_TRUE_MSG(arg_y_div >= 1, "Height divisor must be greater than >= 1"); + PY_ASSERT_TRUE_MSG(arg_y_div <= arg_img->h, "Height divisor must be less than <= img height"); + + image_t out_img; + out_img.w = arg_img->w / arg_x_div; + out_img.h = arg_img->h / arg_y_div; + out_img.pixfmt = arg_img->pixfmt; + out_img.pixels = arg_img->pixels; + PY_ASSERT_TRUE_MSG(image_size(&out_img) <= image_size(arg_img), "Can't pool in place!"); + + imlib_mean_pool(arg_img, &out_img, arg_x_div, arg_y_div); + arg_img->w = out_img.w; + arg_img->h = out_img.h; + py_helper_update_framebuffer(arg_img); + return img_obj; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(py_image_mean_pool_obj, py_image_mean_pool); + +static mp_obj_t py_image_mean_pooled(mp_obj_t img_obj, mp_obj_t x_div_obj, mp_obj_t y_div_obj) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(img_obj); + + int arg_x_div = mp_obj_get_int(x_div_obj); + PY_ASSERT_TRUE_MSG(arg_x_div >= 1, "Width divisor must be greater than >= 1"); + PY_ASSERT_TRUE_MSG(arg_x_div <= arg_img->w, "Width divisor must be less than <= img width"); + int arg_y_div = mp_obj_get_int(y_div_obj); + PY_ASSERT_TRUE_MSG(arg_y_div >= 1, "Height divisor must be greater than >= 1"); + PY_ASSERT_TRUE_MSG(arg_y_div <= arg_img->h, "Height divisor must be less than <= img height"); + + image_t out_img; + out_img.w = arg_img->w / arg_x_div; + out_img.h = arg_img->h / arg_y_div; + out_img.pixfmt = arg_img->pixfmt; + out_img.pixels = xalloc(image_size(&out_img)); + + imlib_mean_pool(arg_img, &out_img, arg_x_div, arg_y_div); + return py_image_from_struct(&out_img); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(py_image_mean_pooled_obj, py_image_mean_pooled); +#endif // IMLIB_ENABLE_MEAN_POOLING + +#ifdef IMLIB_ENABLE_MIDPOINT_POOLING +static mp_obj_t py_image_midpoint_pool(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + int arg_x_div = mp_obj_get_int(args[1]); + PY_ASSERT_TRUE_MSG(arg_x_div >= 1, "Width divisor must be greater than >= 1"); + PY_ASSERT_TRUE_MSG(arg_x_div <= arg_img->w, "Width divisor must be less than <= img width"); + int arg_y_div = mp_obj_get_int(args[2]); + PY_ASSERT_TRUE_MSG(arg_y_div >= 1, "Height divisor must be greater than >= 1"); + PY_ASSERT_TRUE_MSG(arg_y_div <= arg_img->h, "Height divisor must be less than <= img height"); + + int arg_bias = py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bias), 0.5) * 256; + PY_ASSERT_TRUE_MSG((0 <= arg_bias) && (arg_bias <= 256), "Error: 0 <= bias <= 1!"); + + image_t out_img; + out_img.w = arg_img->w / arg_x_div; + out_img.h = arg_img->h / arg_y_div; + out_img.pixfmt = arg_img->pixfmt; + out_img.pixels = arg_img->pixels; + PY_ASSERT_TRUE_MSG(image_size(&out_img) <= image_size(arg_img), "Can't pool in place!"); + + imlib_midpoint_pool(arg_img, &out_img, arg_x_div, arg_y_div, arg_bias); + arg_img->w = out_img.w; + arg_img->h = out_img.h; + py_helper_update_framebuffer(arg_img); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_midpoint_pool_obj, 3, py_image_midpoint_pool); + +static mp_obj_t py_image_midpoint_pooled(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + int arg_x_div = mp_obj_get_int(args[1]); + PY_ASSERT_TRUE_MSG(arg_x_div >= 1, "Width divisor must be greater than >= 1"); + PY_ASSERT_TRUE_MSG(arg_x_div <= arg_img->w, "Width divisor must be less than <= img width"); + int arg_y_div = mp_obj_get_int(args[2]); + PY_ASSERT_TRUE_MSG(arg_y_div >= 1, "Height divisor must be greater than >= 1"); + PY_ASSERT_TRUE_MSG(arg_y_div <= arg_img->h, "Height divisor must be less than <= img height"); + + int arg_bias = py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bias), 0.5) * 256; + PY_ASSERT_TRUE_MSG((0 <= arg_bias) && (arg_bias <= 256), "Error: 0 <= bias <= 1!"); + + image_t out_img; + out_img.w = arg_img->w / arg_x_div; + out_img.h = arg_img->h / arg_y_div; + out_img.pixfmt = arg_img->pixfmt; + out_img.pixels = xalloc(image_size(&out_img)); + + imlib_midpoint_pool(arg_img, &out_img, arg_x_div, arg_y_div, arg_bias); + return py_image_from_struct(&out_img); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_midpoint_pooled_obj, 3, py_image_midpoint_pooled); +#endif // IMLIB_ENABLE_MIDPOINT_POOLING + +static mp_obj_t py_image_to(pixformat_t pixfmt, const uint16_t *default_color_palette, bool copy_to_fb, + uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *src_img = py_image_cobj(args[0]); + + float arg_x_scale = 1.f; + bool got_x_scale = py_helper_keyword_float_maybe(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_scale), &arg_x_scale); + + float arg_y_scale = 1.f; + bool got_y_scale = py_helper_keyword_float_maybe(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_scale), &arg_y_scale); + + rectangle_t arg_roi; + py_helper_keyword_rectangle_roi(src_img, n_args, args, 3, kw_args, &arg_roi); + + int arg_rgb_channel = py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_rgb_channel), -1); + if ((arg_rgb_channel < -1) || (2 < arg_rgb_channel)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("-1 <= rgb_channel <= 2!")); + } + + int arg_alpha = py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_alpha), 256); + if ((arg_alpha < 0) || (256 < arg_alpha)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= alpha <= 256!")); + } + + const uint16_t *color_palette = py_helper_keyword_color_palette(n_args, args, 6, kw_args, default_color_palette); + const uint8_t *alpha_palette = py_helper_keyword_alpha_palette(n_args, args, 7, kw_args, NULL); + + image_hint_t hint = py_helper_keyword_int(n_args, args, 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_hint), 0); + + int arg_x_size; + bool got_x_size = py_helper_keyword_int_maybe(n_args, args, 9, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_size), &arg_x_size); + + int arg_y_size; + bool got_y_size = py_helper_keyword_int_maybe(n_args, args, 10, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_size), &arg_y_size); + + if (got_x_scale && got_x_size) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Choose either x_scale or x_size not both!")); + } + + if (got_y_scale && got_y_size) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Choose either y_scale or y_size not both!")); + } + + if (got_x_size) { + arg_x_scale = arg_x_size / ((float) arg_roi.w); + } + + if (got_y_size) { + arg_y_scale = arg_y_size / ((float) arg_roi.h); + } + + if ((!got_x_scale) && (!got_x_size) && got_y_size) { + arg_x_scale = arg_y_scale; + } + + if ((!got_y_scale) && (!got_y_size) && got_x_size) { + arg_y_scale = arg_x_scale; + } + + mp_obj_t copy_obj = py_helper_keyword_object(n_args, args, 11, kw_args, + MP_OBJ_NEW_QSTR(copy_to_fb ? MP_QSTR_copy_to_fb : MP_QSTR_copy), NULL); + bool copy = false; + image_t *arg_other = copy_to_fb ? NULL : src_img; + + if (copy_obj) { + if (mp_obj_is_integer(copy_obj)) { + copy = mp_obj_get_int(copy_obj); + } else { + arg_other = py_helper_arg_to_image_not_compressed(copy_obj); + } + } + + if (copy_to_fb && copy) { + framebuffer_update_jpeg_buffer(); + } + + image_t dst_img = { + .w = fast_floorf(arg_roi.w * arg_x_scale), + .h = fast_floorf(arg_roi.h * arg_y_scale), + .size = src_img->size, + .pixfmt = (pixfmt == PIXFORMAT_INVALID) ? src_img->pixfmt : pixfmt, + .pixels = NULL, + }; + + if (dst_img.is_bayer) { + if (((arg_x_scale != 1) && (arg_x_scale != -1)) || + ((arg_y_scale != 1) && (arg_y_scale != -1)) || + (arg_rgb_channel != -1) || + (arg_alpha != 256) || + (color_palette != NULL) || + (alpha_palette != NULL)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Only bayer copying/cropping is supported!")); + } else { + bool shift_right = arg_roi.x % 2; + bool shift_down = arg_roi.y % 2; + switch (dst_img.pixfmt) { + case PIXFORMAT_BAYER_BGGR: { + if (shift_right && shift_down) { + dst_img.pixfmt = PIXFORMAT_BAYER_RGGB; + } else if (shift_right) { + dst_img.pixfmt = PIXFORMAT_BAYER_GBRG; + } else if (shift_down) { + dst_img.pixfmt = PIXFORMAT_BAYER_GRBG; + } + break; + } + case PIXFORMAT_BAYER_GBRG: { + if (shift_right && shift_down) { + dst_img.pixfmt = PIXFORMAT_BAYER_GRBG; + } else if (shift_right) { + dst_img.pixfmt = PIXFORMAT_BAYER_BGGR; + } else if (shift_down) { + dst_img.pixfmt = PIXFORMAT_BAYER_RGGB; + } + break; + } + case PIXFORMAT_BAYER_GRBG: { + if (shift_right && shift_down) { + dst_img.pixfmt = PIXFORMAT_BAYER_GBRG; + } else if (shift_right) { + dst_img.pixfmt = PIXFORMAT_BAYER_RGGB; + } else if (shift_down) { + dst_img.pixfmt = PIXFORMAT_BAYER_BGGR; + } + break; + } + case PIXFORMAT_BAYER_RGGB: { + if (shift_right && shift_down) { + dst_img.pixfmt = PIXFORMAT_BAYER_BGGR; + } else if (shift_right) { + dst_img.pixfmt = PIXFORMAT_BAYER_GRBG; + } else if (shift_down) { + dst_img.pixfmt = PIXFORMAT_BAYER_GBRG; + } + break; + } + default: { + break; + } + } + hint &= ~(IMAGE_HINT_AREA | + IMAGE_HINT_BICUBIC | + IMAGE_HINT_BILINEAR | + IMAGE_HINT_EXTRACT_RGB_CHANNEL_FIRST | + IMAGE_HINT_APPLY_COLOR_PALETTE_FIRST); + } + } else if (dst_img.is_yuv) { + if (((arg_x_scale != 1) && (arg_x_scale != -1)) || + ((arg_y_scale != 1) && (arg_y_scale != -1)) || + (arg_rgb_channel != -1) || + (arg_alpha != 256) || + (color_palette != NULL) || + (alpha_palette != NULL)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Only YUV422 copying/cropping is supported!")); + } else { + if (arg_roi.x % 2) { + dst_img.pixfmt = (dst_img.pixfmt == PIXFORMAT_YUV422) ? PIXFORMAT_YVU422 : PIXFORMAT_YUV422; + } + hint &= ~(IMAGE_HINT_AREA | + IMAGE_HINT_BICUBIC | + IMAGE_HINT_BILINEAR | + IMAGE_HINT_EXTRACT_RGB_CHANNEL_FIRST | + IMAGE_HINT_APPLY_COLOR_PALETTE_FIRST); + } + } else if (dst_img.pixfmt == PIXFORMAT_JPEG) { + if ((arg_x_scale != 1) || + (arg_y_scale != 1) || + (arg_roi.x != 0) || + (arg_roi.y != 0) || + (arg_roi.w != src_img->w) || + (arg_roi.h != src_img->h) || + (arg_rgb_channel != -1) || + (arg_alpha != 256) || + (color_palette != NULL) || + (alpha_palette != NULL)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Only jpeg copying is supported!")); + } + } + + uint32_t size = image_size(&dst_img); + + if (copy) { + if (copy_to_fb) { + py_helper_set_to_framebuffer(&dst_img); + } else { + dst_img.data = xalloc(size); + } + } else if (arg_other) { + bool fb = py_helper_is_equal_to_framebuffer(arg_other); + size_t buf_size = fb ? framebuffer_get_buffer_size() : image_size(arg_other); + PY_ASSERT_TRUE_MSG((size <= buf_size), + "The new image won't fit in the target frame buffer!"); + // DO NOT MODIFY arg_other YET (as it could point to src_img)! + dst_img.data = arg_other->data; + py_helper_update_framebuffer(&dst_img); + } else { + dst_img.data = xalloc(size); + } + + if (dst_img.pixfmt == PIXFORMAT_JPEG) { + if (dst_img.data != src_img->data) { + memcpy(dst_img.data, src_img->data, dst_img.size); + } + } else { + fb_alloc_mark(); + imlib_draw_image(&dst_img, src_img, 0, 0, arg_x_scale, arg_y_scale, &arg_roi, + arg_rgb_channel, arg_alpha, color_palette, alpha_palette, + (hint & (~IMAGE_HINT_CENTER)) | IMAGE_HINT_BLACK_BACKGROUND, NULL, NULL); + fb_alloc_free_till_mark(); + } + + if (arg_other) { + memcpy(arg_other, &dst_img, sizeof(image_t)); + } + + return py_image_from_struct(&dst_img); +} + +static mp_obj_t py_image_to_bitmap(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + return py_image_to(PIXFORMAT_BINARY, NULL, false, n_args, args, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_to_bitmap_obj, 1, py_image_to_bitmap); + +static mp_obj_t py_image_to_grayscale(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + return py_image_to(PIXFORMAT_GRAYSCALE, NULL, false, n_args, args, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_to_grayscale_obj, 1, py_image_to_grayscale); + +static mp_obj_t py_image_to_rgb565(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + return py_image_to(PIXFORMAT_RGB565, NULL, false, n_args, args, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_to_rgb565_obj, 1, py_image_to_rgb565); + +static mp_obj_t py_image_to_rainbow(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + return py_image_to(PIXFORMAT_RGB565, rainbow_table, false, n_args, args, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_to_rainbow_obj, 1, py_image_to_rainbow); + +static mp_obj_t py_image_copy(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + return py_image_to(PIXFORMAT_INVALID, NULL, true, n_args, args, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_copy_obj, 1, py_image_copy); + +static mp_obj_t py_image_crop(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + return py_image_to(PIXFORMAT_INVALID, NULL, false, n_args, args, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_crop_obj, 1, py_image_crop); + +static mp_obj_t py_image_jpeg_encode_for_ide(mp_obj_t img_obj) +{ + image_t *arg_img = py_image_cobj(img_obj); + PY_ASSERT_TRUE_MSG(arg_img->pixfmt == PIXFORMAT_JPEG, "Image format is not supported!"); + PY_ASSERT_TRUE_MSG(py_helper_is_equal_to_framebuffer(arg_img), "Can't compress in place!"); + + int new_size = fb_encode_for_ide_new_size(arg_img); + fb_alloc_mark(); + uint8_t *temp = fb_alloc(new_size, FB_ALLOC_NO_HINT); + fb_encode_for_ide(temp, arg_img); + + image_t out; + out.w = arg_img->w; + out.h = arg_img->h; + out.size = new_size; + py_helper_set_to_framebuffer(&out); + arg_img->size = new_size; + + memcpy(arg_img->data, temp, new_size); + fb_alloc_free_till_mark(); + + return img_obj; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_jpeg_encode_for_ide_obj, py_image_jpeg_encode_for_ide); + +static mp_obj_t py_image_jpeg_encoded_for_ide(mp_obj_t img_obj) +{ + image_t *arg_img = py_image_cobj(img_obj); + PY_ASSERT_TRUE_MSG(arg_img->pixfmt == PIXFORMAT_JPEG, "Image format is not supported!"); + + int new_size = fb_encode_for_ide_new_size(arg_img); + uint8_t *temp = xalloc(new_size); + fb_encode_for_ide(temp, arg_img); + arg_img->size = new_size; + + return py_image(arg_img->w, arg_img->h, arg_img->pixfmt, new_size, temp); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_jpeg_encoded_for_ide_obj, py_image_jpeg_encoded_for_ide); + +static mp_obj_t py_image_compress(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + if (IM_IS_JPEG((image_t *) py_image_cobj(args[0]))) return args[0]; + + image_t *arg_img = py_helper_arg_to_image_not_compressed(args[0]); + int arg_q = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_quality), 50); + PY_ASSERT_TRUE_MSG((1 <= arg_q) && (arg_q <= 100), "Error: 1 <= quality <= 100!"); + + fb_alloc_mark(); + image_t out = { .w=arg_img->w, .h=arg_img->h, .pixfmt=PIXFORMAT_JPEG, .size=0, .data=NULL }; // alloc in jpeg compress + PY_ASSERT_FALSE_MSG(jpeg_compress(arg_img, &out, arg_q, false), "Out of Memory!"); + PY_ASSERT_TRUE_MSG(out.size <= image_size(arg_img), "Can't compress in place!"); + memcpy(arg_img->data, out.data, out.size); + + arg_img->pixfmt = PIXFORMAT_JPEG; + arg_img->size = out.size; + fb_alloc_free_till_mark(); + py_helper_update_framebuffer(arg_img); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_compress_obj, 1, py_image_compress); + +static mp_obj_t py_image_compress_for_ide(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + if (IM_IS_JPEG((image_t *) py_image_cobj(args[0]))) return py_image_jpeg_encode_for_ide(args[0]); + + image_t *arg_img = py_helper_arg_to_image_not_compressed(args[0]); + int arg_q = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_quality), 50); + PY_ASSERT_TRUE_MSG((1 <= arg_q) && (arg_q <= 100), "Error: 1 <= quality <= 100!"); + + fb_alloc_mark(); + image_t out = { .w=arg_img->w, .h=arg_img->h, .pixfmt=PIXFORMAT_JPEG, .size=0, .data=NULL }; // alloc in jpeg compress + PY_ASSERT_FALSE_MSG(jpeg_compress(arg_img, &out, arg_q, false), "Out of Memory!"); + int new_size = fb_encode_for_ide_new_size(&out); + PY_ASSERT_TRUE_MSG(new_size <= image_size(arg_img), "Can't compress in place!"); + fb_encode_for_ide(arg_img->data, &out); + + arg_img->pixfmt = PIXFORMAT_JPEG; + arg_img->size = new_size; + fb_alloc_free_till_mark(); + py_helper_update_framebuffer(arg_img); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_compress_for_ide_obj, 1, py_image_compress_for_ide); + +static mp_obj_t py_image_compressed(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_image_cobj(args[0]); + + if (IM_IS_JPEG(arg_img)) { + image_t out = { .w=arg_img->w, .h=arg_img->h, .pixfmt=PIXFORMAT_JPEG, .size=arg_img->size, .data=xalloc(arg_img->size) }; + memcpy(out.data, arg_img->data, arg_img->size); + return py_image_from_struct(&out); + } + + arg_img = py_helper_arg_to_image_not_compressed(args[0]); + int arg_q = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_quality), 50); + PY_ASSERT_TRUE_MSG((1 <= arg_q) && (arg_q <= 100), "Error: 1 <= quality <= 100!"); + + fb_alloc_mark(); + image_t out = { .w=arg_img->w, .h=arg_img->h, .pixfmt=PIXFORMAT_JPEG, .size=0, .data=NULL }; // alloc in jpeg compress + PY_ASSERT_FALSE_MSG(jpeg_compress(arg_img, &out, arg_q, false), "Out of Memory!"); + uint8_t *temp = xalloc(out.size); + memcpy(temp, out.data, out.size); + out.data = temp; + fb_alloc_free_till_mark(); + + return py_image_from_struct(&out); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_compressed_obj, 1, py_image_compressed); + +static mp_obj_t py_image_compressed_for_ide(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + if (IM_IS_JPEG((image_t *) py_image_cobj(args[0]))) return py_image_jpeg_encoded_for_ide(args[0]); + + image_t *arg_img = py_helper_arg_to_image_not_compressed(args[0]); + int arg_q = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_quality), 50); + PY_ASSERT_TRUE_MSG((1 <= arg_q) && (arg_q <= 100), "Error: 1 <= quality <= 100!"); + + fb_alloc_mark(); + image_t out = { .w=arg_img->w, .h=arg_img->h, .pixfmt=PIXFORMAT_JPEG, .size=0, .data=NULL }; // alloc in jpeg compress + PY_ASSERT_FALSE_MSG(jpeg_compress(arg_img, &out, arg_q, false), "Out of Memory!"); + int new_size = fb_encode_for_ide_new_size(&out); + uint8_t *temp = xalloc(new_size); + fb_encode_for_ide(temp, &out); + + out.size = new_size; + out.data = temp; + fb_alloc_free_till_mark(); + + return py_image_from_struct(&out); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_compressed_for_ide_obj, 1, py_image_compressed_for_ide); + +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) +static mp_obj_t py_image_save(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_image_cobj(args[0]); + const char *path = mp_obj_str_get_str(args[1]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 2, kw_args, &roi); + + int arg_q = py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_quality), 50); + PY_ASSERT_TRUE_MSG((1 <= arg_q) && (arg_q <= 100), "Error: 1 <= quality <= 100!"); + + fb_alloc_mark(); + imlib_save_image(arg_img, path, &roi, arg_q); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_save_obj, 2, py_image_save); +#endif //IMLIB_ENABLE_IMAGE_FILE_IO + +static mp_obj_t py_image_flush(mp_obj_t img_obj) +{ + framebuffer_update_jpeg_buffer(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_flush_obj, py_image_flush); + +////////////////// +// Drawing Methods +////////////////// + +STATIC mp_obj_t py_image_clear(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_not_compressed(args[0]); + + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 1, kw_args); + + if (!arg_msk) { + memset(arg_img->data, 0, image_size(arg_img)); + } else { + imlib_zero(arg_img, arg_msk, false); + } + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_clear_obj, 1, py_image_clear); + +STATIC mp_obj_t py_image_draw_line(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 4, &arg_vec); + int arg_x0 = mp_obj_get_int(arg_vec[0]); + int arg_y0 = mp_obj_get_int(arg_vec[1]); + int arg_x1 = mp_obj_get_int(arg_vec[2]); + int arg_y1 = mp_obj_get_int(arg_vec[3]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + + imlib_draw_line(arg_img, arg_x0, arg_y0, arg_x1, arg_y1, arg_c, arg_thickness); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_line_obj, 2, py_image_draw_line); + +STATIC mp_obj_t py_image_draw_rectangle(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 4, &arg_vec); + int arg_rx = mp_obj_get_int(arg_vec[0]); + int arg_ry = mp_obj_get_int(arg_vec[1]); + int arg_rw = mp_obj_get_int(arg_vec[2]); + int arg_rh = mp_obj_get_int(arg_vec[3]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + bool arg_fill = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); + + imlib_draw_rectangle(arg_img, arg_rx, arg_ry, arg_rw, arg_rh, arg_c, arg_thickness, arg_fill); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_rectangle_obj, 2, py_image_draw_rectangle); + +STATIC mp_obj_t py_image_draw_circle(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 3, &arg_vec); + int arg_cx = mp_obj_get_int(arg_vec[0]); + int arg_cy = mp_obj_get_int(arg_vec[1]); + int arg_cr = mp_obj_get_int(arg_vec[2]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + bool arg_fill = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); + + imlib_draw_circle(arg_img, arg_cx, arg_cy, arg_cr, arg_c, arg_thickness, arg_fill); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_circle_obj, 2, py_image_draw_circle); + +STATIC mp_obj_t py_image_draw_ellipse(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 5, &arg_vec); + int arg_cx = mp_obj_get_int(arg_vec[0]); + int arg_cy = mp_obj_get_int(arg_vec[1]); + int arg_rx = mp_obj_get_int(arg_vec[2]); + int arg_ry = mp_obj_get_int(arg_vec[3]); + int arg_r = mp_obj_get_int(arg_vec[4]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 1, kw_args, -1); // White. + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + bool arg_fill = + py_helper_keyword_int(n_args, args, offset + 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); + + imlib_draw_ellipse(arg_img, arg_cx, arg_cy, arg_rx, arg_ry, arg_r, arg_c, arg_thickness, arg_fill); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_ellipse_obj, 2, py_image_draw_ellipse); + +STATIC mp_obj_t py_image_draw_string(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 3, &arg_vec); + int arg_x_off = mp_obj_get_int(arg_vec[0]); + int arg_y_off = mp_obj_get_int(arg_vec[1]); + const char *arg_str = mp_obj_str_get_str(arg_vec[2]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + float arg_scale = + py_helper_keyword_float(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_scale), 1.0); + PY_ASSERT_TRUE_MSG(0 < arg_scale, "Error: 0 < scale!"); + int arg_x_spacing = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_spacing), 0); + int arg_y_spacing = + py_helper_keyword_int(n_args, args, offset + 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_spacing), 0); + bool arg_mono_space = + py_helper_keyword_int(n_args, args, offset + 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mono_space), true); + int arg_char_rotation = + py_helper_keyword_int(n_args, args, offset + 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_char_rotation), 0); + int arg_char_hmirror = + py_helper_keyword_int(n_args, args, offset + 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_char_hmirror), false); + int arg_char_vflip = + py_helper_keyword_int(n_args, args, offset + 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_char_vflip), false); + int arg_string_rotation = + py_helper_keyword_int(n_args, args, offset + 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_string_rotation), 0); + int arg_string_hmirror = + py_helper_keyword_int(n_args, args, offset + 9, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_string_hmirror), false); + int arg_string_vflip = + py_helper_keyword_int(n_args, args, offset + 10, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_string_vflip), false); + + imlib_draw_string(arg_img, arg_x_off, arg_y_off, arg_str, + arg_c, arg_scale, arg_x_spacing, arg_y_spacing, arg_mono_space, + arg_char_rotation, arg_char_hmirror, arg_char_vflip, + arg_string_rotation, arg_string_hmirror, arg_string_vflip); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_string_obj, 2, py_image_draw_string); + +STATIC mp_obj_t py_image_draw_cross(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 2, &arg_vec); + int arg_x = mp_obj_get_int(arg_vec[0]); + int arg_y = mp_obj_get_int(arg_vec[1]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_s = + py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 5); + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + + imlib_draw_line(arg_img, arg_x - arg_s, arg_y , arg_x + arg_s, arg_y , arg_c, arg_thickness); + imlib_draw_line(arg_img, arg_x , arg_y - arg_s, arg_x , arg_y + arg_s, arg_c, arg_thickness); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_cross_obj, 2, py_image_draw_cross); + +STATIC mp_obj_t py_image_draw_arrow(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 4, &arg_vec); + int arg_x0 = mp_obj_get_int(arg_vec[0]); + int arg_y0 = mp_obj_get_int(arg_vec[1]); + int arg_x1 = mp_obj_get_int(arg_vec[2]); + int arg_y1 = mp_obj_get_int(arg_vec[3]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 0, kw_args, -1); // White. + int arg_s = + py_helper_keyword_int(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 10); + int arg_thickness = + py_helper_keyword_int(n_args, args, offset + 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + + int dx = (arg_x1 - arg_x0); + int dy = (arg_y1 - arg_y0); + float length = fast_sqrtf((dx * dx) + (dy * dy)); + + float ux = IM_DIV(dx, length); + float uy = IM_DIV(dy, length); + float vx = -uy; + float vy = ux; + + int a0x = fast_roundf(arg_x1 - (arg_s * ux) + (arg_s * vx * 0.5)); + int a0y = fast_roundf(arg_y1 - (arg_s * uy) + (arg_s * vy * 0.5)); + int a1x = fast_roundf(arg_x1 - (arg_s * ux) - (arg_s * vx * 0.5)); + int a1y = fast_roundf(arg_y1 - (arg_s * uy) - (arg_s * vy * 0.5)); + + imlib_draw_line(arg_img, arg_x0, arg_y0, arg_x1, arg_y1, arg_c, arg_thickness); + imlib_draw_line(arg_img, arg_x1, arg_y1, a0x, a0y, arg_c, arg_thickness); + imlib_draw_line(arg_img, arg_x1, arg_y1, a1x, a1y, arg_c, arg_thickness); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_arrow_obj, 2, py_image_draw_arrow); + +STATIC mp_obj_t py_image_draw_edges(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(args[1], 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, 2, kw_args, -1); // White. + int arg_s = + py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 0); + int arg_thickness = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + bool arg_fill = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); + + imlib_draw_line(arg_img, x0, y0, x1, y1, arg_c, arg_thickness); + imlib_draw_line(arg_img, x1, y1, x2, y2, arg_c, arg_thickness); + imlib_draw_line(arg_img, x2, y2, x3, y3, arg_c, arg_thickness); + imlib_draw_line(arg_img, x3, y3, x0, y0, arg_c, arg_thickness); + + if (arg_s >= 1) { + imlib_draw_circle(arg_img, x0, y0, arg_s, arg_c, arg_thickness, arg_fill); + imlib_draw_circle(arg_img, x1, y1, arg_s, arg_c, arg_thickness, arg_fill); + imlib_draw_circle(arg_img, x2, y2, arg_s, arg_c, arg_thickness, arg_fill); + imlib_draw_circle(arg_img, x3, y3, arg_s, arg_c, arg_thickness, arg_fill); + } + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_edges_obj, 2, py_image_draw_edges); + +STATIC mp_obj_t py_image_draw_image(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + image_t *arg_other = py_helper_arg_to_image_not_compressed(args[1]); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 2, 2, &arg_vec); + int arg_x_off = mp_obj_get_int(arg_vec[0]); + int arg_y_off = mp_obj_get_int(arg_vec[1]); + + float arg_x_scale = 1.f; + bool got_x_scale = py_helper_keyword_float_maybe(n_args, args, offset + 0, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_scale), &arg_x_scale); + + float arg_y_scale = 1.f; + bool got_y_scale = py_helper_keyword_float_maybe(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_scale), &arg_y_scale); + + rectangle_t arg_roi; + py_helper_keyword_rectangle_roi(arg_other, n_args, args, offset + 2, kw_args, &arg_roi); + + int arg_rgb_channel = py_helper_keyword_int(n_args, args, offset + 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_rgb_channel), -1); + if ((arg_rgb_channel < -1) || (2 < arg_rgb_channel)) mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("-1 <= rgb_channel <= 2!")); + + int arg_alpha = py_helper_keyword_int(n_args, args, offset + 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_alpha), 256); + if ((arg_alpha < 0) || (256 < arg_alpha)) mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= alpha <= 256!")); + + const uint16_t *color_palette = py_helper_keyword_color_palette(n_args, args, offset + 5, kw_args, NULL); + const uint8_t *alpha_palette = py_helper_keyword_alpha_palette(n_args, args, offset + 6, kw_args, NULL); + + image_hint_t hint = py_helper_keyword_int(n_args, args, offset + 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_hint), 0); + + int arg_x_size; + bool got_x_size = py_helper_keyword_int_maybe(n_args, args, offset + 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_size), &arg_x_size); + + int arg_y_size; + bool got_y_size = py_helper_keyword_int_maybe(n_args, args, offset + 9, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_size), &arg_y_size); + + if (got_x_scale && got_x_size) mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Choose either x_scale or x_size not both!")); + if (got_y_scale && got_y_size) mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Choose either y_scale or y_size not both!")); + + if (got_x_size) arg_x_scale = arg_x_size / ((float) arg_roi.w); + if (got_y_size) arg_y_scale = arg_y_size / ((float) arg_roi.h); + + if ((!got_x_scale) && (!got_x_size) && got_y_size) arg_x_scale = arg_y_scale; + if ((!got_y_scale) && (!got_y_size) && got_x_size) arg_y_scale = arg_x_scale; + + fb_alloc_mark(); + imlib_draw_image(arg_img, arg_other, arg_x_off, arg_y_off, arg_x_scale, arg_y_scale, &arg_roi, + arg_rgb_channel, arg_alpha, color_palette, alpha_palette, hint, NULL, NULL); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_image_obj, 3, py_image_draw_image); + +STATIC mp_obj_t py_image_draw_keypoints(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, 2, kw_args, -1); // White. + int arg_s = + py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 10); + int arg_thickness = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_thickness), 1); + bool arg_fill = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fill), false); + + if (MP_OBJ_IS_TYPE(args[1], &mp_type_tuple) || MP_OBJ_IS_TYPE(args[1], &mp_type_list)) { + size_t len; + mp_obj_t *items; + mp_obj_get_array(args[1], &len, &items); + for (size_t i = 0; i < len; i++) { + mp_obj_t *tuple; + mp_obj_get_array_fixed_n(items[i], 3, &tuple); + int cx = mp_obj_get_int(tuple[0]); + int cy = mp_obj_get_int(tuple[1]); + int angle = mp_obj_get_int(tuple[2]) % 360; + int si = sin_table[angle] * arg_s; + int co = cos_table[angle] * arg_s; + imlib_draw_line(arg_img, cx, cy, cx + co, cy + si, arg_c, arg_thickness); + imlib_draw_circle(arg_img, cx, cy, (arg_s - 2) / 2, arg_c, arg_thickness, arg_fill); + } + } else { +#ifdef IMLIB_ENABLE_FIND_KEYPOINTS + py_kp_obj_t *kpts_obj = py_kpts_obj(args[1]); + for (int i = 0, ii = array_length(kpts_obj->kpts); i < ii; i++) { + kp_t *kp = array_at(kpts_obj->kpts, i); + int cx = kp->x; + int cy = kp->y; + int angle = kp->angle % 360; + int si = sin_table[angle] * arg_s; + int co = cos_table[angle] * arg_s; + imlib_draw_line(arg_img, cx, cy, cx + co, cy + si, arg_c, arg_thickness); + imlib_draw_circle(arg_img, cx, cy, (arg_s - 2) / 2, arg_c, arg_thickness, arg_fill); + } +#else + PY_ASSERT_TRUE_MSG(false, "Expected a list of tuples!"); +#endif // IMLIB_ENABLE_FIND_KEYPOINTS + } + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_draw_keypoints_obj, 2, py_image_draw_keypoints); + +STATIC mp_obj_t py_image_mask_rectangle(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + int arg_rx; + int arg_ry; + int arg_rw; + int arg_rh; + + if (n_args > 1) { + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 1, 4, &arg_vec); + arg_rx = mp_obj_get_int(arg_vec[0]); + arg_ry = mp_obj_get_int(arg_vec[1]); + arg_rw = mp_obj_get_int(arg_vec[2]); + arg_rh = mp_obj_get_int(arg_vec[3]); + } else { + arg_rx = arg_img->w / 4; + arg_ry = arg_img->h / 4; + arg_rw = arg_img->w / 2; + arg_rh = arg_img->h / 2; + } + + fb_alloc_mark(); + image_t temp; + temp.w = arg_img->w; + temp.h = arg_img->h; + temp.pixfmt = PIXFORMAT_BINARY; + temp.data = fb_alloc0(image_size(&temp), FB_ALLOC_NO_HINT); + + imlib_draw_rectangle(&temp, arg_rx, arg_ry, arg_rw, arg_rh, -1, 0, true); + imlib_zero(arg_img, &temp, true); + + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_mask_rectangle_obj, 1, py_image_mask_rectangle); + +STATIC mp_obj_t py_image_mask_circle(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + int arg_cx; + int arg_cy; + int arg_cr; + + if (n_args > 1) { + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 1, 3, &arg_vec); + arg_cx = mp_obj_get_int(arg_vec[0]); + arg_cy = mp_obj_get_int(arg_vec[1]); + arg_cr = mp_obj_get_int(arg_vec[2]); + } else { + arg_cx = arg_img->w / 2; + arg_cy = arg_img->h / 2; + arg_cr = IM_MIN(arg_img->w, arg_img->h) / 2; + } + + fb_alloc_mark(); + image_t temp; + temp.w = arg_img->w; + temp.h = arg_img->h; + temp.pixfmt = PIXFORMAT_BINARY; + temp.data = fb_alloc0(image_size(&temp), FB_ALLOC_NO_HINT); + + imlib_draw_circle(&temp, arg_cx, arg_cy, arg_cr, -1, 0, true); + imlib_zero(arg_img, &temp, true); + + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_mask_circle_obj, 1, py_image_mask_circle); + +STATIC mp_obj_t py_image_mask_ellipse(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + int arg_cx; + int arg_cy; + int arg_rx; + int arg_ry; + int arg_r; + + if (n_args > 1) { + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 1, 5, &arg_vec); + arg_cx = mp_obj_get_int(arg_vec[0]); + arg_cy = mp_obj_get_int(arg_vec[1]); + arg_rx = mp_obj_get_int(arg_vec[2]); + arg_ry = mp_obj_get_int(arg_vec[3]); + arg_r = mp_obj_get_int(arg_vec[4]); + } else { + arg_cx = arg_img->w / 2; + arg_cy = arg_img->h / 2; + arg_rx = arg_img->w / 2; + arg_ry = arg_img->h / 2; + arg_r = 0; + } + + fb_alloc_mark(); + image_t temp; + temp.w = arg_img->w; + temp.h = arg_img->h; + temp.pixfmt = PIXFORMAT_BINARY; + temp.data = fb_alloc0(image_size(&temp), FB_ALLOC_NO_HINT); + + imlib_draw_ellipse(&temp, arg_cx, arg_cy, arg_rx, arg_ry, arg_r, -1, 0, true); + imlib_zero(arg_img, &temp, true); + + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_mask_ellipse_obj, 1, py_image_mask_ellipse); + +#ifdef IMLIB_ENABLE_FLOOD_FILL +STATIC mp_obj_t py_image_flood_fill(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + const mp_obj_t *arg_vec; + uint offset = py_helper_consume_array(n_args, args, 1, 2, &arg_vec); + int arg_x_off = mp_obj_get_int(arg_vec[0]); + int arg_y_off = mp_obj_get_int(arg_vec[1]); + + float arg_seed_threshold = + py_helper_keyword_float(n_args, args, offset + 0, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_seed_threshold), 0.05); + PY_ASSERT_TRUE_MSG((0.0f <= arg_seed_threshold) && (arg_seed_threshold <= 1.0f), + "Error: 0.0 <= seed_threshold <= 1.0!"); + float arg_floating_threshold = + py_helper_keyword_float(n_args, args, offset + 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_floating_threshold), 0.05); + PY_ASSERT_TRUE_MSG((0.0f <= arg_floating_threshold) && (arg_floating_threshold <= 1.0f), + "Error: 0.0 <= floating_threshold <= 1.0!"); + int arg_c = + py_helper_keyword_color(arg_img, n_args, args, offset + 2, kw_args, -1); // White. + bool arg_invert = + py_helper_keyword_float(n_args, args, offset + 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + bool clear_background = + py_helper_keyword_float(n_args, args, offset + 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_clear_background), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, offset + 5, kw_args); + + fb_alloc_mark(); + imlib_flood_fill(arg_img, arg_x_off, arg_y_off, + arg_seed_threshold, arg_floating_threshold, + arg_c, arg_invert, clear_background, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_flood_fill_obj, 2, py_image_flood_fill); +#endif // IMLIB_ENABLE_FLOOD_FILL + +#ifdef IMLIB_ENABLE_BINARY_OPS +///////////////// +// Binary Methods +///////////////// + +STATIC mp_obj_t py_image_binary(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + list_t arg_thresholds; + list_init(&arg_thresholds, sizeof(color_thresholds_list_lnk_data_t)); + py_helper_arg_to_thresholds(args[1], &arg_thresholds); + + bool arg_invert = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + bool arg_zero = + py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_zero), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 4, kw_args); + bool arg_to_bitmap = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_to_bitmap), false); + bool arg_copy = + py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_copy), false); + + if (arg_to_bitmap && (!arg_copy)) { + switch(arg_img->pixfmt) { + case PIXFORMAT_GRAYSCALE: { + PY_ASSERT_TRUE_MSG((arg_img->w >= (sizeof(uint32_t)/sizeof(uint8_t))), + "Can't convert to bitmap in place!"); + break; + } + case PIXFORMAT_RGB565: { + PY_ASSERT_TRUE_MSG((arg_img->w >= (sizeof(uint32_t)/sizeof(uint16_t))), + "Can't convert to bitmap in place!"); + break; + } + default: { + break; + } + } + } + + image_t out; + out.w = arg_img->w; + out.h = arg_img->h; + out.pixfmt = arg_to_bitmap ? PIXFORMAT_BINARY : arg_img->pixfmt; + out.pixels = arg_copy ? xalloc(image_size(&out)) : arg_img->pixels; + + fb_alloc_mark(); + imlib_binary(&out, arg_img, &arg_thresholds, arg_invert, arg_zero, arg_msk); + fb_alloc_free_till_mark(); + + list_free(&arg_thresholds); + + if (arg_to_bitmap && (!arg_copy)) { + arg_img->pixfmt = PIXFORMAT_BINARY; + py_helper_update_framebuffer(&out); + } + + return py_image_from_struct(&out); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_binary_obj, 2, py_image_binary); + +STATIC mp_obj_t py_image_invert(mp_obj_t img_obj) +{ + imlib_invert(py_helper_arg_to_image_mutable(img_obj)); + return img_obj; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_invert_obj, py_image_invert); + +STATIC mp_obj_t py_image_b_and(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 2, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_b_and(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_b_and(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_msk); + } else { + imlib_b_and(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_b_and_obj, 2, py_image_b_and); + +STATIC mp_obj_t py_image_b_nand(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 2, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_b_nand(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_b_nand(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_msk); + } else { + imlib_b_nand(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_b_nand_obj, 2, py_image_b_nand); + +STATIC mp_obj_t py_image_b_or(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 2, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_b_or(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_b_or(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_msk); + } else { + imlib_b_or(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_b_or_obj, 2, py_image_b_or); + +STATIC mp_obj_t py_image_b_nor(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 2, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_b_nor(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_b_nor(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_msk); + } else { + imlib_b_nor(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_b_nor_obj, 2, py_image_b_nor); + +STATIC mp_obj_t py_image_b_xor(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 2, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_b_xor(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_b_xor(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_msk); + } else { + imlib_b_xor(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_b_xor_obj, 2, py_image_b_xor); + +STATIC mp_obj_t py_image_b_xnor(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 2, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_b_xnor(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_b_xnor(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_msk); + } else { + imlib_b_xnor(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_b_xnor_obj, 2, py_image_b_xnor); + +STATIC mp_obj_t py_image_erode(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + int arg_threshold = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), + py_helper_ksize_to_n(arg_ksize) - 1); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + imlib_erode(py_helper_arg_to_image_mutable(args[0]), arg_ksize, arg_threshold, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_erode_obj, 2, py_image_erode); + +STATIC mp_obj_t py_image_dilate(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + int arg_threshold = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), + 0); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + imlib_dilate(py_helper_arg_to_image_mutable(args[0]), arg_ksize, arg_threshold, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_dilate_obj, 2, py_image_dilate); + +STATIC mp_obj_t py_image_open(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + int arg_threshold = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 0); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + imlib_open(py_helper_arg_to_image_mutable(args[0]), arg_ksize, arg_threshold, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_open_obj, 2, py_image_open); + +STATIC mp_obj_t py_image_close(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + int arg_threshold = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 0); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + imlib_close(py_helper_arg_to_image_mutable(args[0]), arg_ksize, arg_threshold, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_close_obj, 2, py_image_close); +#endif // IMLIB_ENABLE_BINARY_OPS + +#ifdef IMLIB_ENABLE_MATH_OPS +/////////////// +// Math Methods +/////////////// + +STATIC mp_obj_t py_image_gamma_corr(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + float arg_gamma = + py_helper_keyword_float(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_gamma), 1.0f); + float arg_contrast = + py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_contrast), 1.0f); + float arg_brightness = + py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_brightness), 0.0f); + + fb_alloc_mark(); + imlib_gamma_corr(arg_img, arg_gamma, arg_contrast, arg_brightness); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_gamma_corr_obj, 1, py_image_gamma_corr); + +STATIC mp_obj_t py_image_negate(mp_obj_t img_obj) +{ + imlib_negate(py_helper_arg_to_image_mutable(img_obj)); + return img_obj; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_negate_obj, py_image_negate); + +STATIC mp_obj_t py_image_replace(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + bool arg_hmirror = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_hmirror), false); + bool arg_vflip = + py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_vflip), false); + bool arg_transpose = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_transpose), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 5, kw_args); + + if (arg_transpose) { + size_t size0 = image_size(arg_img); + int w = arg_img->w; + int h = arg_img->h; + arg_img->w = h; + arg_img->h = w; + size_t size1 = image_size(arg_img); + arg_img->w = w; + arg_img->h = h; + PY_ASSERT_TRUE_MSG(size1 <= size0, + "Unable to transpose the image because it would grow in size!"); + } + + fb_alloc_mark(); + + mp_obj_t arg_1 = (n_args > 1) ? args[1] : args[0]; + + if (MP_OBJ_IS_STR(arg_1)) { + imlib_replace(arg_img, mp_obj_str_get_str(arg_1), NULL, 0, + arg_hmirror, arg_vflip, arg_transpose, arg_msk); + } else if (MP_OBJ_IS_TYPE(arg_1, &py_image_type)) { + imlib_replace(arg_img, NULL, py_helper_arg_to_image_mutable(arg_1), 0, + arg_hmirror, arg_vflip, arg_transpose, arg_msk); + } else { + imlib_replace(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_hmirror, arg_vflip, arg_transpose, arg_msk); + } + + fb_alloc_free_till_mark(); + py_helper_update_framebuffer(arg_img); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_replace_obj, 1, py_image_replace); + +STATIC mp_obj_t py_image_add(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 2, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_add(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_add(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_msk); + } else { + imlib_add(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_add_obj, 2, py_image_add); + +STATIC mp_obj_t py_image_sub(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + bool arg_reverse = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_reverse), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_sub(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_reverse, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_sub(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_reverse, arg_msk); + } else { + imlib_sub(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_reverse, arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_sub_obj, 2, py_image_sub); + +STATIC mp_obj_t py_image_mul(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + bool arg_invert = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_mul(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_invert, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_mul(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_invert, arg_msk); + } else { + imlib_mul(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_invert, arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_mul_obj, 2, py_image_mul); + +STATIC mp_obj_t py_image_div(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + bool arg_invert = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + bool arg_mod = + py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mod), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 4, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_div(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, + arg_invert, arg_mod, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_div(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, + arg_invert, arg_mod, arg_msk); + } else { + imlib_div(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_invert, arg_mod, arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_div_obj, 2, py_image_div); + +STATIC mp_obj_t py_image_min(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 2, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_min(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_min(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_msk); + } else { + imlib_min(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_min_obj, 2, py_image_min); + +STATIC mp_obj_t py_image_max(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 2, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_max(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_max(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_msk); + } else { + imlib_max(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_max_obj, 2, py_image_max); + +STATIC mp_obj_t py_image_difference(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 2, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_difference(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_difference(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_msk); + } else { + imlib_difference(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_difference_obj, 2, py_image_difference); + +STATIC mp_obj_t py_image_blend(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + float arg_alpha = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_alpha), 128) / 256.0f; + PY_ASSERT_TRUE_MSG((0 <= arg_alpha) && (arg_alpha <= 1), "Error: 0 <= alpha <= 256!"); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(args[1])) { + imlib_blend(arg_img, mp_obj_str_get_str(args[1]), NULL, 0, arg_alpha, arg_msk); + } else if (MP_OBJ_IS_TYPE(args[1], &py_image_type)) { + imlib_blend(arg_img, NULL, py_helper_arg_to_image_mutable(args[1]), 0, arg_alpha, arg_msk); + } else { + imlib_blend(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, n_args, args, 1, NULL, 0), + arg_alpha, arg_msk); + } + + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_blend_obj, 2, py_image_blend); +#endif//IMLIB_ENABLE_MATH_OPS + +#if defined(IMLIB_ENABLE_MATH_OPS) && defined (IMLIB_ENABLE_BINARY_OPS) +STATIC mp_obj_t py_image_top_hat(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + int arg_threshold = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 0); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + imlib_top_hat(py_helper_arg_to_image_mutable(args[0]), arg_ksize, arg_threshold, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_top_hat_obj, 2, py_image_top_hat); + +STATIC mp_obj_t py_image_black_hat(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + int arg_threshold = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 0); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + imlib_black_hat(py_helper_arg_to_image_mutable(args[0]), arg_ksize, arg_threshold, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_black_hat_obj, 2, py_image_black_hat); +#endif // defined(IMLIB_ENABLE_MATH_OPS) && defined (IMLIB_ENABLE_BINARY_OPS) + +//////////////////// +// Filtering Methods +//////////////////// + +static mp_obj_t py_image_histeq(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + bool arg_adaptive = + py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_adaptive), false); + float arg_clip_limit = + py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_clip_limit), -1); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + if (arg_adaptive) imlib_clahe_histeq(arg_img, arg_clip_limit, arg_msk); else imlib_histeq(arg_img, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_histeq_obj, 1, py_image_histeq); + +#ifdef IMLIB_ENABLE_MEAN +STATIC mp_obj_t py_image_mean(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + bool arg_threshold = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = + py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + bool arg_invert = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 5, kw_args); + + fb_alloc_mark(); + imlib_mean_filter(arg_img, arg_ksize, arg_threshold, arg_offset, arg_invert, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_mean_obj, 2, py_image_mean); +#endif // IMLIB_ENABLE_MEAN + +#ifdef IMLIB_ENABLE_MEDIAN +STATIC mp_obj_t py_image_median(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + float arg_percentile = + py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_percentile), 0.5f); + PY_ASSERT_TRUE_MSG((0 <= arg_percentile) && (arg_percentile <= 1), "Error: 0 <= percentile <= 1!"); + bool arg_threshold = + py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + bool arg_invert = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 6, kw_args); + + fb_alloc_mark(); + imlib_median_filter(arg_img, arg_ksize, arg_percentile, arg_threshold, arg_offset, arg_invert, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_median_obj, 2, py_image_median); +#endif // IMLIB_ENABLE_MEDIAN + +#ifdef IMLIB_ENABLE_MODE +STATIC mp_obj_t py_image_mode(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + bool arg_threshold = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = + py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + bool arg_invert = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 5, kw_args); + + fb_alloc_mark(); + imlib_mode_filter(arg_img, arg_ksize, arg_threshold, arg_offset, arg_invert, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_mode_obj, 2, py_image_mode); +#endif // IMLIB_ENABLE_MODE + +#ifdef IMLIB_ENABLE_MIDPOINT +STATIC mp_obj_t py_image_midpoint(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + float arg_bias = + py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bias), 0.5f); + PY_ASSERT_TRUE_MSG((0 <= arg_bias) && (arg_bias <= 1), "Error: 0 <= bias <= 1!"); + bool arg_threshold = + py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + bool arg_invert = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 6, kw_args); + + fb_alloc_mark(); + imlib_midpoint_filter(arg_img, arg_ksize, arg_bias, arg_threshold, arg_offset, arg_invert, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_midpoint_obj, 2, py_image_midpoint); +#endif // IMLIB_ENABLE_MIDPOINT + +#ifdef IMLIB_ENABLE_MORPH +STATIC mp_obj_t py_image_morph(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + + int n = py_helper_ksize_to_n(arg_ksize); + + mp_obj_t *krn; + mp_obj_get_array_fixed_n(args[2], n, &krn); + + fb_alloc_mark(); + + int *arg_krn = fb_alloc(n * sizeof(int), FB_ALLOC_NO_HINT); + int arg_m = 0; + + for (int i = 0; i < n; i++) { + arg_krn[i] = mp_obj_get_int(krn[i]); + arg_m += arg_krn[i]; + } + + if (arg_m == 0) { + arg_m = 1; + } + + float arg_mul = + py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mul), 1.0f / arg_m); + float arg_add = + py_helper_keyword_float(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_add), 0.0f); + bool arg_threshold = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = + py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + bool arg_invert = + py_helper_keyword_int(n_args, args, 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 8, kw_args); + + imlib_morph(arg_img, arg_ksize, arg_krn, arg_mul, arg_add, arg_threshold, arg_offset, arg_invert, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_morph_obj, 3, py_image_morph); +#endif //IMLIB_ENABLE_MORPH + +#ifdef IMLIB_ENABLE_GAUSSIAN +STATIC mp_obj_t py_image_gaussian(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + + int k_2 = arg_ksize * 2; + int n = k_2 + 1; + + fb_alloc_mark(); + + int *pascal = fb_alloc(n * sizeof(int), FB_ALLOC_NO_HINT); + pascal[0] = 1; + + for (int i = 0; i < k_2; i++) { // Compute a row of pascal's triangle. + pascal[i + 1] = (pascal[i] * (k_2 - i)) / (i + 1); + } + + int *arg_krn = fb_alloc(n * n * sizeof(int), FB_ALLOC_NO_HINT); + int arg_m = 0; + + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + int temp = pascal[i] * pascal[j]; + arg_krn[(i * n) + j] = temp; + arg_m += temp; + } + } + + if (py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_unsharp), false)) { + arg_krn[((n/2)*n)+(n/2)] -= arg_m * 2; + arg_m = -arg_m; + } + + float arg_mul = + py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mul), 1.0f / arg_m); + float arg_add = + py_helper_keyword_float(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_add), 0.0f); + bool arg_threshold = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = + py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + bool arg_invert = + py_helper_keyword_int(n_args, args, 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 8, kw_args); + + imlib_morph(arg_img, arg_ksize, arg_krn, arg_mul, arg_add, arg_threshold, arg_offset, arg_invert, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_gaussian_obj, 2, py_image_gaussian); +#endif // IMLIB_ENABLE_GAUSSIAN + +#ifdef IMLIB_ENABLE_LAPLACIAN +STATIC mp_obj_t py_image_laplacian(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + + int k_2 = arg_ksize * 2; + int n = k_2 + 1; + + fb_alloc_mark(); + + int *pascal = fb_alloc(n * sizeof(int), FB_ALLOC_NO_HINT); + pascal[0] = 1; + + for (int i = 0; i < k_2; i++) { // Compute a row of pascal's triangle. + pascal[i + 1] = (pascal[i] * (k_2 - i)) / (i + 1); + } + + int *arg_krn = fb_alloc(n * n * sizeof(int), FB_ALLOC_NO_HINT); + int arg_m = 0; + + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + int temp = pascal[i] * pascal[j]; + arg_krn[(i * n) + j] = -temp; + arg_m += temp; + } + } + + arg_krn[((n/2)*n)+(n/2)] += arg_m; + arg_m = arg_krn[((n/2)*n)+(n/2)]; + + if (py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_sharpen), false)) { + arg_krn[((n/2)*n)+(n/2)] += arg_m; + } + + float arg_mul = + py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_mul), 1.0f / arg_m); + float arg_add = + py_helper_keyword_float(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_add), 0.0f); + bool arg_threshold = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = + py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + bool arg_invert = + py_helper_keyword_int(n_args, args, 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 8, kw_args); + + imlib_morph(arg_img, arg_ksize, arg_krn, arg_mul, arg_add, arg_threshold, arg_offset, arg_invert, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_laplacian_obj, 2, py_image_laplacian); +#endif // IMLIB_ENABLE_LAPLACIAN + +#ifdef IMLIB_ENABLE_BILATERAL +STATIC mp_obj_t py_image_bilateral(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + int arg_ksize = + py_helper_arg_to_ksize(args[1]); + float arg_color_sigma = + py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_color_sigma), 0.1); + float arg_space_sigma = + py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_space_sigma), 1); + bool arg_threshold = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), false); + int arg_offset = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_offset), 0); + bool arg_invert = + py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 7, kw_args); + + fb_alloc_mark(); + imlib_bilateral_filter(arg_img, arg_ksize, arg_color_sigma, arg_space_sigma, arg_threshold, arg_offset, arg_invert, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_bilateral_obj, 2, py_image_bilateral); +#endif // IMLIB_ENABLE_BILATERAL + +#ifdef IMLIB_ENABLE_CARTOON +STATIC mp_obj_t py_image_cartoon(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + float arg_seed_threshold = + py_helper_keyword_float(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_seed_threshold), 0.05); + PY_ASSERT_TRUE_MSG((0.0f <= arg_seed_threshold) && (arg_seed_threshold <= 1.0f), + "Error: 0.0 <= seed_threshold <= 1.0!"); + float arg_floating_threshold = + py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_floating_threshold), 0.05); + PY_ASSERT_TRUE_MSG((0.0f <= arg_floating_threshold) && (arg_floating_threshold <= 1.0f), + "Error: 0.0 <= floating_threshold <= 1.0!"); + image_t *arg_msk = + py_helper_keyword_to_image_mutable_mask(n_args, args, 3, kw_args); + + fb_alloc_mark(); + imlib_cartoon_filter(arg_img, arg_seed_threshold, arg_floating_threshold, arg_msk); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_cartoon_obj, 1, py_image_cartoon); +#endif // IMLIB_ENABLE_CARTOON + +//////////////////// +// Geometric Methods +//////////////////// + +#ifdef IMLIB_ENABLE_LINPOLAR +static mp_obj_t py_image_linpolar(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + PY_ASSERT_FALSE_MSG(arg_img->w % 2, "Width must be even!"); + PY_ASSERT_FALSE_MSG(arg_img->h % 2, "Height must be even!"); + bool arg_reverse = + py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_reverse), false); + + fb_alloc_mark(); + imlib_logpolar(arg_img, true, arg_reverse); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_linpolar_obj, 1, py_image_linpolar); +#endif // IMLIB_ENABLE_LINPOLAR + +#ifdef IMLIB_ENABLE_LOGPOLAR +static mp_obj_t py_image_logpolar(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + PY_ASSERT_FALSE_MSG(arg_img->w % 2, "Width must be even!"); + PY_ASSERT_FALSE_MSG(arg_img->h % 2, "Height must be even!"); + bool arg_reverse = + py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_reverse), false); + + fb_alloc_mark(); + imlib_logpolar(arg_img, false, arg_reverse); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_logpolar_obj, 1, py_image_logpolar); +#endif // IMLIB_ENABLE_LOGPOLAR + +#ifdef IMLIB_ENABLE_LENS_CORR +STATIC mp_obj_t py_image_lens_corr(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + PY_ASSERT_FALSE_MSG(arg_img->w % 2, "Width must be even!"); + PY_ASSERT_FALSE_MSG(arg_img->h % 2, "Height must be even!"); + float arg_strength = + py_helper_keyword_float(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_strength), 1.8f); + PY_ASSERT_TRUE_MSG(arg_strength > 0.0f, "Strength must be > 0!"); + float arg_zoom = + py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_zoom), 1.0f); + PY_ASSERT_TRUE_MSG(arg_zoom > 0.0f, "Zoom must be > 0!"); + + float arg_x_corr = + py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_corr), 0.0f); + float arg_y_corr = + py_helper_keyword_float(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_corr), 0.0f); + + fb_alloc_mark(); + imlib_lens_corr(arg_img, arg_strength, arg_zoom, arg_x_corr, arg_y_corr); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_lens_corr_obj, 1, py_image_lens_corr); +#endif // IMLIB_ENABLE_LENS_CORR + +#ifdef IMLIB_ENABLE_ROTATION_CORR +STATIC mp_obj_t py_image_rotation_corr(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = + py_helper_arg_to_image_mutable(args[0]); + float arg_x_rotation = + IM_DEG2RAD(py_helper_keyword_float(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_rotation), 0.0f)); + float arg_y_rotation = + IM_DEG2RAD(py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_rotation), 0.0f)); + float arg_z_rotation = + IM_DEG2RAD(py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_z_rotation), 0.0f)); + float arg_x_translation = + py_helper_keyword_float(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_translation), 0.0f); + float arg_y_translation = + py_helper_keyword_float(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_translation), 0.0f); + float arg_zoom = + py_helper_keyword_float(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_zoom), 1.0f); + PY_ASSERT_TRUE_MSG(arg_zoom > 0.0f, "Zoom must be > 0!"); + float arg_fov = + IM_DEG2RAD(py_helper_keyword_float(n_args, args, 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fov), 60.0f)); + PY_ASSERT_TRUE_MSG((0.0f < arg_fov) && (arg_fov < 180.0f), "FOV must be > 0 and < 180!"); + float *arg_corners = py_helper_keyword_corner_array(n_args, args, 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_corners)); + + fb_alloc_mark(); + imlib_rotation_corr(arg_img, + arg_x_rotation, arg_y_rotation, arg_z_rotation, + arg_x_translation, arg_y_translation, + arg_zoom, arg_fov, arg_corners); + fb_alloc_free_till_mark(); + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_rotation_corr_obj, 1, py_image_rotation_corr); +#endif // IMLIB_ENABLE_ROTATION_CORR + +////////////// +// Get Methods +////////////// + +#ifdef IMLIB_ENABLE_GET_SIMILARITY +// Similarity Object // +#define py_similarity_obj_size 4 +typedef struct py_similarity_obj { + mp_obj_base_t base; + mp_obj_t avg, std, min, max; +} py_similarity_obj_t; + +static void py_similarity_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_similarity_obj_t *self = self_in; + mp_printf(print, + "{\"mean\":%f, \"stdev\":%f, \"min\":%f, \"max\":%f}", + (double) mp_obj_get_float(self->avg), + (double) mp_obj_get_float(self->std), + (double) mp_obj_get_float(self->min), + (double) mp_obj_get_float(self->max)); +} + +static mp_obj_t py_similarity_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_similarity_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_similarity_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->avg) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_similarity_obj_size, index, false)) { + case 0: return self->avg; + case 1: return self->std; + case 2: return self->min; + case 3: return self->max; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_similarity_mean(mp_obj_t self_in) { return ((py_similarity_obj_t *) self_in)->avg; } +mp_obj_t py_similarity_stdev(mp_obj_t self_in) { return ((py_similarity_obj_t *) self_in)->std; } +mp_obj_t py_similarity_min(mp_obj_t self_in) { return ((py_similarity_obj_t *) self_in)->min; } +mp_obj_t py_similarity_max(mp_obj_t self_in) { return ((py_similarity_obj_t *) self_in)->max; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_similarity_mean_obj, py_similarity_mean); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_similarity_stdev_obj, py_similarity_stdev); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_similarity_min_obj, py_similarity_min); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_similarity_max_obj, py_similarity_max); + +STATIC const mp_rom_map_elem_t py_similarity_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_mean), MP_ROM_PTR(&py_similarity_mean_obj) }, + { MP_ROM_QSTR(MP_QSTR_stdev), MP_ROM_PTR(&py_similarity_stdev_obj) }, + { MP_ROM_QSTR(MP_QSTR_min), MP_ROM_PTR(&py_similarity_min_obj) }, + { MP_ROM_QSTR(MP_QSTR_max), MP_ROM_PTR(&py_similarity_max_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_similarity_locals_dict, py_similarity_locals_dict_table); + +static const mp_obj_type_t py_similarity_type = { + { &mp_type_type }, + .name = MP_QSTR_similarity, + .print = py_similarity_print, + .subscr = py_similarity_subscr, + .locals_dict = (mp_obj_t) &py_similarity_locals_dict +}; + +static mp_obj_t py_image_get_similarity(mp_obj_t img_obj, mp_obj_t other_obj) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(img_obj); + float avg, std, min, max; + + fb_alloc_mark(); + + if (MP_OBJ_IS_STR(other_obj)) { + imlib_get_similarity(arg_img, mp_obj_str_get_str(other_obj), NULL, 0, &avg, &std, &min, &max); + } else if (MP_OBJ_IS_TYPE(other_obj, &py_image_type)) { + imlib_get_similarity(arg_img, NULL, py_helper_arg_to_image_mutable(other_obj), 0, &avg, &std, &min, &max); + } else { + imlib_get_similarity(arg_img, NULL, NULL, + py_helper_keyword_color(arg_img, 1, &other_obj, 0, NULL, 0), + &avg, &std, &min, &max); + } + + fb_alloc_free_till_mark(); + + py_similarity_obj_t *o = m_new_obj(py_similarity_obj_t); + o->base.type = &py_similarity_type; + o->avg = mp_obj_new_float(avg); + o->std = mp_obj_new_float(std); + o->min = mp_obj_new_float(min); + o->max = mp_obj_new_float(max); + return o; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_image_get_similarity_obj, py_image_get_similarity); +#endif // IMLIB_ENABLE_GET_SIMILARITY + +// Statistics Object // +#define py_statistics_obj_size 24 +typedef struct py_statistics_obj { + mp_obj_base_t base; + pixformat_t pixfmt; + mp_obj_t LMean, LMedian, LMode, LSTDev, LMin, LMax, LLQ, LUQ, + AMean, AMedian, AMode, ASTDev, AMin, AMax, ALQ, AUQ, + BMean, BMedian, BMode, BSTDev, BMin, BMax, BLQ, BUQ; +} py_statistics_obj_t; + +static void py_statistics_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_statistics_obj_t *self = self_in; + switch (self->pixfmt) { + case PIXFORMAT_BINARY: { + mp_printf(print, "{\"mean\":%d, \"median\":%d, \"mode\":%d, \"stdev\":%d, \"min\":%d, \"max\":%d, \"lq\":%d, \"uq\":%d}", + mp_obj_get_int(self->LMean), + mp_obj_get_int(self->LMedian), + mp_obj_get_int(self->LMode), + mp_obj_get_int(self->LSTDev), + mp_obj_get_int(self->LMin), + mp_obj_get_int(self->LMax), + mp_obj_get_int(self->LLQ), + mp_obj_get_int(self->LUQ)); + break; + } + case PIXFORMAT_GRAYSCALE: { + mp_printf(print, "{\"mean\":%d, \"median\":%d, \"mode\":%d, \"stdev\":%d, \"min\":%d, \"max\":%d, \"lq\":%d, \"uq\":%d}", + mp_obj_get_int(self->LMean), + mp_obj_get_int(self->LMedian), + mp_obj_get_int(self->LMode), + mp_obj_get_int(self->LSTDev), + mp_obj_get_int(self->LMin), + mp_obj_get_int(self->LMax), + mp_obj_get_int(self->LLQ), + mp_obj_get_int(self->LUQ)); + break; + } + case PIXFORMAT_RGB565: { + mp_printf(print, "{\"l_mean\":%d, \"l_median\":%d, \"l_mode\":%d, \"l_stdev\":%d, \"l_min\":%d, \"l_max\":%d, \"l_lq\":%d, \"l_uq\":%d," + " \"a_mean\":%d, \"a_median\":%d, \"a_mode\":%d, \"a_stdev\":%d, \"a_min\":%d, \"a_max\":%d, \"a_lq\":%d, \"a_uq\":%d," + " \"b_mean\":%d, \"b_median\":%d, \"b_mode\":%d, \"b_stdev\":%d, \"b_min\":%d, \"b_max\":%d, \"b_lq\":%d, \"b_uq\":%d}", + mp_obj_get_int(self->LMean), + mp_obj_get_int(self->LMedian), + mp_obj_get_int(self->LMode), + mp_obj_get_int(self->LSTDev), + mp_obj_get_int(self->LMin), + mp_obj_get_int(self->LMax), + mp_obj_get_int(self->LLQ), + mp_obj_get_int(self->LUQ), + mp_obj_get_int(self->AMean), + mp_obj_get_int(self->AMedian), + mp_obj_get_int(self->AMode), + mp_obj_get_int(self->ASTDev), + mp_obj_get_int(self->AMin), + mp_obj_get_int(self->AMax), + mp_obj_get_int(self->ALQ), + mp_obj_get_int(self->AUQ), + mp_obj_get_int(self->BMean), + mp_obj_get_int(self->BMedian), + mp_obj_get_int(self->BMode), + mp_obj_get_int(self->BSTDev), + mp_obj_get_int(self->BMin), + mp_obj_get_int(self->BMax), + mp_obj_get_int(self->BLQ), + mp_obj_get_int(self->BUQ)); + break; + } + default: { + mp_printf(print, "{}"); + break; + } + } +} + +static mp_obj_t py_statistics_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_statistics_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_statistics_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->LMean) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_statistics_obj_size, index, false)) { + case 0: return self->LMean; + case 1: return self->LMedian; + case 2: return self->LMode; + case 3: return self->LSTDev; + case 4: return self->LMin; + case 5: return self->LMax; + case 6: return self->LLQ; + case 7: return self->LUQ; + case 8: return self->AMean; + case 9: return self->AMedian; + case 10: return self->AMode; + case 11: return self->ASTDev; + case 12: return self->AMin; + case 13: return self->AMax; + case 14: return self->ALQ; + case 15: return self->AUQ; + case 16: return self->BMean; + case 17: return self->BMedian; + case 18: return self->BMode; + case 19: return self->BSTDev; + case 20: return self->BMin; + case 21: return self->BMax; + case 22: return self->BLQ; + case 23: return self->BUQ; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_statistics_mean(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LMean; } +mp_obj_t py_statistics_median(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LMedian; } +mp_obj_t py_statistics_mode(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LMode; } +mp_obj_t py_statistics_stdev(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LSTDev; } +mp_obj_t py_statistics_min(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LMin; } +mp_obj_t py_statistics_max(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LMax; } +mp_obj_t py_statistics_lq(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LLQ; } +mp_obj_t py_statistics_uq(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LUQ; } +mp_obj_t py_statistics_l_mean(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LMean; } +mp_obj_t py_statistics_l_median(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LMedian; } +mp_obj_t py_statistics_l_mode(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LMode; } +mp_obj_t py_statistics_l_stdev(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LSTDev; } +mp_obj_t py_statistics_l_min(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LMin; } +mp_obj_t py_statistics_l_max(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LMax; } +mp_obj_t py_statistics_l_lq(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LLQ; } +mp_obj_t py_statistics_l_uq(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->LUQ; } +mp_obj_t py_statistics_a_mean(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->AMean; } +mp_obj_t py_statistics_a_median(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->AMedian; } +mp_obj_t py_statistics_a_mode(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->AMode; } +mp_obj_t py_statistics_a_stdev(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->ASTDev; } +mp_obj_t py_statistics_a_min(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->AMin; } +mp_obj_t py_statistics_a_max(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->AMax; } +mp_obj_t py_statistics_a_lq(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->ALQ; } +mp_obj_t py_statistics_a_uq(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->AUQ; } +mp_obj_t py_statistics_b_mean(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->BMean; } +mp_obj_t py_statistics_b_median(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->BMedian; } +mp_obj_t py_statistics_b_mode(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->BMode; } +mp_obj_t py_statistics_b_stdev(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->BSTDev; } +mp_obj_t py_statistics_b_min(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->BMin; } +mp_obj_t py_statistics_b_max(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->BMax; } +mp_obj_t py_statistics_b_lq(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->BLQ; } +mp_obj_t py_statistics_b_uq(mp_obj_t self_in) { return ((py_statistics_obj_t *) self_in)->BUQ; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_mean_obj, py_statistics_mean); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_median_obj, py_statistics_median); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_mode_obj, py_statistics_mode); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_stdev_obj, py_statistics_stdev); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_min_obj, py_statistics_min); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_max_obj, py_statistics_max); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_lq_obj, py_statistics_lq); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_uq_obj, py_statistics_uq); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_mean_obj, py_statistics_l_mean); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_median_obj, py_statistics_l_median); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_mode_obj, py_statistics_l_mode); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_stdev_obj, py_statistics_l_stdev); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_min_obj, py_statistics_l_min); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_max_obj, py_statistics_l_max); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_lq_obj, py_statistics_l_lq); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_l_uq_obj, py_statistics_l_uq); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_mean_obj, py_statistics_a_mean); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_median_obj, py_statistics_a_median); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_mode_obj, py_statistics_a_mode); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_stdev_obj, py_statistics_a_stdev); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_min_obj, py_statistics_a_min); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_max_obj, py_statistics_a_max); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_lq_obj, py_statistics_a_lq); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_a_uq_obj, py_statistics_a_uq); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_mean_obj, py_statistics_b_mean); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_median_obj, py_statistics_b_median); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_mode_obj, py_statistics_b_mode); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_stdev_obj, py_statistics_b_stdev); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_min_obj, py_statistics_b_min); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_max_obj, py_statistics_b_max); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_lq_obj, py_statistics_b_lq); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_statistics_b_uq_obj, py_statistics_b_uq); + +STATIC const mp_rom_map_elem_t py_statistics_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_mean), MP_ROM_PTR(&py_statistics_mean_obj) }, + { MP_ROM_QSTR(MP_QSTR_median), MP_ROM_PTR(&py_statistics_median_obj) }, + { MP_ROM_QSTR(MP_QSTR_mode), MP_ROM_PTR(&py_statistics_mode_obj) }, + { MP_ROM_QSTR(MP_QSTR_stdev), MP_ROM_PTR(&py_statistics_stdev_obj) }, + { MP_ROM_QSTR(MP_QSTR_min), MP_ROM_PTR(&py_statistics_min_obj) }, + { MP_ROM_QSTR(MP_QSTR_max), MP_ROM_PTR(&py_statistics_max_obj) }, + { MP_ROM_QSTR(MP_QSTR_lq), MP_ROM_PTR(&py_statistics_lq_obj) }, + { MP_ROM_QSTR(MP_QSTR_uq), MP_ROM_PTR(&py_statistics_uq_obj) }, + { MP_ROM_QSTR(MP_QSTR_l_mean), MP_ROM_PTR(&py_statistics_l_mean_obj) }, + { MP_ROM_QSTR(MP_QSTR_l_median), MP_ROM_PTR(&py_statistics_l_median_obj) }, + { MP_ROM_QSTR(MP_QSTR_l_mode), MP_ROM_PTR(&py_statistics_l_mode_obj) }, + { MP_ROM_QSTR(MP_QSTR_l_stdev), MP_ROM_PTR(&py_statistics_l_stdev_obj) }, + { MP_ROM_QSTR(MP_QSTR_l_min), MP_ROM_PTR(&py_statistics_l_min_obj) }, + { MP_ROM_QSTR(MP_QSTR_l_max), MP_ROM_PTR(&py_statistics_l_max_obj) }, + { MP_ROM_QSTR(MP_QSTR_l_lq), MP_ROM_PTR(&py_statistics_l_lq_obj) }, + { MP_ROM_QSTR(MP_QSTR_l_uq), MP_ROM_PTR(&py_statistics_l_uq_obj) }, + { MP_ROM_QSTR(MP_QSTR_a_mean), MP_ROM_PTR(&py_statistics_a_mean_obj) }, + { MP_ROM_QSTR(MP_QSTR_a_median), MP_ROM_PTR(&py_statistics_a_median_obj) }, + { MP_ROM_QSTR(MP_QSTR_a_mode), MP_ROM_PTR(&py_statistics_a_mode_obj) }, + { MP_ROM_QSTR(MP_QSTR_a_stdev), MP_ROM_PTR(&py_statistics_a_stdev_obj) }, + { MP_ROM_QSTR(MP_QSTR_a_min), MP_ROM_PTR(&py_statistics_a_min_obj) }, + { MP_ROM_QSTR(MP_QSTR_a_max), MP_ROM_PTR(&py_statistics_a_max_obj) }, + { MP_ROM_QSTR(MP_QSTR_a_lq), MP_ROM_PTR(&py_statistics_a_lq_obj) }, + { MP_ROM_QSTR(MP_QSTR_a_uq), MP_ROM_PTR(&py_statistics_a_uq_obj) }, + { MP_ROM_QSTR(MP_QSTR_b_mean), MP_ROM_PTR(&py_statistics_b_mean_obj) }, + { MP_ROM_QSTR(MP_QSTR_b_median), MP_ROM_PTR(&py_statistics_b_median_obj) }, + { MP_ROM_QSTR(MP_QSTR_b_mode), MP_ROM_PTR(&py_statistics_b_mode_obj) }, + { MP_ROM_QSTR(MP_QSTR_b_stdev), MP_ROM_PTR(&py_statistics_b_stdev_obj) }, + { MP_ROM_QSTR(MP_QSTR_b_min), MP_ROM_PTR(&py_statistics_b_min_obj) }, + { MP_ROM_QSTR(MP_QSTR_b_max), MP_ROM_PTR(&py_statistics_b_max_obj) }, + { MP_ROM_QSTR(MP_QSTR_b_lq), MP_ROM_PTR(&py_statistics_b_lq_obj) }, + { MP_ROM_QSTR(MP_QSTR_b_uq), MP_ROM_PTR(&py_statistics_b_uq_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_statistics_locals_dict, py_statistics_locals_dict_table); + +static const mp_obj_type_t py_statistics_type = { + { &mp_type_type }, + .name = MP_QSTR_statistics, + .print = py_statistics_print, + .subscr = py_statistics_subscr, + .locals_dict = (mp_obj_t) &py_statistics_locals_dict +}; + +// Percentile Object // +#define py_percentile_obj_size 3 +typedef struct py_percentile_obj { + mp_obj_base_t base; + pixformat_t pixfmt; + mp_obj_t LValue, AValue, BValue; +} py_percentile_obj_t; + +static void py_percentile_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_percentile_obj_t *self = self_in; + switch (self->pixfmt) { + case PIXFORMAT_BINARY: { + mp_printf(print, "{\"value\":%d}", + mp_obj_get_int(self->LValue)); + break; + } + case PIXFORMAT_GRAYSCALE: { + mp_printf(print, "{\"value\":%d}", + mp_obj_get_int(self->LValue)); + break; + } + case PIXFORMAT_RGB565: { + mp_printf(print, "{\"l_value:%d\", \"a_value\":%d, \"b_value\":%d}", + mp_obj_get_int(self->LValue), + mp_obj_get_int(self->AValue), + mp_obj_get_int(self->BValue)); + break; + } + default: { + mp_printf(print, "{}"); + break; + } + } +} + +static mp_obj_t py_percentile_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_percentile_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_percentile_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->LValue) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_percentile_obj_size, index, false)) { + case 0: return self->LValue; + case 1: return self->AValue; + case 2: return self->BValue; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_percentile_value(mp_obj_t self_in) { return ((py_percentile_obj_t *) self_in)->LValue; } +mp_obj_t py_percentile_l_value(mp_obj_t self_in) { return ((py_percentile_obj_t *) self_in)->LValue; } +mp_obj_t py_percentile_a_value(mp_obj_t self_in) { return ((py_percentile_obj_t *) self_in)->AValue; } +mp_obj_t py_percentile_b_value(mp_obj_t self_in) { return ((py_percentile_obj_t *) self_in)->BValue; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_percentile_value_obj, py_percentile_value); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_percentile_l_value_obj, py_percentile_l_value); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_percentile_a_value_obj, py_percentile_a_value); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_percentile_b_value_obj, py_percentile_b_value); + +STATIC const mp_rom_map_elem_t py_percentile_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&py_percentile_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_l_value), MP_ROM_PTR(&py_percentile_l_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_a_value), MP_ROM_PTR(&py_percentile_a_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_b_value), MP_ROM_PTR(&py_percentile_b_value_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_percentile_locals_dict, py_percentile_locals_dict_table); + +static const mp_obj_type_t py_percentile_type = { + { &mp_type_type }, + .name = MP_QSTR_percentile, + .print = py_percentile_print, + .subscr = py_percentile_subscr, + .locals_dict = (mp_obj_t) &py_percentile_locals_dict +}; + +// Threshold Object // +#define py_threshold_obj_size 3 +typedef struct py_threshold_obj { + mp_obj_base_t base; + pixformat_t pixfmt; + mp_obj_t LValue, AValue, BValue; +} py_threshold_obj_t; + +static void py_threshold_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_threshold_obj_t *self = self_in; + switch (self->pixfmt) { + case PIXFORMAT_BINARY: { + mp_printf(print, "{\"value\":%d}", + mp_obj_get_int(self->LValue)); + break; + } + case PIXFORMAT_GRAYSCALE: { + mp_printf(print, "{\"value\":%d}", + mp_obj_get_int(self->LValue)); + break; + } + case PIXFORMAT_RGB565: { + mp_printf(print, "{\"l_value\":%d, \"a_value\":%d, \"b_value\":%d}", + mp_obj_get_int(self->LValue), + mp_obj_get_int(self->AValue), + mp_obj_get_int(self->BValue)); + break; + } + default: { + mp_printf(print, "{}"); + break; + } + } +} + +static mp_obj_t py_threshold_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_threshold_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_threshold_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->LValue) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_threshold_obj_size, index, false)) { + case 0: return self->LValue; + case 1: return self->AValue; + case 2: return self->BValue; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_threshold_value(mp_obj_t self_in) { return ((py_threshold_obj_t *) self_in)->LValue; } +mp_obj_t py_threshold_l_value(mp_obj_t self_in) { return ((py_threshold_obj_t *) self_in)->LValue; } +mp_obj_t py_threshold_a_value(mp_obj_t self_in) { return ((py_threshold_obj_t *) self_in)->AValue; } +mp_obj_t py_threshold_b_value(mp_obj_t self_in) { return ((py_threshold_obj_t *) self_in)->BValue; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_threshold_value_obj, py_threshold_value); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_threshold_l_value_obj, py_threshold_l_value); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_threshold_a_value_obj, py_threshold_a_value); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_threshold_b_value_obj, py_threshold_b_value); + +STATIC const mp_rom_map_elem_t py_threshold_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&py_threshold_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_l_value), MP_ROM_PTR(&py_threshold_l_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_a_value), MP_ROM_PTR(&py_threshold_a_value_obj) }, + { MP_ROM_QSTR(MP_QSTR_b_value), MP_ROM_PTR(&py_threshold_b_value_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_threshold_locals_dict, py_threshold_locals_dict_table); + +static const mp_obj_type_t py_threshold_type = { + { &mp_type_type }, + .name = MP_QSTR_threshold, + .print = py_threshold_print, + .subscr = py_threshold_subscr, + .locals_dict = (mp_obj_t) &py_threshold_locals_dict +}; + +// Histogram Object // +#define py_histogram_obj_size 3 +typedef struct py_histogram_obj { + mp_obj_base_t base; + pixformat_t pixfmt; + mp_obj_t LBins, ABins, BBins; +} py_histogram_obj_t; + +static void py_histogram_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_histogram_obj_t *self = self_in; + switch (self->pixfmt) { + case PIXFORMAT_BINARY: { + mp_printf(print, "{\"bins\":"); + mp_obj_print_helper(print, self->LBins, kind); + mp_printf(print, "}"); + break; + } + case PIXFORMAT_GRAYSCALE: { + mp_printf(print, "{\"bins\":"); + mp_obj_print_helper(print, self->LBins, kind); + mp_printf(print, "}"); + break; + } + case PIXFORMAT_RGB565: { + mp_printf(print, "{\"l_bins\":"); + mp_obj_print_helper(print, self->LBins, kind); + mp_printf(print, ", \"a_bins\":"); + mp_obj_print_helper(print, self->ABins, kind); + mp_printf(print, ", \"b_bins\":"); + mp_obj_print_helper(print, self->BBins, kind); + mp_printf(print, "}"); + break; + } + default: { + mp_printf(print, "{}"); + break; + } + } +} + +static mp_obj_t py_histogram_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_histogram_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_histogram_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->LBins) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_histogram_obj_size, index, false)) { + case 0: return self->LBins; + case 1: return self->ABins; + case 2: return self->BBins; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_histogram_bins(mp_obj_t self_in) { return ((py_histogram_obj_t *) self_in)->LBins; } +mp_obj_t py_histogram_l_bins(mp_obj_t self_in) { return ((py_histogram_obj_t *) self_in)->LBins; } +mp_obj_t py_histogram_a_bins(mp_obj_t self_in) { return ((py_histogram_obj_t *) self_in)->ABins; } +mp_obj_t py_histogram_b_bins(mp_obj_t self_in) { return ((py_histogram_obj_t *) self_in)->BBins; } + +mp_obj_t py_histogram_get_percentile(mp_obj_t self_in, mp_obj_t percentile) +{ + histogram_t hist; + hist.LBinCount = ((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->LBins)->len; + hist.ABinCount = ((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->ABins)->len; + hist.BBinCount = ((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->BBins)->len; + fb_alloc_mark(); + hist.LBins = fb_alloc(hist.LBinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.ABins = fb_alloc(hist.ABinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.BBins = fb_alloc(hist.BBinCount * sizeof(float), FB_ALLOC_NO_HINT); + + for (int i = 0; i < hist.LBinCount; i++) { + hist.LBins[i] = mp_obj_get_float(((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->LBins)->items[i]); + } + + for (int i = 0; i < hist.ABinCount; i++) { + hist.ABins[i] = mp_obj_get_float(((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->ABins)->items[i]); + } + + for (int i = 0; i < hist.BBinCount; i++) { + hist.BBins[i] = mp_obj_get_float(((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->BBins)->items[i]); + } + + percentile_t p; + imlib_get_percentile(&p, ((py_histogram_obj_t *) self_in)->pixfmt, &hist, mp_obj_get_float(percentile)); + fb_alloc_free_till_mark(); + + py_percentile_obj_t *o = m_new_obj(py_percentile_obj_t); + o->base.type = &py_percentile_type; + o->pixfmt = ((py_histogram_obj_t *) self_in)->pixfmt; + + o->LValue = mp_obj_new_int(p.LValue); + o->AValue = mp_obj_new_int(p.AValue); + o->BValue = mp_obj_new_int(p.BValue); + + return o; +} + +mp_obj_t py_histogram_get_threshold(mp_obj_t self_in) +{ + histogram_t hist; + hist.LBinCount = ((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->LBins)->len; + hist.ABinCount = ((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->ABins)->len; + hist.BBinCount = ((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->BBins)->len; + fb_alloc_mark(); + hist.LBins = fb_alloc(hist.LBinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.ABins = fb_alloc(hist.ABinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.BBins = fb_alloc(hist.BBinCount * sizeof(float), FB_ALLOC_NO_HINT); + + for (int i = 0; i < hist.LBinCount; i++) { + hist.LBins[i] = mp_obj_get_float(((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->LBins)->items[i]); + } + + for (int i = 0; i < hist.ABinCount; i++) { + hist.ABins[i] = mp_obj_get_float(((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->ABins)->items[i]); + } + + for (int i = 0; i < hist.BBinCount; i++) { + hist.BBins[i] = mp_obj_get_float(((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->BBins)->items[i]); + } + + threshold_t t; + imlib_get_threshold(&t, ((py_histogram_obj_t *) self_in)->pixfmt, &hist); + fb_alloc_free_till_mark(); + + py_threshold_obj_t *o = m_new_obj(py_threshold_obj_t); + o->base.type = &py_threshold_type; + o->pixfmt = ((py_threshold_obj_t *) self_in)->pixfmt; + + o->LValue = mp_obj_new_int(t.LValue); + o->AValue = mp_obj_new_int(t.AValue); + o->BValue = mp_obj_new_int(t.BValue); + + return o; +} + +mp_obj_t py_histogram_get_statistics(mp_obj_t self_in) +{ + histogram_t hist; + hist.LBinCount = ((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->LBins)->len; + hist.ABinCount = ((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->ABins)->len; + hist.BBinCount = ((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->BBins)->len; + fb_alloc_mark(); + hist.LBins = fb_alloc(hist.LBinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.ABins = fb_alloc(hist.ABinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.BBins = fb_alloc(hist.BBinCount * sizeof(float), FB_ALLOC_NO_HINT); + + for (int i = 0; i < hist.LBinCount; i++) { + hist.LBins[i] = mp_obj_get_float(((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->LBins)->items[i]); + } + + for (int i = 0; i < hist.ABinCount; i++) { + hist.ABins[i] = mp_obj_get_float(((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->ABins)->items[i]); + } + + for (int i = 0; i < hist.BBinCount; i++) { + hist.BBins[i] = mp_obj_get_float(((mp_obj_list_t *) ((py_histogram_obj_t *) self_in)->BBins)->items[i]); + } + + statistics_t stats; + imlib_get_statistics(&stats, ((py_histogram_obj_t *) self_in)->pixfmt, &hist); + fb_alloc_free_till_mark(); + + py_statistics_obj_t *o = m_new_obj(py_statistics_obj_t); + o->base.type = &py_statistics_type; + o->pixfmt = ((py_histogram_obj_t *) self_in)->pixfmt; + + o->LMean = mp_obj_new_int(stats.LMean); + o->LMedian = mp_obj_new_int(stats.LMedian); + o->LMode= mp_obj_new_int(stats.LMode); + o->LSTDev = mp_obj_new_int(stats.LSTDev); + o->LMin = mp_obj_new_int(stats.LMin); + o->LMax = mp_obj_new_int(stats.LMax); + o->LLQ = mp_obj_new_int(stats.LLQ); + o->LUQ = mp_obj_new_int(stats.LUQ); + o->AMean = mp_obj_new_int(stats.AMean); + o->AMedian = mp_obj_new_int(stats.AMedian); + o->AMode= mp_obj_new_int(stats.AMode); + o->ASTDev = mp_obj_new_int(stats.ASTDev); + o->AMin = mp_obj_new_int(stats.AMin); + o->AMax = mp_obj_new_int(stats.AMax); + o->ALQ = mp_obj_new_int(stats.ALQ); + o->AUQ = mp_obj_new_int(stats.AUQ); + o->BMean = mp_obj_new_int(stats.BMean); + o->BMedian = mp_obj_new_int(stats.BMedian); + o->BMode= mp_obj_new_int(stats.BMode); + o->BSTDev = mp_obj_new_int(stats.BSTDev); + o->BMin = mp_obj_new_int(stats.BMin); + o->BMax = mp_obj_new_int(stats.BMax); + o->BLQ = mp_obj_new_int(stats.BLQ); + o->BUQ = mp_obj_new_int(stats.BUQ); + + return o; +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_bins_obj, py_histogram_bins); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_l_bins_obj, py_histogram_l_bins); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_a_bins_obj, py_histogram_a_bins); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_b_bins_obj, py_histogram_b_bins); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_histogram_get_percentile_obj, py_histogram_get_percentile); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_get_threshold_obj, py_histogram_get_threshold); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_histogram_get_statistics_obj, py_histogram_get_statistics); + +STATIC const mp_rom_map_elem_t py_histogram_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_bins), MP_ROM_PTR(&py_histogram_bins_obj) }, + { MP_ROM_QSTR(MP_QSTR_l_bins), MP_ROM_PTR(&py_histogram_l_bins_obj) }, + { MP_ROM_QSTR(MP_QSTR_a_bins), MP_ROM_PTR(&py_histogram_a_bins_obj) }, + { MP_ROM_QSTR(MP_QSTR_b_bins), MP_ROM_PTR(&py_histogram_b_bins_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_percentile), MP_ROM_PTR(&py_histogram_get_percentile_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_threshold), MP_ROM_PTR(&py_histogram_get_threshold_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_stats), MP_ROM_PTR(&py_histogram_get_statistics_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_statistics), MP_ROM_PTR(&py_histogram_get_statistics_obj) }, + { MP_ROM_QSTR(MP_QSTR_statistics), MP_ROM_PTR(&py_histogram_get_statistics_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_histogram_locals_dict, py_histogram_locals_dict_table); + +static const mp_obj_type_t py_histogram_type = { + { &mp_type_type }, + .name = MP_QSTR_histogram, + .print = py_histogram_print, + .subscr = py_histogram_subscr, + .locals_dict = (mp_obj_t) &py_histogram_locals_dict +}; + +static mp_obj_t py_image_get_histogram(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + list_t thresholds; + list_init(&thresholds, sizeof(color_thresholds_list_lnk_data_t)); + py_helper_keyword_thresholds(n_args, args, 1, kw_args, &thresholds); + bool invert = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + image_t *other = py_helper_keyword_to_image_mutable(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_difference), NULL); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 3, kw_args, &roi); + + histogram_t hist; + switch (arg_img->pixfmt) { + case PIXFORMAT_BINARY: { + int bins = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bins), + (COLOR_BINARY_MAX-COLOR_BINARY_MIN+1)); + PY_ASSERT_TRUE_MSG(bins >= 2, "bins must be >= 2"); + hist.LBinCount = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_l_bins), bins); + PY_ASSERT_TRUE_MSG(hist.LBinCount >= 2, "l_bins must be >= 2"); + hist.ABinCount = 0; + hist.BBinCount = 0; + fb_alloc_mark(); + hist.LBins = fb_alloc(hist.LBinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.ABins = NULL; + hist.BBins = NULL; + imlib_get_histogram(&hist, arg_img, &roi, &thresholds, invert, other); + list_free(&thresholds); + break; + } + case PIXFORMAT_GRAYSCALE: { + int bins = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bins), + (COLOR_GRAYSCALE_MAX-COLOR_GRAYSCALE_MIN+1)); + PY_ASSERT_TRUE_MSG(bins >= 2, "bins must be >= 2"); + hist.LBinCount = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_l_bins), bins); + PY_ASSERT_TRUE_MSG(hist.LBinCount >= 2, "l_bins must be >= 2"); + hist.ABinCount = 0; + hist.BBinCount = 0; + fb_alloc_mark(); + hist.LBins = fb_alloc(hist.LBinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.ABins = NULL; + hist.BBins = NULL; + imlib_get_histogram(&hist, arg_img, &roi, &thresholds, invert, other); + list_free(&thresholds); + break; + } + case PIXFORMAT_RGB565: { + int l_bins = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bins), + (COLOR_L_MAX-COLOR_L_MIN+1)); + PY_ASSERT_TRUE_MSG(l_bins >= 2, "bins must be >= 2"); + hist.LBinCount = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_l_bins), l_bins); + PY_ASSERT_TRUE_MSG(hist.LBinCount >= 2, "l_bins must be >= 2"); + int a_bins = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bins), + (COLOR_A_MAX-COLOR_A_MIN+1)); + PY_ASSERT_TRUE_MSG(a_bins >= 2, "bins must be >= 2"); + hist.ABinCount = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_a_bins), a_bins); + PY_ASSERT_TRUE_MSG(hist.ABinCount >= 2, "a_bins must be >= 2"); + int b_bins = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bins), + (COLOR_B_MAX-COLOR_B_MIN+1)); + PY_ASSERT_TRUE_MSG(b_bins >= 2, "bins must be >= 2"); + hist.BBinCount = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_b_bins), b_bins); + PY_ASSERT_TRUE_MSG(hist.BBinCount >= 2, "b_bins must be >= 2"); + fb_alloc_mark(); + hist.LBins = fb_alloc(hist.LBinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.ABins = fb_alloc(hist.ABinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.BBins = fb_alloc(hist.BBinCount * sizeof(float), FB_ALLOC_NO_HINT); + imlib_get_histogram(&hist, arg_img, &roi, &thresholds, invert, other); + list_free(&thresholds); + break; + } + default: { + return MP_OBJ_NULL; + } + } + + py_histogram_obj_t *o = m_new_obj(py_histogram_obj_t); + o->base.type = &py_histogram_type; + o->pixfmt = arg_img->pixfmt; + + o->LBins = mp_obj_new_list(hist.LBinCount, NULL); + o->ABins = mp_obj_new_list(hist.ABinCount, NULL); + o->BBins = mp_obj_new_list(hist.BBinCount, NULL); + + for (int i = 0; i < hist.LBinCount; i++) { + ((mp_obj_list_t *) o->LBins)->items[i] = mp_obj_new_float(hist.LBins[i]); + } + + for (int i = 0; i < hist.ABinCount; i++) { + ((mp_obj_list_t *) o->ABins)->items[i] = mp_obj_new_float(hist.ABins[i]); + } + + for (int i = 0; i < hist.BBinCount; i++) { + ((mp_obj_list_t *) o->BBins)->items[i] = mp_obj_new_float(hist.BBins[i]); + } + + fb_alloc_free_till_mark(); + + return o; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_get_histogram_obj, 1, py_image_get_histogram); + +static mp_obj_t py_image_get_statistics(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + list_t thresholds; + list_init(&thresholds, sizeof(color_thresholds_list_lnk_data_t)); + py_helper_keyword_thresholds(n_args, args, 1, kw_args, &thresholds); + bool invert = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + image_t *other = py_helper_keyword_to_image_mutable(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_difference), NULL); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 3, kw_args, &roi); + + histogram_t hist; + switch (arg_img->pixfmt) { + case PIXFORMAT_BINARY: { + int bins = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bins), + (COLOR_BINARY_MAX-COLOR_BINARY_MIN+1)); + PY_ASSERT_TRUE_MSG(bins >= 2, "bins must be >= 2"); + hist.LBinCount = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_l_bins), bins); + PY_ASSERT_TRUE_MSG(hist.LBinCount >= 2, "l_bins must be >= 2"); + hist.ABinCount = 0; + hist.BBinCount = 0; + fb_alloc_mark(); + hist.LBins = fb_alloc(hist.LBinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.ABins = NULL; + hist.BBins = NULL; + imlib_get_histogram(&hist, arg_img, &roi, &thresholds, invert, other); + list_free(&thresholds); + break; + } + case PIXFORMAT_GRAYSCALE: { + int bins = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bins), + (COLOR_GRAYSCALE_MAX-COLOR_GRAYSCALE_MIN+1)); + PY_ASSERT_TRUE_MSG(bins >= 2, "bins must be >= 2"); + hist.LBinCount = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_l_bins), bins); + PY_ASSERT_TRUE_MSG(hist.LBinCount >= 2, "l_bins must be >= 2"); + hist.ABinCount = 0; + hist.BBinCount = 0; + fb_alloc_mark(); + hist.LBins = fb_alloc(hist.LBinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.ABins = NULL; + hist.BBins = NULL; + imlib_get_histogram(&hist, arg_img, &roi, &thresholds, invert, other); + list_free(&thresholds); + break; + } + case PIXFORMAT_RGB565: { + int l_bins = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bins), + (COLOR_L_MAX-COLOR_L_MIN+1)); + PY_ASSERT_TRUE_MSG(l_bins >= 2, "bins must be >= 2"); + hist.LBinCount = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_l_bins), l_bins); + PY_ASSERT_TRUE_MSG(hist.LBinCount >= 2, "l_bins must be >= 2"); + int a_bins = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bins), + (COLOR_A_MAX-COLOR_A_MIN+1)); + PY_ASSERT_TRUE_MSG(a_bins >= 2, "bins must be >= 2"); + hist.ABinCount = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_a_bins), a_bins); + PY_ASSERT_TRUE_MSG(hist.ABinCount >= 2, "a_bins must be >= 2"); + int b_bins = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_bins), + (COLOR_B_MAX-COLOR_B_MIN+1)); + PY_ASSERT_TRUE_MSG(b_bins >= 2, "bins must be >= 2"); + hist.BBinCount = py_helper_keyword_int(n_args, args, n_args, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_b_bins), b_bins); + PY_ASSERT_TRUE_MSG(hist.BBinCount >= 2, "b_bins must be >= 2"); + fb_alloc_mark(); + hist.LBins = fb_alloc(hist.LBinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.ABins = fb_alloc(hist.ABinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.BBins = fb_alloc(hist.BBinCount * sizeof(float), FB_ALLOC_NO_HINT); + imlib_get_histogram(&hist, arg_img, &roi, &thresholds, invert, other); + list_free(&thresholds); + break; + } + default: { + return MP_OBJ_NULL; + } + } + + statistics_t stats; + imlib_get_statistics(&stats, arg_img->pixfmt, &hist); + fb_alloc_free_till_mark(); + + py_statistics_obj_t *o = m_new_obj(py_statistics_obj_t); + o->base.type = &py_statistics_type; + o->pixfmt = arg_img->pixfmt; + + o->LMean = mp_obj_new_int(stats.LMean); + o->LMedian = mp_obj_new_int(stats.LMedian); + o->LMode= mp_obj_new_int(stats.LMode); + o->LSTDev = mp_obj_new_int(stats.LSTDev); + o->LMin = mp_obj_new_int(stats.LMin); + o->LMax = mp_obj_new_int(stats.LMax); + o->LLQ = mp_obj_new_int(stats.LLQ); + o->LUQ = mp_obj_new_int(stats.LUQ); + o->AMean = mp_obj_new_int(stats.AMean); + o->AMedian = mp_obj_new_int(stats.AMedian); + o->AMode= mp_obj_new_int(stats.AMode); + o->ASTDev = mp_obj_new_int(stats.ASTDev); + o->AMin = mp_obj_new_int(stats.AMin); + o->AMax = mp_obj_new_int(stats.AMax); + o->ALQ = mp_obj_new_int(stats.ALQ); + o->AUQ = mp_obj_new_int(stats.AUQ); + o->BMean = mp_obj_new_int(stats.BMean); + o->BMedian = mp_obj_new_int(stats.BMedian); + o->BMode= mp_obj_new_int(stats.BMode); + o->BSTDev = mp_obj_new_int(stats.BSTDev); + o->BMin = mp_obj_new_int(stats.BMin); + o->BMax = mp_obj_new_int(stats.BMax); + o->BLQ = mp_obj_new_int(stats.BLQ); + o->BUQ = mp_obj_new_int(stats.BUQ); + + return o; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_get_statistics_obj, 1, py_image_get_statistics); + +// Line Object // +#define py_line_obj_size 8 +typedef struct py_line_obj { + mp_obj_base_t base; + mp_obj_t x1, y1, x2, y2, length, magnitude, theta, rho; +} py_line_obj_t; + +static void py_line_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_line_obj_t *self = self_in; + mp_printf(print, + "{\"x1\":%d, \"y1\":%d, \"x2\":%d, \"y2\":%d, \"length\":%d, \"magnitude\":%d, \"theta\":%d, \"rho\":%d}", + mp_obj_get_int(self->x1), + mp_obj_get_int(self->y1), + mp_obj_get_int(self->x2), + mp_obj_get_int(self->y2), + mp_obj_get_int(self->length), + mp_obj_get_int(self->magnitude), + mp_obj_get_int(self->theta), + mp_obj_get_int(self->rho)); +} + +static mp_obj_t py_line_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_line_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_line_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->x1) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_line_obj_size, index, false)) { + case 0: return self->x1; + case 1: return self->y1; + case 2: return self->x2; + case 3: return self->y2; + case 4: return self->length; + case 5: return self->magnitude; + case 6: return self->theta; + case 7: return self->rho; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_line_line(mp_obj_t self_in) +{ + return mp_obj_new_tuple(4, (mp_obj_t []) {((py_line_obj_t *) self_in)->x1, + ((py_line_obj_t *) self_in)->y1, + ((py_line_obj_t *) self_in)->x2, + ((py_line_obj_t *) self_in)->y2}); +} + +mp_obj_t py_line_x1(mp_obj_t self_in) { return ((py_line_obj_t *) self_in)->x1; } +mp_obj_t py_line_y1(mp_obj_t self_in) { return ((py_line_obj_t *) self_in)->y1; } +mp_obj_t py_line_x2(mp_obj_t self_in) { return ((py_line_obj_t *) self_in)->x2; } +mp_obj_t py_line_y2(mp_obj_t self_in) { return ((py_line_obj_t *) self_in)->y2; } +mp_obj_t py_line_length(mp_obj_t self_in) { return ((py_line_obj_t *) self_in)->length; } +mp_obj_t py_line_magnitude(mp_obj_t self_in) { return ((py_line_obj_t *) self_in)->magnitude; } +mp_obj_t py_line_theta(mp_obj_t self_in) { return ((py_line_obj_t *) self_in)->theta; } +mp_obj_t py_line_rho(mp_obj_t self_in) { return ((py_line_obj_t *) self_in)->rho; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_line_obj, py_line_line); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_x1_obj, py_line_x1); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_y1_obj, py_line_y1); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_x2_obj, py_line_x2); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_y2_obj, py_line_y2); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_length_obj, py_line_length); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_magnitude_obj, py_line_magnitude); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_theta_obj, py_line_theta); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_line_rho_obj, py_line_rho); + +STATIC const mp_rom_map_elem_t py_line_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_line), MP_ROM_PTR(&py_line_line_obj) }, + { MP_ROM_QSTR(MP_QSTR_x1), MP_ROM_PTR(&py_line_x1_obj) }, + { MP_ROM_QSTR(MP_QSTR_y1), MP_ROM_PTR(&py_line_y1_obj) }, + { MP_ROM_QSTR(MP_QSTR_x2), MP_ROM_PTR(&py_line_x2_obj) }, + { MP_ROM_QSTR(MP_QSTR_y2), MP_ROM_PTR(&py_line_y2_obj) }, + { MP_ROM_QSTR(MP_QSTR_length), MP_ROM_PTR(&py_line_length_obj) }, + { MP_ROM_QSTR(MP_QSTR_magnitude), MP_ROM_PTR(&py_line_magnitude_obj) }, + { MP_ROM_QSTR(MP_QSTR_theta), MP_ROM_PTR(&py_line_theta_obj) }, + { MP_ROM_QSTR(MP_QSTR_rho), MP_ROM_PTR(&py_line_rho_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_line_locals_dict, py_line_locals_dict_table); + +static const mp_obj_type_t py_line_type = { + { &mp_type_type }, + .name = MP_QSTR_line, + .print = py_line_print, + .subscr = py_line_subscr, + .locals_dict = (mp_obj_t) &py_line_locals_dict +}; + +static mp_obj_t py_image_get_regression(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + list_t thresholds; + list_init(&thresholds, sizeof(color_thresholds_list_lnk_data_t)); + py_helper_arg_to_thresholds(args[1], &thresholds); + if (!list_size(&thresholds)) return mp_const_none; + bool invert = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 3, kw_args, &roi); + + unsigned int x_stride = py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_stride), 2); + PY_ASSERT_TRUE_MSG(x_stride > 0, "x_stride must not be zero."); + unsigned int y_stride = py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_stride), 1); + PY_ASSERT_TRUE_MSG(y_stride > 0, "y_stride must not be zero."); + unsigned int area_threshold = py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_area_threshold), 10); + unsigned int pixels_threshold = py_helper_keyword_int(n_args, args, 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_pixels_threshold), 10); + bool robust = py_helper_keyword_int(n_args, args, 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_robust), false); + + find_lines_list_lnk_data_t out; + fb_alloc_mark(); + bool result = imlib_get_regression(&out, arg_img, &roi, x_stride, + y_stride, &thresholds, invert, area_threshold, pixels_threshold, robust); + fb_alloc_free_till_mark(); + list_free(&thresholds); + if (!result) { + return mp_const_none; + } + + py_line_obj_t *o = m_new_obj(py_line_obj_t); + o->base.type = &py_line_type; + o->x1 = mp_obj_new_int(out.line.x1); + o->y1 = mp_obj_new_int(out.line.y1); + o->x2 = mp_obj_new_int(out.line.x2); + o->y2 = mp_obj_new_int(out.line.y2); + int x_diff = out.line.x2 - out.line.x1; + int y_diff = out.line.y2 - out.line.y1; + o->length = mp_obj_new_int(fast_roundf(fast_sqrtf((x_diff * x_diff) + (y_diff * y_diff)))); + o->magnitude = mp_obj_new_int(out.magnitude); + o->theta = mp_obj_new_int(out.theta); + o->rho = mp_obj_new_int(out.rho); + + return o; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_get_regression_obj, 2, py_image_get_regression); + +/////////////// +// Find Methods +/////////////// + +// Blob Object // +#define py_blob_obj_size 12 +typedef struct py_blob_obj { + mp_obj_base_t base; + mp_obj_t corners; + mp_obj_t min_corners; + mp_obj_t x, y, w, h, pixels, cx, cy, rotation, code, count, perimeter, roundness; + mp_obj_t x_hist_bins; + mp_obj_t y_hist_bins; +} py_blob_obj_t; + +static void py_blob_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_blob_obj_t *self = self_in; + mp_printf(print, + "{\"x\":%d, \"y\":%d, \"w\":%d, \"h\":%d," + " \"pixels\":%d, \"cx\":%d, \"cy\":%d, \"rotation\":%f, \"code\":%d, \"count\":%d," + " \"perimeter\":%d, \"roundness\":%f}", + mp_obj_get_int(self->x), + mp_obj_get_int(self->y), + mp_obj_get_int(self->w), + mp_obj_get_int(self->h), + mp_obj_get_int(self->pixels), + fast_roundf(mp_obj_get_float(self->cx)), + fast_roundf(mp_obj_get_float(self->cy)), + (double) mp_obj_get_float(self->rotation), + mp_obj_get_int(self->code), + mp_obj_get_int(self->count), + mp_obj_get_int(self->perimeter), + (double) mp_obj_get_float(self->roundness)); +} + +static mp_obj_t py_blob_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_blob_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_blob_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->x) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_blob_obj_size, index, false)) { + case 0: return self->x; + case 1: return self->y; + case 2: return self->w; + case 3: return self->h; + case 4: return self->pixels; + case 5: return mp_obj_new_int(fast_roundf(mp_obj_get_float(self->cx))); + case 6: return mp_obj_new_int(fast_roundf(mp_obj_get_float(self->cy))); + case 7: return self->rotation; + case 8: return self->code; + case 9: return self->count; + case 10: return self->perimeter; + case 11: return self->roundness; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_blob_corners(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->corners; } +mp_obj_t py_blob_min_corners(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->min_corners; } +mp_obj_t py_blob_rect(mp_obj_t self_in) +{ + return mp_obj_new_tuple(4, (mp_obj_t []) {((py_blob_obj_t *) self_in)->x, + ((py_blob_obj_t *) self_in)->y, + ((py_blob_obj_t *) self_in)->w, + ((py_blob_obj_t *) self_in)->h}); +} + +mp_obj_t py_blob_x(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->x; } +mp_obj_t py_blob_y(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->y; } +mp_obj_t py_blob_w(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->w; } +mp_obj_t py_blob_h(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->h; } +mp_obj_t py_blob_pixels(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->pixels; } +mp_obj_t py_blob_cx(mp_obj_t self_in) { return mp_obj_new_int(fast_roundf(mp_obj_get_float(((py_blob_obj_t *) self_in)->cx))); } +mp_obj_t py_blob_cxf(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->cx; } +mp_obj_t py_blob_cy(mp_obj_t self_in) { return mp_obj_new_int(fast_roundf(mp_obj_get_float(((py_blob_obj_t *) self_in)->cy))); } +mp_obj_t py_blob_cyf(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->cy; } +mp_obj_t py_blob_rotation(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->rotation; } +mp_obj_t py_blob_rotation_deg(mp_obj_t self_in) { return mp_obj_new_int(IM_RAD2DEG(mp_obj_get_float(((py_blob_obj_t *) self_in)->rotation))); } +mp_obj_t py_blob_rotation_rad(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->rotation; } +mp_obj_t py_blob_code(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->code; } +mp_obj_t py_blob_count(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->count; } +mp_obj_t py_blob_perimeter(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->perimeter; } +mp_obj_t py_blob_roundness(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->roundness; } +mp_obj_t py_blob_elongation(mp_obj_t self_in) { return mp_obj_new_float(1 - mp_obj_get_float(((py_blob_obj_t *) self_in)->roundness)); } +mp_obj_t py_blob_area(mp_obj_t self_in) { + return mp_obj_new_int(mp_obj_get_int(((py_blob_obj_t *) self_in)->w) * mp_obj_get_int(((py_blob_obj_t *) self_in)->h)); +} +mp_obj_t py_blob_density(mp_obj_t self_in) { + int area = mp_obj_get_int(((py_blob_obj_t *) self_in)->w) * mp_obj_get_int(((py_blob_obj_t *) self_in)->h); + int pixels = mp_obj_get_int(((py_blob_obj_t *) self_in)->pixels); + return mp_obj_new_float(IM_DIV(pixels, ((float) area))); +} +// Rect-area versus pixels (e.g. blob area) -> Above. +// Rect-area versus perimeter -> Basically the same as the above with a different scale factor. +// Rect-perimeter versus pixels (e.g. blob area) -> Basically the same as the above with a different scale factor. +// Rect-perimeter versus perimeter -> Basically the same as the above with a different scale factor. +mp_obj_t py_blob_compactness(mp_obj_t self_in) { + int pixels = mp_obj_get_int(((py_blob_obj_t *) self_in)->pixels); + float perimeter = mp_obj_get_int(((py_blob_obj_t *) self_in)->perimeter); + return mp_obj_new_float(IM_DIV((pixels * 4 * M_PI), (perimeter * perimeter))); +} +mp_obj_t py_blob_solidity(mp_obj_t self_in) { + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(((py_blob_obj_t *) self_in)->min_corners, 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + // Shoelace Formula + float min_area = (((x0*y1)+(x1*y2)+(x2*y3)+(x3*y0))-((y0*x1)+(y1*x2)+(y2*x3)+(y3*x0)))/2.0f; + int pixels = mp_obj_get_int(((py_blob_obj_t *) self_in)->pixels); + return mp_obj_new_float(IM_MIN(IM_DIV(pixels, min_area), 1)); +} +mp_obj_t py_blob_convexity(mp_obj_t self_in) { + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(((py_blob_obj_t *) self_in)->min_corners, 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + float d0 = fast_sqrtf(((x0 - x1) * (x0 - x1)) + ((y0 - y1) * (y0 - y1))); + float d1 = fast_sqrtf(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2))); + float d2 = fast_sqrtf(((x2 - x3) * (x2 - x3)) + ((y2 - y3) * (y2 - y3))); + float d3 = fast_sqrtf(((x3 - x0) * (x3 - x0)) + ((y3 - y0) * (y3 - y0))); + int perimeter = mp_obj_get_int(((py_blob_obj_t *) self_in)->perimeter); + return mp_obj_new_float(IM_MIN(IM_DIV(d0 + d1 + d2 + d3, perimeter), 1)); +} +// Min rect-area versus pixels (e.g. blob area) -> Above. +// Min rect-area versus perimeter -> Basically the same as the above with a different scale factor. +// Min rect-perimeter versus pixels (e.g. blob area) -> Basically the same as the above with a different scale factor. +// Min rect-perimeter versus perimeter -> Above +mp_obj_t py_blob_x_hist_bins(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->x_hist_bins; } +mp_obj_t py_blob_y_hist_bins(mp_obj_t self_in) { return ((py_blob_obj_t *) self_in)->y_hist_bins; } +mp_obj_t py_blob_major_axis_line(mp_obj_t self_in) { + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(((py_blob_obj_t *) self_in)->min_corners, 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + int m0x = (x0 + x1) / 2; + int m0y = (y0 + y1) / 2; + int m1x = (x1 + x2) / 2; + int m1y = (y1 + y2) / 2; + int m2x = (x2 + x3) / 2; + int m2y = (y2 + y3) / 2; + int m3x = (x3 + x0) / 2; + int m3y = (y3 + y0) / 2; + + float l0 = fast_sqrtf(((m0x - m2x) * (m0x - m2x)) + ((m0y - m2y) * (m0y - m2y))); + float l1 = fast_sqrtf(((m1x - m3x) * (m1x - m3x)) + ((m1y - m3y) * (m1y - m3y))); + + if (l0 >= l1) { + return mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(m0x), + mp_obj_new_int(m0y), + mp_obj_new_int(m2x), + mp_obj_new_int(m2y)}); + } else { + return mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(m1x), + mp_obj_new_int(m1y), + mp_obj_new_int(m3x), + mp_obj_new_int(m3y)}); + } +} +mp_obj_t py_blob_minor_axis_line(mp_obj_t self_in) { + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(((py_blob_obj_t *) self_in)->min_corners, 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + int m0x = (x0 + x1) / 2; + int m0y = (y0 + y1) / 2; + int m1x = (x1 + x2) / 2; + int m1y = (y1 + y2) / 2; + int m2x = (x2 + x3) / 2; + int m2y = (y2 + y3) / 2; + int m3x = (x3 + x0) / 2; + int m3y = (y3 + y0) / 2; + + float l0 = fast_sqrtf(((m0x - m2x) * (m0x - m2x)) + ((m0y - m2y) * (m0y - m2y))); + float l1 = fast_sqrtf(((m1x - m3x) * (m1x - m3x)) + ((m1y - m3y) * (m1y - m3y))); + + if (l0 < l1) { + return mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(m0x), + mp_obj_new_int(m0y), + mp_obj_new_int(m2x), + mp_obj_new_int(m2y)}); + } else { + return mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(m1x), + mp_obj_new_int(m1y), + mp_obj_new_int(m3x), + mp_obj_new_int(m3y)}); + } +} +mp_obj_t py_blob_enclosing_circle(mp_obj_t self_in) { + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(((py_blob_obj_t *) self_in)->min_corners, 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + int cx = (x0 + x1 + x2 + x3) / 4; + int cy = (y0 + y1 + y2 + y3) / 4; + + float d0 = fast_sqrtf(((x0 - cx) * (x0 - cx)) + ((y0 - cy) * (y0 - cy))); + float d1 = fast_sqrtf(((x1 - cx) * (x1 - cx)) + ((y1 - cy) * (y1 - cy))); + float d2 = fast_sqrtf(((x2 - cx) * (x2 - cx)) + ((y2 - cy) * (y2 - cy))); + float d3 = fast_sqrtf(((x3 - cx) * (x3 - cx)) + ((y3 - cy) * (y3 - cy))); + float d = IM_MAX(d0, IM_MAX(d1, IM_MAX(d2, d3))); + + return mp_obj_new_tuple(3, (mp_obj_t []) {mp_obj_new_int(cx), + mp_obj_new_int(cy), + mp_obj_new_int(fast_roundf(d))}); +} +mp_obj_t py_blob_enclosed_ellipse(mp_obj_t self_in) { + mp_obj_t *corners, *p0, *p1, *p2, *p3; + mp_obj_get_array_fixed_n(((py_blob_obj_t *) self_in)->min_corners, 4, &corners); + mp_obj_get_array_fixed_n(corners[0], 2, &p0); + mp_obj_get_array_fixed_n(corners[1], 2, &p1); + mp_obj_get_array_fixed_n(corners[2], 2, &p2); + mp_obj_get_array_fixed_n(corners[3], 2, &p3); + + int x0, y0, x1, y1, x2, y2, x3, y3; + x0 = mp_obj_get_int(p0[0]); + y0 = mp_obj_get_int(p0[1]); + x1 = mp_obj_get_int(p1[0]); + y1 = mp_obj_get_int(p1[1]); + x2 = mp_obj_get_int(p2[0]); + y2 = mp_obj_get_int(p2[1]); + x3 = mp_obj_get_int(p3[0]); + y3 = mp_obj_get_int(p3[1]); + + int m0x = (x0 + x1) / 2; + int m0y = (y0 + y1) / 2; + int m1x = (x1 + x2) / 2; + int m1y = (y1 + y2) / 2; + int m2x = (x2 + x3) / 2; + int m2y = (y2 + y3) / 2; + int m3x = (x3 + x0) / 2; + int m3y = (y3 + y0) / 2; + + int cx = (x0 + x1 + x2 + x3) / 4; + int cy = (y0 + y1 + y2 + y3) / 4; + + float d0 = fast_sqrtf(((m0x - cx) * (m0x - cx)) + ((m0y - cy) * (m0y - cy))); + float d1 = fast_sqrtf(((m1x - cx) * (m1x - cx)) + ((m1y - cy) * (m1y - cy))); + float d2 = fast_sqrtf(((m2x - cx) * (m2x - cx)) + ((m2y - cy) * (m2y - cy))); + float d3 = fast_sqrtf(((m3x - cx) * (m3x - cx)) + ((m3y - cy) * (m3y - cy))); + float a = IM_MIN(d0, d2); + float b = IM_MIN(d1, d3); + + float l0 = fast_sqrtf(((m0x - m2x) * (m0x - m2x)) + ((m0y - m2y) * (m0y - m2y))); + float l1 = fast_sqrtf(((m1x - m3x) * (m1x - m3x)) + ((m1y - m3y) * (m1y - m3y))); + + float r; + + if (l0 >= l1) { + r = IM_RAD2DEG(fast_atan2f(m0y - m2y, m0x - m2x)); + } else { + r = IM_RAD2DEG(fast_atan2f(m1y - m3y, m1x - m3x) + M_PI_2); + } + + return mp_obj_new_tuple(5, (mp_obj_t []) {mp_obj_new_int(cx), + mp_obj_new_int(cy), + mp_obj_new_int(a), + mp_obj_new_int(b), + mp_obj_new_int(r)}); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_corners_obj, py_blob_corners); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_min_corners_obj, py_blob_min_corners); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_rect_obj, py_blob_rect); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_x_obj, py_blob_x); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_y_obj, py_blob_y); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_w_obj, py_blob_w); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_h_obj, py_blob_h); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_pixels_obj, py_blob_pixels); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_cx_obj, py_blob_cx); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_cxf_obj, py_blob_cxf); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_cy_obj, py_blob_cy); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_cyf_obj, py_blob_cyf); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_rotation_obj, py_blob_rotation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_rotation_deg_obj, py_blob_rotation_deg); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_rotation_rad_obj, py_blob_rotation_rad); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_code_obj, py_blob_code); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_count_obj, py_blob_count); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_perimeter_obj, py_blob_perimeter); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_roundness_obj, py_blob_roundness); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_elongation_obj, py_blob_elongation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_area_obj, py_blob_area); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_density_obj, py_blob_density); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_compactness_obj, py_blob_compactness); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_solidity_obj, py_blob_solidity); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_convexity_obj, py_blob_convexity); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_x_hist_bins_obj, py_blob_x_hist_bins); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_y_hist_bins_obj, py_blob_y_hist_bins); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_major_axis_line_obj, py_blob_major_axis_line); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_minor_axis_line_obj, py_blob_minor_axis_line); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_enclosing_circle_obj, py_blob_enclosing_circle); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_blob_enclosed_ellipse_obj, py_blob_enclosed_ellipse); + +STATIC const mp_rom_map_elem_t py_blob_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_corners), MP_ROM_PTR(&py_blob_corners_obj) }, + { MP_ROM_QSTR(MP_QSTR_min_corners), MP_ROM_PTR(&py_blob_min_corners_obj) }, + { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&py_blob_rect_obj) }, + { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_blob_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_blob_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_w), MP_ROM_PTR(&py_blob_w_obj) }, + { MP_ROM_QSTR(MP_QSTR_h), MP_ROM_PTR(&py_blob_h_obj) }, + { MP_ROM_QSTR(MP_QSTR_pixels), MP_ROM_PTR(&py_blob_pixels_obj) }, + { MP_ROM_QSTR(MP_QSTR_cx), MP_ROM_PTR(&py_blob_cx_obj) }, + { MP_ROM_QSTR(MP_QSTR_cxf), MP_ROM_PTR(&py_blob_cxf_obj) }, + { MP_ROM_QSTR(MP_QSTR_cy), MP_ROM_PTR(&py_blob_cy_obj) }, + { MP_ROM_QSTR(MP_QSTR_cyf), MP_ROM_PTR(&py_blob_cyf_obj) }, + { MP_ROM_QSTR(MP_QSTR_rotation), MP_ROM_PTR(&py_blob_rotation_obj) }, + { MP_ROM_QSTR(MP_QSTR_rotation_deg), MP_ROM_PTR(&py_blob_rotation_deg_obj) }, + { MP_ROM_QSTR(MP_QSTR_rotation_rad), MP_ROM_PTR(&py_blob_rotation_rad_obj) }, + { MP_ROM_QSTR(MP_QSTR_code), MP_ROM_PTR(&py_blob_code_obj) }, + { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&py_blob_count_obj) }, + { MP_ROM_QSTR(MP_QSTR_perimeter), MP_ROM_PTR(&py_blob_perimeter_obj) }, + { MP_ROM_QSTR(MP_QSTR_roundness), MP_ROM_PTR(&py_blob_roundness_obj) }, + { MP_ROM_QSTR(MP_QSTR_elongation), MP_ROM_PTR(&py_blob_elongation_obj) }, + { MP_ROM_QSTR(MP_QSTR_area), MP_ROM_PTR(&py_blob_area_obj) } , + { MP_ROM_QSTR(MP_QSTR_density), MP_ROM_PTR(&py_blob_density_obj) }, + { MP_ROM_QSTR(MP_QSTR_extent), MP_ROM_PTR(&py_blob_density_obj) }, + { MP_ROM_QSTR(MP_QSTR_compactness), MP_ROM_PTR(&py_blob_compactness_obj) }, + { MP_ROM_QSTR(MP_QSTR_solidity), MP_ROM_PTR(&py_blob_solidity_obj) }, + { MP_ROM_QSTR(MP_QSTR_convexity), MP_ROM_PTR(&py_blob_convexity_obj) }, + { MP_ROM_QSTR(MP_QSTR_x_hist_bins), MP_ROM_PTR(&py_blob_x_hist_bins_obj) }, + { MP_ROM_QSTR(MP_QSTR_y_hist_bins), MP_ROM_PTR(&py_blob_y_hist_bins_obj) }, + { MP_ROM_QSTR(MP_QSTR_major_axis_line), MP_ROM_PTR(&py_blob_major_axis_line_obj) }, + { MP_ROM_QSTR(MP_QSTR_minor_axis_line), MP_ROM_PTR(&py_blob_minor_axis_line_obj) }, + { MP_ROM_QSTR(MP_QSTR_enclosing_circle), MP_ROM_PTR(&py_blob_enclosing_circle_obj) }, + { MP_ROM_QSTR(MP_QSTR_enclosed_ellipse), MP_ROM_PTR(&py_blob_enclosed_ellipse_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_blob_locals_dict, py_blob_locals_dict_table); + +static const mp_obj_type_t py_blob_type = { + { &mp_type_type }, + .name = MP_QSTR_blob, + .print = py_blob_print, + .subscr = py_blob_subscr, + .locals_dict = (mp_obj_t) &py_blob_locals_dict +}; + +static bool py_image_find_blobs_threshold_cb(void *fun_obj, find_blobs_list_lnk_data_t *blob) +{ + py_blob_obj_t *o = m_new_obj(py_blob_obj_t); + o->base.type = &py_blob_type; + o->corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x), + mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].x), + mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x), + mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].x), + mp_obj_new_int(blob->corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y)})}); + point_t min_corners[4]; + point_min_area_rectangle(blob->corners, min_corners, FIND_BLOBS_CORNERS_RESOLUTION); + o->min_corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[0].x), mp_obj_new_int(min_corners[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[1].x), mp_obj_new_int(min_corners[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[2].x), mp_obj_new_int(min_corners[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[3].x), mp_obj_new_int(min_corners[3].y)})}); + o->x = mp_obj_new_int(blob->rect.x); + o->y = mp_obj_new_int(blob->rect.y); + o->w = mp_obj_new_int(blob->rect.w); + o->h = mp_obj_new_int(blob->rect.h); + o->pixels = mp_obj_new_int(blob->pixels); + o->cx = mp_obj_new_float(blob->centroid_x); + o->cy = mp_obj_new_float(blob->centroid_y); + o->rotation = mp_obj_new_float(blob->rotation); + o->code = mp_obj_new_int(blob->code); + o->count = mp_obj_new_int(blob->count); + o->perimeter = mp_obj_new_int(blob->perimeter); + o->roundness = mp_obj_new_float(blob->roundness); + o->x_hist_bins = mp_obj_new_list(blob->x_hist_bins_count, NULL); + o->y_hist_bins = mp_obj_new_list(blob->y_hist_bins_count, NULL); + + for (int i = 0; i < blob->x_hist_bins_count; i++) { + ((mp_obj_list_t *) o->x_hist_bins)->items[i] = mp_obj_new_int(blob->x_hist_bins[i]); + } + + for (int i = 0; i < blob->y_hist_bins_count; i++) { + ((mp_obj_list_t *) o->y_hist_bins)->items[i] = mp_obj_new_int(blob->y_hist_bins[i]); + } + + return mp_obj_is_true(mp_call_function_1(fun_obj, o)); +} + +static bool py_image_find_blobs_merge_cb(void *fun_obj, find_blobs_list_lnk_data_t *blob0, find_blobs_list_lnk_data_t *blob1) +{ + py_blob_obj_t *o0 = m_new_obj(py_blob_obj_t); + o0->base.type = &py_blob_type; + o0->corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x), + mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].x), + mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x), + mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].x), + mp_obj_new_int(blob0->corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y)})}); + point_t min_area_rect_corners0[4]; + point_min_area_rectangle(blob0->corners, min_area_rect_corners0, FIND_BLOBS_CORNERS_RESOLUTION); + o0->min_corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners0[0].x), mp_obj_new_int(min_area_rect_corners0[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners0[1].x), mp_obj_new_int(min_area_rect_corners0[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners0[2].x), mp_obj_new_int(min_area_rect_corners0[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners0[3].x), mp_obj_new_int(min_area_rect_corners0[3].y)})}); + o0->x = mp_obj_new_int(blob0->rect.x); + o0->y = mp_obj_new_int(blob0->rect.y); + o0->w = mp_obj_new_int(blob0->rect.w); + o0->h = mp_obj_new_int(blob0->rect.h); + o0->pixels = mp_obj_new_int(blob0->pixels); + o0->cx = mp_obj_new_float(blob0->centroid_x); + o0->cy = mp_obj_new_float(blob0->centroid_y); + o0->rotation = mp_obj_new_float(blob0->rotation); + o0->code = mp_obj_new_int(blob0->code); + o0->count = mp_obj_new_int(blob0->count); + o0->perimeter = mp_obj_new_int(blob0->perimeter); + o0->roundness = mp_obj_new_float(blob0->roundness); + o0->x_hist_bins = mp_obj_new_list(blob0->x_hist_bins_count, NULL); + o0->y_hist_bins = mp_obj_new_list(blob0->y_hist_bins_count, NULL); + + for (int i = 0; i < blob0->x_hist_bins_count; i++) { + ((mp_obj_list_t *) o0->x_hist_bins)->items[i] = mp_obj_new_int(blob0->x_hist_bins[i]); + } + + for (int i = 0; i < blob0->y_hist_bins_count; i++) { + ((mp_obj_list_t *) o0->y_hist_bins)->items[i] = mp_obj_new_int(blob0->y_hist_bins[i]); + } + + py_blob_obj_t *o1 = m_new_obj(py_blob_obj_t); + o1->base.type = &py_blob_type; + o1->corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x), + mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].x), + mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x), + mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].x), + mp_obj_new_int(blob1->corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y)})}); + point_t min_area_rect_corners1[4]; + point_min_area_rectangle(blob1->corners, min_area_rect_corners1, FIND_BLOBS_CORNERS_RESOLUTION); + o1->min_corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners1[0].x), mp_obj_new_int(min_area_rect_corners1[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners1[1].x), mp_obj_new_int(min_area_rect_corners1[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners1[2].x), mp_obj_new_int(min_area_rect_corners1[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_area_rect_corners1[3].x), mp_obj_new_int(min_area_rect_corners1[3].y)})}); + o1->x = mp_obj_new_int(blob1->rect.x); + o1->y = mp_obj_new_int(blob1->rect.y); + o1->w = mp_obj_new_int(blob1->rect.w); + o1->h = mp_obj_new_int(blob1->rect.h); + o1->pixels = mp_obj_new_int(blob1->pixels); + o1->cx = mp_obj_new_float(blob1->centroid_x); + o1->cy = mp_obj_new_float(blob1->centroid_y); + o1->rotation = mp_obj_new_float(blob1->rotation); + o1->code = mp_obj_new_int(blob1->code); + o1->count = mp_obj_new_int(blob1->count); + o1->perimeter = mp_obj_new_int(blob1->perimeter); + o1->roundness = mp_obj_new_float(blob1->roundness); + o1->x_hist_bins = mp_obj_new_list(blob1->x_hist_bins_count, NULL); + o1->y_hist_bins = mp_obj_new_list(blob1->y_hist_bins_count, NULL); + + for (int i = 0; i < blob1->x_hist_bins_count; i++) { + ((mp_obj_list_t *) o1->x_hist_bins)->items[i] = mp_obj_new_int(blob1->x_hist_bins[i]); + } + + for (int i = 0; i < blob1->y_hist_bins_count; i++) { + ((mp_obj_list_t *) o1->y_hist_bins)->items[i] = mp_obj_new_int(blob1->y_hist_bins[i]); + } + + return mp_obj_is_true(mp_call_function_2(fun_obj, o0, o1)); +} + +static mp_obj_t py_image_find_blobs(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + list_t thresholds; + list_init(&thresholds, sizeof(color_thresholds_list_lnk_data_t)); + py_helper_arg_to_thresholds(args[1], &thresholds); + if (!list_size(&thresholds)) return mp_obj_new_list(0, NULL); + bool invert = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 3, kw_args, &roi); + + unsigned int x_stride = + py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_stride), 2); + PY_ASSERT_TRUE_MSG(x_stride > 0, "x_stride must not be zero."); + unsigned int y_stride = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_stride), 1); + PY_ASSERT_TRUE_MSG(y_stride > 0, "y_stride must not be zero."); + unsigned int area_threshold = + py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_area_threshold), 10); + unsigned int pixels_threshold = + py_helper_keyword_int(n_args, args, 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_pixels_threshold), 10); + bool merge = + py_helper_keyword_int(n_args, args, 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_merge), false); + int margin = + py_helper_keyword_int(n_args, args, 9, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_margin), 0); + mp_obj_t threshold_cb = + py_helper_keyword_object(n_args, args, 10, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold_cb), NULL); + mp_obj_t merge_cb = + py_helper_keyword_object(n_args, args, 11, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_merge_cb), NULL); + unsigned int x_hist_bins_max = + py_helper_keyword_int(n_args, args, 12, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_hist_bins_max), 0); + unsigned int y_hist_bins_max = + py_helper_keyword_int(n_args, args, 13, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_hist_bins_max), 0); + + list_t out; + fb_alloc_mark(); + imlib_find_blobs(&out, arg_img, &roi, x_stride, y_stride, &thresholds, invert, + area_threshold, pixels_threshold, merge, margin, + py_image_find_blobs_threshold_cb, threshold_cb, py_image_find_blobs_merge_cb, merge_cb, x_hist_bins_max, y_hist_bins_max); + fb_alloc_free_till_mark(); + list_free(&thresholds); + + mp_obj_list_t *objects_list = mp_obj_new_list(list_size(&out), NULL); + for (size_t i = 0; list_size(&out); i++) { + find_blobs_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + py_blob_obj_t *o = m_new_obj(py_blob_obj_t); + o->base.type = &py_blob_type; + o->corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x), + mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].x), + mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x), + mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].x), + mp_obj_new_int(lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y)})}); + point_t min_corners[4]; + point_min_area_rectangle(lnk_data.corners, min_corners, FIND_BLOBS_CORNERS_RESOLUTION); + o->min_corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[0].x), mp_obj_new_int(min_corners[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[1].x), mp_obj_new_int(min_corners[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[2].x), mp_obj_new_int(min_corners[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(min_corners[3].x), mp_obj_new_int(min_corners[3].y)})}); + o->x = mp_obj_new_int(lnk_data.rect.x); + o->y = mp_obj_new_int(lnk_data.rect.y); + o->w = mp_obj_new_int(lnk_data.rect.w); + o->h = mp_obj_new_int(lnk_data.rect.h); + o->pixels = mp_obj_new_int(lnk_data.pixels); + o->cx = mp_obj_new_float(lnk_data.centroid_x); + o->cy = mp_obj_new_float(lnk_data.centroid_y); + o->rotation = mp_obj_new_float(lnk_data.rotation); + o->code = mp_obj_new_int(lnk_data.code); + o->count = mp_obj_new_int(lnk_data.count); + o->perimeter = mp_obj_new_int(lnk_data.perimeter); + o->roundness = mp_obj_new_float(lnk_data.roundness); + o->x_hist_bins = mp_obj_new_list(lnk_data.x_hist_bins_count, NULL); + o->y_hist_bins = mp_obj_new_list(lnk_data.y_hist_bins_count, NULL); + + for (int i = 0; i < lnk_data.x_hist_bins_count; i++) { + ((mp_obj_list_t *) o->x_hist_bins)->items[i] = mp_obj_new_int(lnk_data.x_hist_bins[i]); + } + + for (int i = 0; i < lnk_data.y_hist_bins_count; i++) { + ((mp_obj_list_t *) o->y_hist_bins)->items[i] = mp_obj_new_int(lnk_data.y_hist_bins[i]); + } + + objects_list->items[i] = o; + if (lnk_data.x_hist_bins) xfree(lnk_data.x_hist_bins); + if (lnk_data.y_hist_bins) xfree(lnk_data.y_hist_bins); + } + + return objects_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_blobs_obj, 2, py_image_find_blobs); + +#ifdef IMLIB_ENABLE_FIND_LINES +static mp_obj_t py_image_find_lines(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 1, kw_args, &roi); + + unsigned int x_stride = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_stride), 2); + PY_ASSERT_TRUE_MSG(x_stride > 0, "x_stride must not be zero."); + unsigned int y_stride = py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_stride), 1); + PY_ASSERT_TRUE_MSG(y_stride > 0, "y_stride must not be zero."); + uint32_t threshold = py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 1000); + unsigned int theta_margin = py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_theta_margin), 25); + unsigned int rho_margin = py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_rho_margin), 25); + + list_t out; + fb_alloc_mark(); + imlib_find_lines(&out, arg_img, &roi, x_stride, y_stride, threshold, theta_margin, rho_margin); + fb_alloc_free_till_mark(); + + mp_obj_list_t *objects_list = mp_obj_new_list(list_size(&out), NULL); + for (size_t i = 0; list_size(&out); i++) { + find_lines_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + py_line_obj_t *o = m_new_obj(py_line_obj_t); + o->base.type = &py_line_type; + o->x1 = mp_obj_new_int(lnk_data.line.x1); + o->y1 = mp_obj_new_int(lnk_data.line.y1); + o->x2 = mp_obj_new_int(lnk_data.line.x2); + o->y2 = mp_obj_new_int(lnk_data.line.y2); + int x_diff = lnk_data.line.x2 - lnk_data.line.x1; + int y_diff = lnk_data.line.y2 - lnk_data.line.y1; + o->length = mp_obj_new_int(fast_roundf(fast_sqrtf((x_diff * x_diff) + (y_diff * y_diff)))); + o->magnitude = mp_obj_new_int(lnk_data.magnitude); + o->theta = mp_obj_new_int(lnk_data.theta); + o->rho = mp_obj_new_int(lnk_data.rho); + + objects_list->items[i] = o; + } + + return objects_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_lines_obj, 1, py_image_find_lines); +#endif // IMLIB_ENABLE_FIND_LINES + +#ifdef IMLIB_ENABLE_FIND_LINE_SEGMENTS +static mp_obj_t py_image_find_line_segments(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_image_cobj(args[0]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 1, kw_args, &roi); + + unsigned int merge_distance = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_merge_distance), 0); + unsigned int max_theta_diff = py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_max_theta_diff), 15); + + list_t out; + fb_alloc_mark(); + imlib_lsd_find_line_segments(&out, arg_img, &roi, merge_distance, max_theta_diff); + fb_alloc_free_till_mark(); + + mp_obj_list_t *objects_list = mp_obj_new_list(list_size(&out), NULL); + for (size_t i = 0; list_size(&out); i++) { + find_lines_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + py_line_obj_t *o = m_new_obj(py_line_obj_t); + o->base.type = &py_line_type; + o->x1 = mp_obj_new_int(lnk_data.line.x1); + o->y1 = mp_obj_new_int(lnk_data.line.y1); + o->x2 = mp_obj_new_int(lnk_data.line.x2); + o->y2 = mp_obj_new_int(lnk_data.line.y2); + int x_diff = lnk_data.line.x2 - lnk_data.line.x1; + int y_diff = lnk_data.line.y2 - lnk_data.line.y1; + o->length = mp_obj_new_int(fast_roundf(fast_sqrtf((x_diff * x_diff) + (y_diff * y_diff)))); + o->magnitude = mp_obj_new_int(lnk_data.magnitude); + o->theta = mp_obj_new_int(lnk_data.theta); + o->rho = mp_obj_new_int(lnk_data.rho); + + objects_list->items[i] = o; + } + + return objects_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_line_segments_obj, 1, py_image_find_line_segments); +#endif // IMLIB_ENABLE_FIND_LINE_SEGMENTS + +#ifdef IMLIB_ENABLE_FIND_CIRCLES +// Circle Object // +#define py_circle_obj_size 4 +typedef struct py_circle_obj { + mp_obj_base_t base; + mp_obj_t x, y, r, magnitude; +} py_circle_obj_t; + +static void py_circle_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_circle_obj_t *self = self_in; + mp_printf(print, + "{\"x\":%d, \"y\":%d, \"r\":%d, \"magnitude\":%d}", + mp_obj_get_int(self->x), + mp_obj_get_int(self->y), + mp_obj_get_int(self->r), + mp_obj_get_int(self->magnitude)); +} + +static mp_obj_t py_circle_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_circle_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_circle_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->x) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_circle_obj_size, index, false)) { + case 0: return self->x; + case 1: return self->y; + case 2: return self->r; + case 3: return self->magnitude; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_circle_circle(mp_obj_t self_in) +{ + return mp_obj_new_tuple(3, (mp_obj_t []) {((py_circle_obj_t *) self_in)->x, + ((py_circle_obj_t *) self_in)->y, + ((py_circle_obj_t *) self_in)->r}); +} + +mp_obj_t py_circle_x(mp_obj_t self_in) { return ((py_circle_obj_t *) self_in)->x; } +mp_obj_t py_circle_y(mp_obj_t self_in) { return ((py_circle_obj_t *) self_in)->y; } +mp_obj_t py_circle_r(mp_obj_t self_in) { return ((py_circle_obj_t *) self_in)->r; } +mp_obj_t py_circle_magnitude(mp_obj_t self_in) { return ((py_circle_obj_t *) self_in)->magnitude; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_circle_obj, py_circle_circle); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_x_obj, py_circle_x); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_y_obj, py_circle_y); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_r_obj, py_circle_r); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_circle_magnitude_obj, py_circle_magnitude); + +STATIC const mp_rom_map_elem_t py_circle_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_circle), MP_ROM_PTR(&py_circle_circle_obj) }, + { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_circle_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_circle_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_r), MP_ROM_PTR(&py_circle_r_obj) }, + { MP_ROM_QSTR(MP_QSTR_magnitude), MP_ROM_PTR(&py_circle_magnitude_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_circle_locals_dict, py_circle_locals_dict_table); + +static const mp_obj_type_t py_circle_type = { + { &mp_type_type }, + .name = MP_QSTR_circle, + .print = py_circle_print, + .subscr = py_circle_subscr, + .locals_dict = (mp_obj_t) &py_circle_locals_dict +}; + +static mp_obj_t py_image_find_circles(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 1, kw_args, &roi); + + unsigned int x_stride = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_stride), 2); + PY_ASSERT_TRUE_MSG(x_stride > 0, "x_stride must not be zero."); + unsigned int y_stride = py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_stride), 1); + PY_ASSERT_TRUE_MSG(y_stride > 0, "y_stride must not be zero."); + uint32_t threshold = py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 2000); + unsigned int x_margin = py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_margin), 10); + unsigned int y_margin = py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_margin), 10); + unsigned int r_margin = py_helper_keyword_int(n_args, args, 7, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_r_margin), 10); + unsigned int r_min = IM_MAX(py_helper_keyword_int(n_args, args, 8, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_r_min), + 2), 2); + unsigned int r_max = IM_MIN(py_helper_keyword_int(n_args, args, 9, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_r_max), + IM_MIN((roi.w / 2), (roi.h / 2))), IM_MIN((roi.w / 2), (roi.h / 2))); + unsigned int r_step = py_helper_keyword_int(n_args, args, 10, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_r_step), 2); + + list_t out; + fb_alloc_mark(); + imlib_find_circles(&out, arg_img, &roi, x_stride, y_stride, threshold, x_margin, y_margin, r_margin, + r_min, r_max, r_step); + fb_alloc_free_till_mark(); + + mp_obj_list_t *objects_list = mp_obj_new_list(list_size(&out), NULL); + for (size_t i = 0; list_size(&out); i++) { + find_circles_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + py_circle_obj_t *o = m_new_obj(py_circle_obj_t); + o->base.type = &py_circle_type; + o->x = mp_obj_new_int(lnk_data.p.x); + o->y = mp_obj_new_int(lnk_data.p.y); + o->r = mp_obj_new_int(lnk_data.r); + o->magnitude = mp_obj_new_int(lnk_data.magnitude); + + objects_list->items[i] = o; + } + + return objects_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_circles_obj, 1, py_image_find_circles); +#endif // IMLIB_ENABLE_FIND_CIRCLES + +#ifdef IMLIB_ENABLE_FIND_RECTS +// Rect Object // +#define py_rect_obj_size 5 +typedef struct py_rect_obj { + mp_obj_base_t base; + mp_obj_t corners; + mp_obj_t x, y, w, h, magnitude; +} py_rect_obj_t; + +static void py_rect_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_rect_obj_t *self = self_in; + mp_printf(print, + "{\"x\":%d, \"y\":%d, \"w\":%d, \"h\":%d, \"magnitude\":%d}", + mp_obj_get_int(self->x), + mp_obj_get_int(self->y), + mp_obj_get_int(self->w), + mp_obj_get_int(self->h), + mp_obj_get_int(self->magnitude)); +} + +static mp_obj_t py_rect_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_rect_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_rect_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->x) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_rect_obj_size, index, false)) { + case 0: return self->x; + case 1: return self->y; + case 2: return self->w; + case 3: return self->h; + case 4: return self->magnitude; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_rect_corners(mp_obj_t self_in) { return ((py_rect_obj_t *) self_in)->corners; } +mp_obj_t py_rect_rect(mp_obj_t self_in) +{ + return mp_obj_new_tuple(4, (mp_obj_t []) {((py_rect_obj_t *) self_in)->x, + ((py_rect_obj_t *) self_in)->y, + ((py_rect_obj_t *) self_in)->w, + ((py_rect_obj_t *) self_in)->h}); +} + +mp_obj_t py_rect_x(mp_obj_t self_in) { return ((py_rect_obj_t *) self_in)->x; } +mp_obj_t py_rect_y(mp_obj_t self_in) { return ((py_rect_obj_t *) self_in)->y; } +mp_obj_t py_rect_w(mp_obj_t self_in) { return ((py_rect_obj_t *) self_in)->w; } +mp_obj_t py_rect_h(mp_obj_t self_in) { return ((py_rect_obj_t *) self_in)->h; } +mp_obj_t py_rect_magnitude(mp_obj_t self_in) { return ((py_rect_obj_t *) self_in)->magnitude; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_corners_obj, py_rect_corners); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_rect_obj, py_rect_rect); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_x_obj, py_rect_x); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_y_obj, py_rect_y); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_w_obj, py_rect_w); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_h_obj, py_rect_h); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_rect_magnitude_obj, py_rect_magnitude); + +STATIC const mp_rom_map_elem_t py_rect_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_corners), MP_ROM_PTR(&py_rect_corners_obj) }, + { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&py_rect_rect_obj) }, + { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_rect_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_rect_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_w), MP_ROM_PTR(&py_rect_w_obj) }, + { MP_ROM_QSTR(MP_QSTR_h), MP_ROM_PTR(&py_rect_h_obj) }, + { MP_ROM_QSTR(MP_QSTR_magnitude), MP_ROM_PTR(&py_rect_magnitude_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_rect_locals_dict, py_rect_locals_dict_table); + +static const mp_obj_type_t py_rect_type = { + { &mp_type_type }, + .name = MP_QSTR_rect, + .print = py_rect_print, + .subscr = py_rect_subscr, + .locals_dict = (mp_obj_t) &py_rect_locals_dict +}; + +static mp_obj_t py_image_find_rects(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_image_cobj(args[0]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 1, kw_args, &roi); + + uint32_t threshold = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 1000); + + list_t out; + fb_alloc_mark(); + imlib_find_rects(&out, arg_img, &roi, threshold); + fb_alloc_free_till_mark(); + + mp_obj_list_t *objects_list = mp_obj_new_list(list_size(&out), NULL); + for (size_t i = 0; list_size(&out); i++) { + find_rects_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + py_rect_obj_t *o = m_new_obj(py_rect_obj_t); + o->base.type = &py_rect_type; + o->corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[0].x), mp_obj_new_int(lnk_data.corners[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[1].x), mp_obj_new_int(lnk_data.corners[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[2].x), mp_obj_new_int(lnk_data.corners[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[3].x), mp_obj_new_int(lnk_data.corners[3].y)})}); + o->x = mp_obj_new_int(lnk_data.rect.x); + o->y = mp_obj_new_int(lnk_data.rect.y); + o->w = mp_obj_new_int(lnk_data.rect.w); + o->h = mp_obj_new_int(lnk_data.rect.h); + o->magnitude = mp_obj_new_int(lnk_data.magnitude); + + objects_list->items[i] = o; + } + + return objects_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_rects_obj, 1, py_image_find_rects); +#endif // IMLIB_ENABLE_FIND_RECTS + +#ifdef IMLIB_ENABLE_QRCODES +// QRCode Object // +#define py_qrcode_obj_size 10 +typedef struct py_qrcode_obj { + mp_obj_base_t base; + mp_obj_t corners; + mp_obj_t x, y, w, h, payload, version, ecc_level, mask, data_type, eci; +} py_qrcode_obj_t; + +static void py_qrcode_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_qrcode_obj_t *self = self_in; + mp_printf(print, + "{\"x\":%d, \"y\":%d, \"w\":%d, \"h\":%d, \"payload\":\"%s\"," + " \"version\":%d, \"ecc_level\":%d, \"mask\":%d, \"data_type\":%d, \"eci\":%d}", + mp_obj_get_int(self->x), + mp_obj_get_int(self->y), + mp_obj_get_int(self->w), + mp_obj_get_int(self->h), + mp_obj_str_get_str(self->payload), + mp_obj_get_int(self->version), + mp_obj_get_int(self->ecc_level), + mp_obj_get_int(self->mask), + mp_obj_get_int(self->data_type), + mp_obj_get_int(self->eci)); +} + +static mp_obj_t py_qrcode_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_qrcode_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_qrcode_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->x) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_qrcode_obj_size, index, false)) { + case 0: return self->x; + case 1: return self->y; + case 2: return self->w; + case 3: return self->h; + case 4: return self->payload; + case 5: return self->version; + case 6: return self->ecc_level; + case 7: return self->mask; + case 8: return self->data_type; + case 9: return self->eci; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_qrcode_corners(mp_obj_t self_in) { return ((py_qrcode_obj_t *) self_in)->corners; } +mp_obj_t py_qrcode_rect(mp_obj_t self_in) +{ + return mp_obj_new_tuple(4, (mp_obj_t []) {((py_qrcode_obj_t *) self_in)->x, + ((py_qrcode_obj_t *) self_in)->y, + ((py_qrcode_obj_t *) self_in)->w, + ((py_qrcode_obj_t *) self_in)->h}); +} + +mp_obj_t py_qrcode_x(mp_obj_t self_in) { return ((py_qrcode_obj_t *) self_in)->x; } +mp_obj_t py_qrcode_y(mp_obj_t self_in) { return ((py_qrcode_obj_t *) self_in)->y; } +mp_obj_t py_qrcode_w(mp_obj_t self_in) { return ((py_qrcode_obj_t *) self_in)->w; } +mp_obj_t py_qrcode_h(mp_obj_t self_in) { return ((py_qrcode_obj_t *) self_in)->h; } +mp_obj_t py_qrcode_payload(mp_obj_t self_in) { return ((py_qrcode_obj_t *) self_in)->payload; } +mp_obj_t py_qrcode_version(mp_obj_t self_in) { return ((py_qrcode_obj_t *) self_in)->version; } +mp_obj_t py_qrcode_ecc_level(mp_obj_t self_in) { return ((py_qrcode_obj_t *) self_in)->ecc_level; } +mp_obj_t py_qrcode_mask(mp_obj_t self_in) { return ((py_qrcode_obj_t *) self_in)->mask; } +mp_obj_t py_qrcode_data_type(mp_obj_t self_in) { return ((py_qrcode_obj_t *) self_in)->data_type; } +mp_obj_t py_qrcode_eci(mp_obj_t self_in) { return ((py_qrcode_obj_t *) self_in)->eci; } +mp_obj_t py_qrcode_is_numeric(mp_obj_t self_in) { return mp_obj_new_bool(mp_obj_get_int(((py_qrcode_obj_t *) self_in)->data_type) == 1); } +mp_obj_t py_qrcode_is_alphanumeric(mp_obj_t self_in) { return mp_obj_new_bool(mp_obj_get_int(((py_qrcode_obj_t *) self_in)->data_type) == 2); } +mp_obj_t py_qrcode_is_binary(mp_obj_t self_in) { return mp_obj_new_bool(mp_obj_get_int(((py_qrcode_obj_t *) self_in)->data_type) == 4); } +mp_obj_t py_qrcode_is_kanji(mp_obj_t self_in) { return mp_obj_new_bool(mp_obj_get_int(((py_qrcode_obj_t *) self_in)->data_type) == 8); } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_corners_obj, py_qrcode_corners); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_rect_obj, py_qrcode_rect); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_x_obj, py_qrcode_x); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_y_obj, py_qrcode_y); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_w_obj, py_qrcode_w); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_h_obj, py_qrcode_h); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_payload_obj, py_qrcode_payload); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_version_obj, py_qrcode_version); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_ecc_level_obj, py_qrcode_ecc_level); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_mask_obj, py_qrcode_mask); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_data_type_obj, py_qrcode_data_type); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_eci_obj, py_qrcode_eci); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_is_numeric_obj, py_qrcode_is_numeric); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_is_alphanumeric_obj, py_qrcode_is_alphanumeric); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_is_binary_obj, py_qrcode_is_binary); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_qrcode_is_kanji_obj, py_qrcode_is_kanji); + +STATIC const mp_rom_map_elem_t py_qrcode_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_corners), MP_ROM_PTR(&py_qrcode_corners_obj) }, + { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&py_qrcode_rect_obj) }, + { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_qrcode_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_qrcode_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_w), MP_ROM_PTR(&py_qrcode_w_obj) }, + { MP_ROM_QSTR(MP_QSTR_h), MP_ROM_PTR(&py_qrcode_h_obj) }, + { MP_ROM_QSTR(MP_QSTR_payload), MP_ROM_PTR(&py_qrcode_payload_obj) }, + { MP_ROM_QSTR(MP_QSTR_version), MP_ROM_PTR(&py_qrcode_version_obj) }, + { MP_ROM_QSTR(MP_QSTR_ecc_level), MP_ROM_PTR(&py_qrcode_ecc_level_obj) }, + { MP_ROM_QSTR(MP_QSTR_mask), MP_ROM_PTR(&py_qrcode_mask_obj) }, + { MP_ROM_QSTR(MP_QSTR_data_type), MP_ROM_PTR(&py_qrcode_data_type_obj) }, + { MP_ROM_QSTR(MP_QSTR_eci), MP_ROM_PTR(&py_qrcode_eci_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_numeric), MP_ROM_PTR(&py_qrcode_is_numeric_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_alphanumeric), MP_ROM_PTR(&py_qrcode_is_alphanumeric_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_binary), MP_ROM_PTR(&py_qrcode_is_binary_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_kanji), MP_ROM_PTR(&py_qrcode_is_kanji_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_qrcode_locals_dict, py_qrcode_locals_dict_table); + +static const mp_obj_type_t py_qrcode_type = { + { &mp_type_type }, + .name = MP_QSTR_qrcode, + .print = py_qrcode_print, + .subscr = py_qrcode_subscr, + .locals_dict = (mp_obj_t) &py_qrcode_locals_dict +}; + +static mp_obj_t py_image_find_qrcodes(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_image_cobj(args[0]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 1, kw_args, &roi); + + list_t out; + fb_alloc_mark(); + imlib_find_qrcodes(&out, arg_img, &roi); + fb_alloc_free_till_mark(); + + mp_obj_list_t *objects_list = mp_obj_new_list(list_size(&out), NULL); + for (size_t i = 0; list_size(&out); i++) { + find_qrcodes_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + py_qrcode_obj_t *o = m_new_obj(py_qrcode_obj_t); + o->base.type = &py_qrcode_type; + o->corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[0].x), mp_obj_new_int(lnk_data.corners[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[1].x), mp_obj_new_int(lnk_data.corners[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[2].x), mp_obj_new_int(lnk_data.corners[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[3].x), mp_obj_new_int(lnk_data.corners[3].y)})}); + o->x = mp_obj_new_int(lnk_data.rect.x); + o->y = mp_obj_new_int(lnk_data.rect.y); + o->w = mp_obj_new_int(lnk_data.rect.w); + o->h = mp_obj_new_int(lnk_data.rect.h); + o->payload = mp_obj_new_str(lnk_data.payload, lnk_data.payload_len); + o->version = mp_obj_new_int(lnk_data.version); + o->ecc_level = mp_obj_new_int(lnk_data.ecc_level); + o->mask = mp_obj_new_int(lnk_data.mask); + o->data_type = mp_obj_new_int(lnk_data.data_type); + o->eci = mp_obj_new_int(lnk_data.eci); + + objects_list->items[i] = o; + xfree(lnk_data.payload); + } + + return objects_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_qrcodes_obj, 1, py_image_find_qrcodes); +#endif // IMLIB_ENABLE_QRCODES + +#ifdef IMLIB_ENABLE_APRILTAGS +// AprilTag Object // +#define py_apriltag_obj_size 18 +typedef struct py_apriltag_obj { + mp_obj_base_t base; + mp_obj_t corners; + mp_obj_t x, y, w, h, id, family, cx, cy, rotation, decision_margin, hamming, goodness; + mp_obj_t x_translation, y_translation, z_translation; + mp_obj_t x_rotation, y_rotation, z_rotation; +} py_apriltag_obj_t; + +static void py_apriltag_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_apriltag_obj_t *self = self_in; + mp_printf(print, + "{\"x\":%d, \"y\":%d, \"w\":%d, \"h\":%d, \"id\":%d," + " \"family\":%d, \"cx\":%d, \"cy\":%d, \"rotation\":%f, \"decision_margin\":%f, \"hamming\":%d, \"goodness\":%f," + " \"x_translation\":%f, \"y_translation\":%f, \"z_translation\":%f," + " \"x_rotation\":%f, \"y_rotation\":%f, \"z_rotation\":%f}", + mp_obj_get_int(self->x), + mp_obj_get_int(self->y), + mp_obj_get_int(self->w), + mp_obj_get_int(self->h), + mp_obj_get_int(self->id), + mp_obj_get_int(self->family), + mp_obj_get_int(self->cx), + mp_obj_get_int(self->cy), + (double) mp_obj_get_float(self->rotation), + (double) mp_obj_get_float(self->decision_margin), + mp_obj_get_int(self->hamming), + (double) mp_obj_get_float(self->goodness), + (double) mp_obj_get_float(self->x_translation), + (double) mp_obj_get_float(self->y_translation), + (double) mp_obj_get_float(self->z_translation), + (double) mp_obj_get_float(self->x_rotation), + (double) mp_obj_get_float(self->y_rotation), + (double) mp_obj_get_float(self->z_rotation)); +} + +static mp_obj_t py_apriltag_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_apriltag_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_apriltag_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->x) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_apriltag_obj_size, index, false)) { + case 0: return self->x; + case 1: return self->y; + case 2: return self->w; + case 3: return self->h; + case 4: return self->id; + case 5: return self->family; + case 6: return self->cx; + case 7: return self->cy; + case 8: return self->rotation; + case 9: return self->decision_margin; + case 10: return self->hamming; + case 11: return self->goodness; + case 12: return self->x_translation; + case 13: return self->y_translation; + case 14: return self->z_translation; + case 15: return self->x_rotation; + case 16: return self->y_rotation; + case 17: return self->z_rotation; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_apriltag_corners(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->corners; } +mp_obj_t py_apriltag_rect(mp_obj_t self_in) +{ + return mp_obj_new_tuple(4, (mp_obj_t []) {((py_apriltag_obj_t *) self_in)->x, + ((py_apriltag_obj_t *) self_in)->y, + ((py_apriltag_obj_t *) self_in)->w, + ((py_apriltag_obj_t *) self_in)->h}); +} + +mp_obj_t py_apriltag_x(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->x; } +mp_obj_t py_apriltag_y(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->y; } +mp_obj_t py_apriltag_w(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->w; } +mp_obj_t py_apriltag_h(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->h; } +mp_obj_t py_apriltag_id(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->id; } +mp_obj_t py_apriltag_family(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->family; } +mp_obj_t py_apriltag_cx(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->cx; } +mp_obj_t py_apriltag_cy(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->cy; } +mp_obj_t py_apriltag_rotation(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->rotation; } +mp_obj_t py_apriltag_decision_margin(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->decision_margin; } +mp_obj_t py_apriltag_hamming(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->hamming; } +mp_obj_t py_apriltag_goodness(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->goodness; } +mp_obj_t py_apriltag_x_translation(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->x_translation; } +mp_obj_t py_apriltag_y_translation(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->y_translation; } +mp_obj_t py_apriltag_z_translation(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->z_translation; } +mp_obj_t py_apriltag_x_rotation(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->x_rotation; } +mp_obj_t py_apriltag_y_rotation(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->y_rotation; } +mp_obj_t py_apriltag_z_rotation(mp_obj_t self_in) { return ((py_apriltag_obj_t *) self_in)->z_rotation; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_corners_obj, py_apriltag_corners); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_rect_obj, py_apriltag_rect); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_x_obj, py_apriltag_x); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_y_obj, py_apriltag_y); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_w_obj, py_apriltag_w); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_h_obj, py_apriltag_h); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_id_obj, py_apriltag_id); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_family_obj, py_apriltag_family); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_cx_obj, py_apriltag_cx); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_cy_obj, py_apriltag_cy); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_rotation_obj, py_apriltag_rotation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_decision_margin_obj, py_apriltag_decision_margin); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_hamming_obj, py_apriltag_hamming); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_goodness_obj, py_apriltag_goodness); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_x_translation_obj, py_apriltag_x_translation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_y_translation_obj, py_apriltag_y_translation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_z_translation_obj, py_apriltag_z_translation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_x_rotation_obj, py_apriltag_x_rotation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_y_rotation_obj, py_apriltag_y_rotation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_apriltag_z_rotation_obj, py_apriltag_z_rotation); + +STATIC const mp_rom_map_elem_t py_apriltag_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_corners), MP_ROM_PTR(&py_apriltag_corners_obj) }, + { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&py_apriltag_rect_obj) }, + { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_apriltag_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_apriltag_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_w), MP_ROM_PTR(&py_apriltag_w_obj) }, + { MP_ROM_QSTR(MP_QSTR_h), MP_ROM_PTR(&py_apriltag_h_obj) }, + { MP_ROM_QSTR(MP_QSTR_id), MP_ROM_PTR(&py_apriltag_id_obj) }, + { MP_ROM_QSTR(MP_QSTR_family), MP_ROM_PTR(&py_apriltag_family_obj) }, + { MP_ROM_QSTR(MP_QSTR_cx), MP_ROM_PTR(&py_apriltag_cx_obj) }, + { MP_ROM_QSTR(MP_QSTR_cy), MP_ROM_PTR(&py_apriltag_cy_obj) }, + { MP_ROM_QSTR(MP_QSTR_rotation), MP_ROM_PTR(&py_apriltag_rotation_obj) }, + { MP_ROM_QSTR(MP_QSTR_decision_margin), MP_ROM_PTR(&py_apriltag_decision_margin_obj) }, + { MP_ROM_QSTR(MP_QSTR_hamming), MP_ROM_PTR(&py_apriltag_hamming_obj) }, + { MP_ROM_QSTR(MP_QSTR_goodness), MP_ROM_PTR(&py_apriltag_goodness_obj) }, + { MP_ROM_QSTR(MP_QSTR_x_translation), MP_ROM_PTR(&py_apriltag_x_translation_obj) }, + { MP_ROM_QSTR(MP_QSTR_y_translation), MP_ROM_PTR(&py_apriltag_y_translation_obj) }, + { MP_ROM_QSTR(MP_QSTR_z_translation), MP_ROM_PTR(&py_apriltag_z_translation_obj) }, + { MP_ROM_QSTR(MP_QSTR_x_rotation), MP_ROM_PTR(&py_apriltag_x_rotation_obj) }, + { MP_ROM_QSTR(MP_QSTR_y_rotation), MP_ROM_PTR(&py_apriltag_y_rotation_obj) }, + { MP_ROM_QSTR(MP_QSTR_z_rotation), MP_ROM_PTR(&py_apriltag_z_rotation_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_apriltag_locals_dict, py_apriltag_locals_dict_table); + +static const mp_obj_type_t py_apriltag_type = { + { &mp_type_type }, + .name = MP_QSTR_apriltag, + .print = py_apriltag_print, + .subscr = py_apriltag_subscr, + .locals_dict = (mp_obj_t) &py_apriltag_locals_dict +}; + +static mp_obj_t py_image_find_apriltags(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_image_cobj(args[0]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 1, kw_args, &roi); +#ifndef IMLIB_ENABLE_HIGH_RES_APRILTAGS + PY_ASSERT_TRUE_MSG((roi.w * roi.h) < 65536, "The maximum supported resolution for find_apriltags() is < 64K pixels."); +#endif + if ((roi.w < 4) || (roi.h < 4)) { + return mp_obj_new_list(0, NULL); + } + + apriltag_families_t families = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_families), TAG36H11); + // 2.8mm Focal Length w/ OV7725 sensor for reference. + float fx = py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fx), (2.8 / 3.984) * arg_img->w); + // 2.8mm Focal Length w/ OV7725 sensor for reference. + float fy = py_helper_keyword_float(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fy), (2.8 / 2.952) * arg_img->h); + // Use the image versus the roi here since the image should be projected from the camera center. + float cx = py_helper_keyword_float(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_cx), arg_img->w * 0.5); + // Use the image versus the roi here since the image should be projected from the camera center. + float cy = py_helper_keyword_float(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_cy), arg_img->h * 0.5); + + list_t out; + fb_alloc_mark(); + imlib_find_apriltags(&out, arg_img, &roi, families, fx, fy, cx, cy); + fb_alloc_free_till_mark(); + + mp_obj_list_t *objects_list = mp_obj_new_list(list_size(&out), NULL); + for (size_t i = 0; list_size(&out); i++) { + find_apriltags_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + py_apriltag_obj_t *o = m_new_obj(py_apriltag_obj_t); + o->base.type = &py_apriltag_type; + o->corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[0].x), mp_obj_new_int(lnk_data.corners[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[1].x), mp_obj_new_int(lnk_data.corners[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[2].x), mp_obj_new_int(lnk_data.corners[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[3].x), mp_obj_new_int(lnk_data.corners[3].y)})}); + o->x = mp_obj_new_int(lnk_data.rect.x); + o->y = mp_obj_new_int(lnk_data.rect.y); + o->w = mp_obj_new_int(lnk_data.rect.w); + o->h = mp_obj_new_int(lnk_data.rect.h); + o->id = mp_obj_new_int(lnk_data.id); + o->family = mp_obj_new_int(lnk_data.family); + o->cx = mp_obj_new_int(lnk_data.centroid.x); + o->cy = mp_obj_new_int(lnk_data.centroid.y); + o->rotation = mp_obj_new_float(lnk_data.z_rotation); + o->decision_margin = mp_obj_new_float(lnk_data.decision_margin); + o->hamming = mp_obj_new_int(lnk_data.hamming); + o->goodness = mp_obj_new_float(lnk_data.goodness); + o->x_translation = mp_obj_new_float(lnk_data.x_translation); + o->y_translation = mp_obj_new_float(lnk_data.y_translation); + o->z_translation = mp_obj_new_float(lnk_data.z_translation); + o->x_rotation = mp_obj_new_float(lnk_data.x_rotation); + o->y_rotation = mp_obj_new_float(lnk_data.y_rotation); + o->z_rotation = mp_obj_new_float(lnk_data.z_rotation); + + objects_list->items[i] = o; + } + + return objects_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_apriltags_obj, 1, py_image_find_apriltags); +#endif // IMLIB_ENABLE_APRILTAGS + +#ifdef IMLIB_ENABLE_DATAMATRICES +// DataMatrix Object // +#define py_datamatrix_obj_size 10 +typedef struct py_datamatrix_obj { + mp_obj_base_t base; + mp_obj_t corners; + mp_obj_t x, y, w, h, payload, rotation, rows, columns, capacity, padding; +} py_datamatrix_obj_t; + +static void py_datamatrix_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_datamatrix_obj_t *self = self_in; + mp_printf(print, + "{\"x\":%d, \"y\":%d, \"w\":%d, \"h\":%d, \"payload\":\"%s\"," + " \"rotation\":%f, \"rows\":%d, \"columns\":%d, \"capacity\":%d, \"padding\":%d}", + mp_obj_get_int(self->x), + mp_obj_get_int(self->y), + mp_obj_get_int(self->w), + mp_obj_get_int(self->h), + mp_obj_str_get_str(self->payload), + (double) mp_obj_get_float(self->rotation), + mp_obj_get_int(self->rows), + mp_obj_get_int(self->columns), + mp_obj_get_int(self->capacity), + mp_obj_get_int(self->padding)); +} + +static mp_obj_t py_datamatrix_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_datamatrix_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_datamatrix_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->x) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_datamatrix_obj_size, index, false)) { + case 0: return self->x; + case 1: return self->y; + case 2: return self->w; + case 3: return self->h; + case 4: return self->payload; + case 5: return self->rotation; + case 6: return self->rows; + case 7: return self->columns; + case 8: return self->capacity; + case 9: return self->padding; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_datamatrix_corners(mp_obj_t self_in) { return ((py_datamatrix_obj_t *) self_in)->corners; } +mp_obj_t py_datamatrix_rect(mp_obj_t self_in) +{ + return mp_obj_new_tuple(4, (mp_obj_t []) {((py_datamatrix_obj_t *) self_in)->x, + ((py_datamatrix_obj_t *) self_in)->y, + ((py_datamatrix_obj_t *) self_in)->w, + ((py_datamatrix_obj_t *) self_in)->h}); +} + +mp_obj_t py_datamatrix_x(mp_obj_t self_in) { return ((py_datamatrix_obj_t *) self_in)->x; } +mp_obj_t py_datamatrix_y(mp_obj_t self_in) { return ((py_datamatrix_obj_t *) self_in)->y; } +mp_obj_t py_datamatrix_w(mp_obj_t self_in) { return ((py_datamatrix_obj_t *) self_in)->w; } +mp_obj_t py_datamatrix_h(mp_obj_t self_in) { return ((py_datamatrix_obj_t *) self_in)->h; } +mp_obj_t py_datamatrix_payload(mp_obj_t self_in) { return ((py_datamatrix_obj_t *) self_in)->payload; } +mp_obj_t py_datamatrix_rotation(mp_obj_t self_in) { return ((py_datamatrix_obj_t *) self_in)->rotation; } +mp_obj_t py_datamatrix_rows(mp_obj_t self_in) { return ((py_datamatrix_obj_t *) self_in)->rows; } +mp_obj_t py_datamatrix_columns(mp_obj_t self_in) { return ((py_datamatrix_obj_t *) self_in)->columns; } +mp_obj_t py_datamatrix_capacity(mp_obj_t self_in) { return ((py_datamatrix_obj_t *) self_in)->capacity; } +mp_obj_t py_datamatrix_padding(mp_obj_t self_in) { return ((py_datamatrix_obj_t *) self_in)->padding; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_corners_obj, py_datamatrix_corners); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_rect_obj, py_datamatrix_rect); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_x_obj, py_datamatrix_x); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_y_obj, py_datamatrix_y); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_w_obj, py_datamatrix_w); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_h_obj, py_datamatrix_h); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_payload_obj, py_datamatrix_payload); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_rotation_obj, py_datamatrix_rotation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_rows_obj, py_datamatrix_rows); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_columns_obj, py_datamatrix_columns); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_capacity_obj, py_datamatrix_capacity); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_datamatrix_padding_obj, py_datamatrix_padding); + +STATIC const mp_rom_map_elem_t py_datamatrix_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_corners), MP_ROM_PTR(&py_datamatrix_corners_obj) }, + { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&py_datamatrix_rect_obj) }, + { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_datamatrix_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_datamatrix_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_w), MP_ROM_PTR(&py_datamatrix_w_obj) }, + { MP_ROM_QSTR(MP_QSTR_h), MP_ROM_PTR(&py_datamatrix_h_obj) }, + { MP_ROM_QSTR(MP_QSTR_payload), MP_ROM_PTR(&py_datamatrix_payload_obj) }, + { MP_ROM_QSTR(MP_QSTR_rotation), MP_ROM_PTR(&py_datamatrix_rotation_obj) }, + { MP_ROM_QSTR(MP_QSTR_rows), MP_ROM_PTR(&py_datamatrix_rows_obj) }, + { MP_ROM_QSTR(MP_QSTR_columns), MP_ROM_PTR(&py_datamatrix_columns_obj) }, + { MP_ROM_QSTR(MP_QSTR_capacity), MP_ROM_PTR(&py_datamatrix_capacity_obj) }, + { MP_ROM_QSTR(MP_QSTR_padding), MP_ROM_PTR(&py_datamatrix_padding_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_datamatrix_locals_dict, py_datamatrix_locals_dict_table); + +static const mp_obj_type_t py_datamatrix_type = { + { &mp_type_type }, + .name = MP_QSTR_datamatrix, + .print = py_datamatrix_print, + .subscr = py_datamatrix_subscr, + .locals_dict = (mp_obj_t) &py_datamatrix_locals_dict +}; + +static mp_obj_t py_image_find_datamatrices(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_image_cobj(args[0]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 1, kw_args, &roi); + + int effort = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_effort), 200); + + list_t out; + fb_alloc_mark(); + imlib_find_datamatrices(&out, arg_img, &roi, effort); + fb_alloc_free_till_mark(); + + mp_obj_list_t *objects_list = mp_obj_new_list(list_size(&out), NULL); + for (size_t i = 0; list_size(&out); i++) { + find_datamatrices_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + py_datamatrix_obj_t *o = m_new_obj(py_datamatrix_obj_t); + o->base.type = &py_datamatrix_type; + o->corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[0].x), mp_obj_new_int(lnk_data.corners[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[1].x), mp_obj_new_int(lnk_data.corners[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[2].x), mp_obj_new_int(lnk_data.corners[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[3].x), mp_obj_new_int(lnk_data.corners[3].y)})}); + o->x = mp_obj_new_int(lnk_data.rect.x); + o->y = mp_obj_new_int(lnk_data.rect.y); + o->w = mp_obj_new_int(lnk_data.rect.w); + o->h = mp_obj_new_int(lnk_data.rect.h); + o->payload = mp_obj_new_str(lnk_data.payload, lnk_data.payload_len); + o->rotation = mp_obj_new_float(IM_DEG2RAD(lnk_data.rotation)); + o->rows = mp_obj_new_int(lnk_data.rows); + o->columns = mp_obj_new_int(lnk_data.columns); + o->capacity = mp_obj_new_int(lnk_data.capacity); + o->padding = mp_obj_new_int(lnk_data.padding); + + objects_list->items[i] = o; + xfree(lnk_data.payload); + } + + return objects_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_datamatrices_obj, 1, py_image_find_datamatrices); +#endif // IMLIB_ENABLE_DATAMATRICES + +#ifdef IMLIB_ENABLE_BARCODES +// BarCode Object // +#define py_barcode_obj_size 8 +typedef struct py_barcode_obj { + mp_obj_base_t base; + mp_obj_t corners; + mp_obj_t x, y, w, h, payload, type, rotation, quality; +} py_barcode_obj_t; + +static void py_barcode_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_barcode_obj_t *self = self_in; + mp_printf(print, + "{\"x\":%d, \"y\":%d, \"w\":%d, \"h\":%d, \"payload\":\"%s\"," + " \"type\":%d, \"rotation\":%f, \"quality\":%d}", + mp_obj_get_int(self->x), + mp_obj_get_int(self->y), + mp_obj_get_int(self->w), + mp_obj_get_int(self->h), + mp_obj_str_get_str(self->payload), + mp_obj_get_int(self->type), + (double) mp_obj_get_float(self->rotation), + mp_obj_get_int(self->quality)); +} + +static mp_obj_t py_barcode_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_barcode_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_barcode_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->x) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_barcode_obj_size, index, false)) { + case 0: return self->x; + case 1: return self->y; + case 2: return self->w; + case 3: return self->h; + case 4: return self->payload; + case 5: return self->type; + case 6: return self->rotation; + case 7: return self->quality; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_barcode_corners(mp_obj_t self_in) { return ((py_barcode_obj_t *) self_in)->corners; } +mp_obj_t py_barcode_rect(mp_obj_t self_in) +{ + return mp_obj_new_tuple(4, (mp_obj_t []) {((py_barcode_obj_t *) self_in)->x, + ((py_barcode_obj_t *) self_in)->y, + ((py_barcode_obj_t *) self_in)->w, + ((py_barcode_obj_t *) self_in)->h}); +} + +mp_obj_t py_barcode_x(mp_obj_t self_in) { return ((py_barcode_obj_t *) self_in)->x; } +mp_obj_t py_barcode_y(mp_obj_t self_in) { return ((py_barcode_obj_t *) self_in)->y; } +mp_obj_t py_barcode_w(mp_obj_t self_in) { return ((py_barcode_obj_t *) self_in)->w; } +mp_obj_t py_barcode_h(mp_obj_t self_in) { return ((py_barcode_obj_t *) self_in)->h; } +mp_obj_t py_barcode_payload_fun(mp_obj_t self_in) { return ((py_barcode_obj_t *) self_in)->payload; } +mp_obj_t py_barcode_type_fun(mp_obj_t self_in) { return ((py_barcode_obj_t *) self_in)->type; } +mp_obj_t py_barcode_rotation_fun(mp_obj_t self_in) { return ((py_barcode_obj_t *) self_in)->rotation; } +mp_obj_t py_barcode_quality_fun(mp_obj_t self_in) { return ((py_barcode_obj_t *) self_in)->quality; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_corners_obj, py_barcode_corners); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_rect_obj, py_barcode_rect); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_x_obj, py_barcode_x); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_y_obj, py_barcode_y); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_w_obj, py_barcode_w); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_h_obj, py_barcode_h); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_payload_fun_obj, py_barcode_payload_fun); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_type_fun_obj, py_barcode_type_fun); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_rotation_fun_obj, py_barcode_rotation_fun); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_barcode_quality_fun_obj, py_barcode_quality_fun); + +STATIC const mp_rom_map_elem_t py_barcode_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_corners), MP_ROM_PTR(&py_barcode_corners_obj) }, + { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&py_barcode_rect_obj) }, + { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_barcode_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_barcode_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_w), MP_ROM_PTR(&py_barcode_w_obj) }, + { MP_ROM_QSTR(MP_QSTR_h), MP_ROM_PTR(&py_barcode_h_obj) }, + { MP_ROM_QSTR(MP_QSTR_payload), MP_ROM_PTR(&py_barcode_payload_fun_obj) }, + { MP_ROM_QSTR(MP_QSTR_type), MP_ROM_PTR(&py_barcode_type_fun_obj) }, + { MP_ROM_QSTR(MP_QSTR_rotation), MP_ROM_PTR(&py_barcode_rotation_fun_obj) }, + { MP_ROM_QSTR(MP_QSTR_quality), MP_ROM_PTR(&py_barcode_quality_fun_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_barcode_locals_dict, py_barcode_locals_dict_table); + +static const mp_obj_type_t py_barcode_type = { + { &mp_type_type }, + .name = MP_QSTR_barcode, + .print = py_barcode_print, + .subscr = py_barcode_subscr, + .locals_dict = (mp_obj_t) &py_barcode_locals_dict +}; + +static mp_obj_t py_image_find_barcodes(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_image_cobj(args[0]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 1, kw_args, &roi); + + list_t out; + fb_alloc_mark(); + imlib_find_barcodes(&out, arg_img, &roi); + fb_alloc_free_till_mark(); + + mp_obj_list_t *objects_list = mp_obj_new_list(list_size(&out), NULL); + for (size_t i = 0; list_size(&out); i++) { + find_barcodes_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + py_barcode_obj_t *o = m_new_obj(py_barcode_obj_t); + o->base.type = &py_barcode_type; + o->corners = mp_obj_new_tuple(4, (mp_obj_t []) + {mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[0].x), mp_obj_new_int(lnk_data.corners[0].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[1].x), mp_obj_new_int(lnk_data.corners[1].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[2].x), mp_obj_new_int(lnk_data.corners[2].y)}), + mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_int(lnk_data.corners[3].x), mp_obj_new_int(lnk_data.corners[3].y)})}); + o->x = mp_obj_new_int(lnk_data.rect.x); + o->y = mp_obj_new_int(lnk_data.rect.y); + o->w = mp_obj_new_int(lnk_data.rect.w); + o->h = mp_obj_new_int(lnk_data.rect.h); + o->payload = mp_obj_new_str(lnk_data.payload, lnk_data.payload_len); + o->type = mp_obj_new_int(lnk_data.type); + o->rotation = mp_obj_new_float(IM_DEG2RAD(lnk_data.rotation)); + o->quality = mp_obj_new_int(lnk_data.quality); + + objects_list->items[i] = o; + xfree(lnk_data.payload); + } + + return objects_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_barcodes_obj, 1, py_image_find_barcodes); +#endif // IMLIB_ENABLE_BARCODES + +#ifdef IMLIB_ENABLE_FIND_DISPLACEMENT +// Displacement Object // +#define py_displacement_obj_size 5 +typedef struct py_displacement_obj { + mp_obj_base_t base; + mp_obj_t x_translation, y_translation, rotation, scale, response; +} py_displacement_obj_t; + +static void py_displacement_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_displacement_obj_t *self = self_in; + mp_printf(print, + "{\"x_translation\":%f, \"y_translation\":%f, \"rotation\":%f, \"scale\":%f, \"response\":%f}", + (double) mp_obj_get_float(self->x_translation), + (double) mp_obj_get_float(self->y_translation), + (double) mp_obj_get_float(self->rotation), + (double) mp_obj_get_float(self->scale), + (double) mp_obj_get_float(self->response)); +} + +static mp_obj_t py_displacement_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_displacement_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_displacement_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->x_translation) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_displacement_obj_size, index, false)) { + case 0: return self->x_translation; + case 1: return self->y_translation; + case 2: return self->rotation; + case 3: return self->scale; + case 4: return self->response; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_displacement_x_translation(mp_obj_t self_in) { return ((py_displacement_obj_t *) self_in)->x_translation; } +mp_obj_t py_displacement_y_translation(mp_obj_t self_in) { return ((py_displacement_obj_t *) self_in)->y_translation; } +mp_obj_t py_displacement_rotation(mp_obj_t self_in) { return ((py_displacement_obj_t *) self_in)->rotation; } +mp_obj_t py_displacement_scale(mp_obj_t self_in) { return ((py_displacement_obj_t *) self_in)->scale; } +mp_obj_t py_displacement_response(mp_obj_t self_in) { return ((py_displacement_obj_t *) self_in)->response; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_displacement_x_translation_obj, py_displacement_x_translation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_displacement_y_translation_obj, py_displacement_y_translation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_displacement_rotation_obj, py_displacement_rotation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_displacement_scale_obj, py_displacement_scale); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_displacement_response_obj, py_displacement_response); + +STATIC const mp_rom_map_elem_t py_displacement_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_x_translation), MP_ROM_PTR(&py_displacement_x_translation_obj) }, + { MP_ROM_QSTR(MP_QSTR_y_translation), MP_ROM_PTR(&py_displacement_y_translation_obj) }, + { MP_ROM_QSTR(MP_QSTR_rotation), MP_ROM_PTR(&py_displacement_rotation_obj) }, + { MP_ROM_QSTR(MP_QSTR_scale), MP_ROM_PTR(&py_displacement_scale_obj) }, + { MP_ROM_QSTR(MP_QSTR_response), MP_ROM_PTR(&py_displacement_response_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_displacement_locals_dict, py_displacement_locals_dict_table); + +static const mp_obj_type_t py_displacement_type = { + { &mp_type_type }, + .name = MP_QSTR_displacement, + .print = py_displacement_print, + .subscr = py_displacement_subscr, + .locals_dict = (mp_obj_t) &py_displacement_locals_dict +}; + +static mp_obj_t py_image_find_displacement(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + image_t *arg_template_img = py_helper_arg_to_image_mutable(args[1]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 2, kw_args, &roi); + + rectangle_t template_roi; + py_helper_keyword_rectangle(arg_template_img, n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_template_roi), &template_roi); + + PY_ASSERT_FALSE_MSG((roi.w != template_roi.w) || (roi.h != template_roi.h), "ROI(w,h) != TEMPLATE_ROI(w,h)"); + + bool logpolar = py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_logpolar), false); + bool fix_rotation_scale = py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_fix_rotation_scale), false); + + float x, y, r, s, response; + fb_alloc_mark(); + imlib_phasecorrelate(arg_img, arg_template_img, &roi, &template_roi, logpolar, fix_rotation_scale, &x, &y, &r, &s, &response); + fb_alloc_free_till_mark(); + + py_displacement_obj_t *o = m_new_obj(py_displacement_obj_t); + o->base.type = &py_displacement_type; + o->x_translation = mp_obj_new_float(x); + o->y_translation = mp_obj_new_float(y); + o->rotation = mp_obj_new_float(r); + o->scale = mp_obj_new_float(s); + o->response = mp_obj_new_float(response); + + return o; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_displacement_obj, 2, py_image_find_displacement); +#endif // IMLIB_ENABLE_FIND_DISPLACEMENT + +#ifdef IMLIB_FIND_TEMPLATE +static mp_obj_t py_image_find_template(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_grayscale(args[0]); + image_t *arg_template = py_helper_arg_to_image_grayscale(args[1]); + float arg_thresh = mp_obj_get_float(args[2]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 3, kw_args, &roi); + + // Make sure ROI is bigger than or equal to template size + PY_ASSERT_TRUE_MSG((roi.w >= arg_template->w && roi.h >= arg_template->h), + "Region of interest is smaller than template!"); + + // Make sure ROI is smaller than or equal to image size + PY_ASSERT_TRUE_MSG(((roi.x + roi.w) <= arg_img->w && (roi.y + roi.h) <= arg_img->h), + "Region of interest is bigger than image!"); + + int step = py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_step), 2); + int search = py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_search), SEARCH_EX); + + // Find template + rectangle_t r; + float corr; + fb_alloc_mark(); + if (search == SEARCH_DS) { + corr = imlib_template_match_ds(arg_img, arg_template, &r); + } else { + corr = imlib_template_match_ex(arg_img, arg_template, &roi, step, &r); + } + fb_alloc_free_till_mark(); + + if (corr > arg_thresh) { + mp_obj_t rec_obj[4] = { + mp_obj_new_int(r.x), + mp_obj_new_int(r.y), + mp_obj_new_int(r.w), + mp_obj_new_int(r.h) + }; + return mp_obj_new_tuple(4, rec_obj); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_template_obj, 3, py_image_find_template); +#endif // IMLIB_FIND_TEMPLATE + +static mp_obj_t py_image_find_features(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + cascade_t *cascade = py_cascade_cobj(args[1]); + cascade->threshold = py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 0.5f); + cascade->scale_factor = py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_scale_factor), 1.5f); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 4, kw_args, &roi); + + // Make sure ROI is bigger than feature size + PY_ASSERT_TRUE_MSG((roi.w > cascade->window.w && roi.h > cascade->window.h), + "Region of interest is smaller than detector window!"); + + // Detect objects + fb_alloc_mark(); + array_t *objects_array = imlib_detect_objects(arg_img, cascade, &roi); + fb_alloc_free_till_mark(); + + // Add detected objects to a new Python list... + mp_obj_t objects_list = mp_obj_new_list(0, NULL); + for (int i=0; ix), + mp_obj_new_int(r->y), + mp_obj_new_int(r->w), + mp_obj_new_int(r->h), + }; + mp_obj_list_append(objects_list, mp_obj_new_tuple(4, rec_obj)); + } + array_free(objects_array); + return objects_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_features_obj, 2, py_image_find_features); + +static mp_obj_t py_image_find_eye(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_grayscale(args[0]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 1, kw_args, &roi); + + point_t iris; + imlib_find_iris(arg_img, &iris, &roi); + + mp_obj_t eye_obj[2] = { + mp_obj_new_int(iris.x), + mp_obj_new_int(iris.y), + }; + + return mp_obj_new_tuple(2, eye_obj); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_eye_obj, 2, py_image_find_eye); + +#ifdef IMLIB_ENABLE_FIND_LBP +static mp_obj_t py_image_find_lbp(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_grayscale(args[0]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 1, kw_args, &roi); + + py_lbp_obj_t *lbp_obj = m_new_obj(py_lbp_obj_t); + lbp_obj->base.type = &py_lbp_type; + lbp_obj->hist = imlib_lbp_desc(arg_img, &roi); + return lbp_obj; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_lbp_obj, 2, py_image_find_lbp); +#endif // IMLIB_ENABLE_FIND_LBP + +#ifdef IMLIB_ENABLE_FIND_KEYPOINTS +static mp_obj_t py_image_find_keypoints(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_mutable(args[0]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 1, kw_args, &roi); + + int threshold = + py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 20); + bool normalized = + py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_normalized), false); + float scale_factor = + py_helper_keyword_float(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_scale_factor), 1.5f); + int max_keypoints = + py_helper_keyword_int(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_max_keypoints), 100); + corner_detector_t corner_detector = + py_helper_keyword_int(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_corner_detector), CORNER_AGAST); + + #ifndef IMLIB_ENABLE_FAST + // Force AGAST when FAST is disabled. + corner_detector = CORNER_AGAST; + #endif + + // Find keypoints + fb_alloc_mark(); + array_t *kpts = orb_find_keypoints(arg_img, normalized, threshold, scale_factor, max_keypoints, corner_detector, &roi); + fb_alloc_free_till_mark(); + + if (array_length(kpts)) { + py_kp_obj_t *kp_obj = m_new_obj(py_kp_obj_t); + kp_obj->base.type = &py_kp_type; + kp_obj->kpts = kpts; + kp_obj->threshold = threshold; + kp_obj->normalized = normalized; + return kp_obj; + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_keypoints_obj, 1, py_image_find_keypoints); +#endif // IMLIB_ENABLE_FIND_KEYPOINTS + +#ifdef IMLIB_ENABLE_BINARY_OPS +static mp_obj_t py_image_find_edges(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_grayscale(args[0]); + edge_detector_t edge_type = mp_obj_get_int(args[1]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 2, kw_args, &roi); + + int thresh[2] = {100, 200}; + mp_obj_t thresh_obj = py_helper_keyword_object(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), NULL); + + if (thresh_obj) { + mp_obj_t *thresh_array; + mp_obj_get_array_fixed_n(thresh_obj, 2, &thresh_array); + thresh[0] = mp_obj_get_int(thresh_array[0]); + thresh[1] = mp_obj_get_int(thresh_array[1]); + } + + switch (edge_type) { + case EDGE_SIMPLE: { + fb_alloc_mark(); + imlib_edge_simple(arg_img, &roi, thresh[0], thresh[1]); + fb_alloc_free_till_mark(); + break; + } + case EDGE_CANNY: { + fb_alloc_mark(); + imlib_edge_canny(arg_img, &roi, thresh[0], thresh[1]); + fb_alloc_free_till_mark(); + break; + } + + } + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_edges_obj, 2, py_image_find_edges); +#endif + +#ifdef IMLIB_ENABLE_HOG +static mp_obj_t py_image_find_hog(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *arg_img = py_helper_arg_to_image_grayscale(args[0]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 1, kw_args, &roi); + + int size = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 8); + + fb_alloc_mark(); + imlib_find_hog(arg_img, &roi, size); + fb_alloc_free_till_mark(); + + return args[0]; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_find_hog_obj, 1, py_image_find_hog); +#endif // IMLIB_ENABLE_HOG + +#ifdef IMLIB_ENABLE_SELECTIVE_SEARCH +static mp_obj_t py_image_selective_search(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + image_t *img = py_helper_arg_to_image_mutable(args[0]); + int t = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 500); + int s = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_size), 20); + float a1 = py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_a1), 1.0f); + float a2 = py_helper_keyword_float(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_a1), 1.0f); + float a3 = py_helper_keyword_float(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_a1), 1.0f); + array_t *proposals_array = imlib_selective_search(img, t, s, a1, a2, a3); + + // Add proposals to a new Python list... + mp_obj_t proposals_list = mp_obj_new_list(0, NULL); + for (int i=0; ix), + mp_obj_new_int(r->y), + mp_obj_new_int(r->w), + mp_obj_new_int(r->h), + }; + mp_obj_list_append(proposals_list, mp_obj_new_tuple(4, rec_obj)); + } + + array_free(proposals_array); + return proposals_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_selective_search_obj, 1, py_image_selective_search); +#endif // IMLIB_ENABLE_SELECTIVE_SEARCH + +static const mp_rom_map_elem_t locals_dict_table[] = { + /* Basic Methods */ + {MP_ROM_QSTR(MP_QSTR_width), MP_ROM_PTR(&py_image_width_obj)}, + {MP_ROM_QSTR(MP_QSTR_height), MP_ROM_PTR(&py_image_height_obj)}, + {MP_ROM_QSTR(MP_QSTR_format), MP_ROM_PTR(&py_image_format_obj)}, + {MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&py_image_size_obj)}, + {MP_ROM_QSTR(MP_QSTR_bytearray), MP_ROM_PTR(&py_image_bytearray_obj)}, + {MP_ROM_QSTR(MP_QSTR_get_pixel), MP_ROM_PTR(&py_image_get_pixel_obj)}, + {MP_ROM_QSTR(MP_QSTR_set_pixel), MP_ROM_PTR(&py_image_set_pixel_obj)}, +#ifdef IMLIB_ENABLE_MEAN_POOLING + {MP_ROM_QSTR(MP_QSTR_mean_pool), MP_ROM_PTR(&py_image_mean_pool_obj)}, + {MP_ROM_QSTR(MP_QSTR_mean_pooled), MP_ROM_PTR(&py_image_mean_pooled_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_mean_pool), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_mean_pooled), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_MIDPOINT_POOLING + {MP_ROM_QSTR(MP_QSTR_midpoint_pool), MP_ROM_PTR(&py_image_midpoint_pool_obj)}, + {MP_ROM_QSTR(MP_QSTR_midpoint_pooled), MP_ROM_PTR(&py_image_midpoint_pooled_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_midpoint_pool), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_midpoint_pooled), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif + {MP_ROM_QSTR(MP_QSTR_to_bitmap), MP_ROM_PTR(&py_image_to_bitmap_obj)}, + {MP_ROM_QSTR(MP_QSTR_to_grayscale), MP_ROM_PTR(&py_image_to_grayscale_obj)}, + {MP_ROM_QSTR(MP_QSTR_to_rgb565), MP_ROM_PTR(&py_image_to_rgb565_obj)}, + {MP_ROM_QSTR(MP_QSTR_to_rainbow), MP_ROM_PTR(&py_image_to_rainbow_obj)}, + {MP_ROM_QSTR(MP_QSTR_compress), MP_ROM_PTR(&py_image_compress_obj)}, + {MP_ROM_QSTR(MP_QSTR_compress_for_ide), MP_ROM_PTR(&py_image_compress_for_ide_obj)}, + {MP_ROM_QSTR(MP_QSTR_compressed), MP_ROM_PTR(&py_image_compressed_obj)}, + {MP_ROM_QSTR(MP_QSTR_compressed_for_ide), MP_ROM_PTR(&py_image_compressed_for_ide_obj)}, + {MP_ROM_QSTR(MP_QSTR_jpeg_encode_for_ide), MP_ROM_PTR(&py_image_jpeg_encode_for_ide_obj)}, + {MP_ROM_QSTR(MP_QSTR_jpeg_encoded_for_ide),MP_ROM_PTR(&py_image_jpeg_encoded_for_ide_obj)}, + {MP_ROM_QSTR(MP_QSTR_copy), MP_ROM_PTR(&py_image_copy_obj)}, + {MP_ROM_QSTR(MP_QSTR_crop), MP_ROM_PTR(&py_image_crop_obj)}, + {MP_ROM_QSTR(MP_QSTR_scale), MP_ROM_PTR(&py_image_crop_obj)}, + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + {MP_ROM_QSTR(MP_QSTR_save), MP_ROM_PTR(&py_image_save_obj)}, + #else + {MP_ROM_QSTR(MP_QSTR_save), MP_ROM_PTR(&py_func_unavailable_obj)}, + #endif + {MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&py_image_flush_obj)}, + /* Drawing Methods */ + {MP_ROM_QSTR(MP_QSTR_clear), MP_ROM_PTR(&py_image_clear_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_line), MP_ROM_PTR(&py_image_draw_line_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_rectangle), MP_ROM_PTR(&py_image_draw_rectangle_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_circle), MP_ROM_PTR(&py_image_draw_circle_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_ellipse), MP_ROM_PTR(&py_image_draw_ellipse_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_string), MP_ROM_PTR(&py_image_draw_string_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_cross), MP_ROM_PTR(&py_image_draw_cross_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_arrow), MP_ROM_PTR(&py_image_draw_arrow_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_edges), MP_ROM_PTR(&py_image_draw_edges_obj)}, + {MP_ROM_QSTR(MP_QSTR_draw_image), MP_ROM_PTR(&py_image_draw_image_obj)}, +#ifdef IMLIB_ENABLE_FLOOD_FILL + {MP_ROM_QSTR(MP_QSTR_flood_fill), MP_ROM_PTR(&py_image_flood_fill_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_flood_fill), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif + {MP_ROM_QSTR(MP_QSTR_draw_keypoints), MP_ROM_PTR(&py_image_draw_keypoints_obj)}, + {MP_ROM_QSTR(MP_QSTR_mask_rectangle), MP_ROM_PTR(&py_image_mask_rectangle_obj)}, + {MP_ROM_QSTR(MP_QSTR_mask_circle), MP_ROM_PTR(&py_image_mask_circle_obj)}, + {MP_ROM_QSTR(MP_QSTR_mask_ellipse), MP_ROM_PTR(&py_image_mask_ellipse_obj)}, + /* Binary Methods */ +#ifdef IMLIB_ENABLE_BINARY_OPS + {MP_ROM_QSTR(MP_QSTR_binary), MP_ROM_PTR(&py_image_binary_obj)}, + {MP_ROM_QSTR(MP_QSTR_invert), MP_ROM_PTR(&py_image_invert_obj)}, + {MP_ROM_QSTR(MP_QSTR_and), MP_ROM_PTR(&py_image_b_and_obj)}, + {MP_ROM_QSTR(MP_QSTR_b_and), MP_ROM_PTR(&py_image_b_and_obj)}, + {MP_ROM_QSTR(MP_QSTR_nand), MP_ROM_PTR(&py_image_b_nand_obj)}, + {MP_ROM_QSTR(MP_QSTR_b_nand), MP_ROM_PTR(&py_image_b_nand_obj)}, + {MP_ROM_QSTR(MP_QSTR_or), MP_ROM_PTR(&py_image_b_or_obj)}, + {MP_ROM_QSTR(MP_QSTR_b_or), MP_ROM_PTR(&py_image_b_or_obj)}, + {MP_ROM_QSTR(MP_QSTR_nor), MP_ROM_PTR(&py_image_b_nor_obj)}, + {MP_ROM_QSTR(MP_QSTR_b_nor), MP_ROM_PTR(&py_image_b_nor_obj)}, + {MP_ROM_QSTR(MP_QSTR_xor), MP_ROM_PTR(&py_image_b_xor_obj)}, + {MP_ROM_QSTR(MP_QSTR_b_xor), MP_ROM_PTR(&py_image_b_xor_obj)}, + {MP_ROM_QSTR(MP_QSTR_xnor), MP_ROM_PTR(&py_image_b_xnor_obj)}, + {MP_ROM_QSTR(MP_QSTR_b_xnor), MP_ROM_PTR(&py_image_b_xnor_obj)}, + {MP_ROM_QSTR(MP_QSTR_erode), MP_ROM_PTR(&py_image_erode_obj)}, + {MP_ROM_QSTR(MP_QSTR_dilate), MP_ROM_PTR(&py_image_dilate_obj)}, + {MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&py_image_open_obj)}, + {MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&py_image_close_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_binary), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_invert), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_and), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_b_and), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_nand), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_b_nand), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_or), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_b_or), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_nor), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_b_nor), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_xor), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_b_xor), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_xnor), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_b_xnor), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_erode), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_dilate), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_MATH_OPS + /* Math Methods */ + {MP_ROM_QSTR(MP_QSTR_gamma_corr), MP_ROM_PTR(&py_image_gamma_corr_obj)}, + {MP_ROM_QSTR(MP_QSTR_negate), MP_ROM_PTR(&py_image_negate_obj)}, + {MP_ROM_QSTR(MP_QSTR_assign), MP_ROM_PTR(&py_image_replace_obj)}, + {MP_ROM_QSTR(MP_QSTR_replace), MP_ROM_PTR(&py_image_replace_obj)}, + {MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&py_image_replace_obj)}, + {MP_ROM_QSTR(MP_QSTR_add), MP_ROM_PTR(&py_image_add_obj)}, + {MP_ROM_QSTR(MP_QSTR_sub), MP_ROM_PTR(&py_image_sub_obj)}, + {MP_ROM_QSTR(MP_QSTR_mul), MP_ROM_PTR(&py_image_mul_obj)}, + {MP_ROM_QSTR(MP_QSTR_div), MP_ROM_PTR(&py_image_div_obj)}, + {MP_ROM_QSTR(MP_QSTR_min), MP_ROM_PTR(&py_image_min_obj)}, + {MP_ROM_QSTR(MP_QSTR_max), MP_ROM_PTR(&py_image_max_obj)}, + {MP_ROM_QSTR(MP_QSTR_difference), MP_ROM_PTR(&py_image_difference_obj)}, + {MP_ROM_QSTR(MP_QSTR_blend), MP_ROM_PTR(&py_image_blend_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_negate), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_assign), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_replace), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_set), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_add), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_sub), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_mul), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_div), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_min), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_max), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_difference), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_blend), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#if defined(IMLIB_ENABLE_MATH_OPS) && defined (IMLIB_ENABLE_BINARY_OPS) + {MP_ROM_QSTR(MP_QSTR_top_hat), MP_ROM_PTR(&py_image_top_hat_obj)}, + {MP_ROM_QSTR(MP_QSTR_black_hat), MP_ROM_PTR(&py_image_black_hat_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_top_hat), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_black_hat), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif //defined(IMLIB_ENABLE_MATH_OPS) && defined (IMLIB_ENABLE_BINARY_OPS) + /* Filtering Methods */ + {MP_ROM_QSTR(MP_QSTR_histeq), MP_ROM_PTR(&py_image_histeq_obj)}, +#ifdef IMLIB_ENABLE_MEAN + {MP_ROM_QSTR(MP_QSTR_mean), MP_ROM_PTR(&py_image_mean_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_mean), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_MEDIAN + {MP_ROM_QSTR(MP_QSTR_median), MP_ROM_PTR(&py_image_median_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_median), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_MODE + {MP_ROM_QSTR(MP_QSTR_mode), MP_ROM_PTR(&py_image_mode_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_mode), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_MIDPOINT + {MP_ROM_QSTR(MP_QSTR_midpoint), MP_ROM_PTR(&py_image_midpoint_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_midpoint), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_MORPH + {MP_ROM_QSTR(MP_QSTR_morph), MP_ROM_PTR(&py_image_morph_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_morph), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_GAUSSIAN + {MP_ROM_QSTR(MP_QSTR_blur), MP_ROM_PTR(&py_image_gaussian_obj)}, + {MP_ROM_QSTR(MP_QSTR_gaussian), MP_ROM_PTR(&py_image_gaussian_obj)}, + {MP_ROM_QSTR(MP_QSTR_gaussian_blur), MP_ROM_PTR(&py_image_gaussian_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_blur), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_gaussian), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_gaussian_blur), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_LAPLACIAN + {MP_ROM_QSTR(MP_QSTR_laplacian), MP_ROM_PTR(&py_image_laplacian_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_laplacian), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_BILATERAL + {MP_ROM_QSTR(MP_QSTR_bilateral), MP_ROM_PTR(&py_image_bilateral_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_bilateral), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_CARTOON + {MP_ROM_QSTR(MP_QSTR_cartoon), MP_ROM_PTR(&py_image_cartoon_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_cartoon), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif + /* Geometric Methods */ +#ifdef IMLIB_ENABLE_LINPOLAR + {MP_ROM_QSTR(MP_QSTR_linpolar), MP_ROM_PTR(&py_image_linpolar_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_linpolar), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_LOGPOLAR + {MP_ROM_QSTR(MP_QSTR_logpolar), MP_ROM_PTR(&py_image_logpolar_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_logpolar), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_LENS_CORR + {MP_ROM_QSTR(MP_QSTR_lens_corr), MP_ROM_PTR(&py_image_lens_corr_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_lens_corr), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_ROTATION_CORR + {MP_ROM_QSTR(MP_QSTR_rotation_corr), MP_ROM_PTR(&py_image_rotation_corr_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_rotation_corr), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif + /* Get Methods */ +#ifdef IMLIB_ENABLE_GET_SIMILARITY + {MP_ROM_QSTR(MP_QSTR_get_similarity), MP_ROM_PTR(&py_image_get_similarity_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_get_similarity), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif + {MP_ROM_QSTR(MP_QSTR_get_hist), MP_ROM_PTR(&py_image_get_histogram_obj)}, + {MP_ROM_QSTR(MP_QSTR_get_histogram), MP_ROM_PTR(&py_image_get_histogram_obj)}, + {MP_ROM_QSTR(MP_QSTR_histogram), MP_ROM_PTR(&py_image_get_histogram_obj)}, + {MP_ROM_QSTR(MP_QSTR_get_stats), MP_ROM_PTR(&py_image_get_statistics_obj)}, + {MP_ROM_QSTR(MP_QSTR_get_statistics), MP_ROM_PTR(&py_image_get_statistics_obj)}, + {MP_ROM_QSTR(MP_QSTR_statistics), MP_ROM_PTR(&py_image_get_statistics_obj)}, + {MP_ROM_QSTR(MP_QSTR_get_regression), MP_ROM_PTR(&py_image_get_regression_obj)}, + /* Find Methods */ + {MP_ROM_QSTR(MP_QSTR_find_blobs), MP_ROM_PTR(&py_image_find_blobs_obj)}, +#ifdef IMLIB_ENABLE_FIND_LINES + {MP_ROM_QSTR(MP_QSTR_find_lines), MP_ROM_PTR(&py_image_find_lines_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_lines), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_FIND_LINE_SEGMENTS + {MP_ROM_QSTR(MP_QSTR_find_line_segments), MP_ROM_PTR(&py_image_find_line_segments_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_line_segments), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_FIND_CIRCLES + {MP_ROM_QSTR(MP_QSTR_find_circles), MP_ROM_PTR(&py_image_find_circles_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_circles), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_FIND_RECTS + {MP_ROM_QSTR(MP_QSTR_find_rects), MP_ROM_PTR(&py_image_find_rects_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_rects), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_QRCODES + {MP_ROM_QSTR(MP_QSTR_find_qrcodes), MP_ROM_PTR(&py_image_find_qrcodes_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_qrcodes), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_APRILTAGS + {MP_ROM_QSTR(MP_QSTR_find_apriltags), MP_ROM_PTR(&py_image_find_apriltags_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_apriltags), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_DATAMATRICES + {MP_ROM_QSTR(MP_QSTR_find_datamatrices), MP_ROM_PTR(&py_image_find_datamatrices_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_datamatrices), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_BARCODES + {MP_ROM_QSTR(MP_QSTR_find_barcodes), MP_ROM_PTR(&py_image_find_barcodes_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_barcodes), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_FIND_DISPLACEMENT + {MP_ROM_QSTR(MP_QSTR_find_displacement), MP_ROM_PTR(&py_image_find_displacement_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_displacement), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_FIND_TEMPLATE + {MP_ROM_QSTR(MP_QSTR_find_template), MP_ROM_PTR(&py_image_find_template_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_template), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif + {MP_ROM_QSTR(MP_QSTR_find_features), MP_ROM_PTR(&py_image_find_features_obj)}, + {MP_ROM_QSTR(MP_QSTR_find_eye), MP_ROM_PTR(&py_image_find_eye_obj)}, +#ifdef IMLIB_ENABLE_FIND_LBP + {MP_ROM_QSTR(MP_QSTR_find_lbp), MP_ROM_PTR(&py_image_find_lbp_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_lbp), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_FIND_KEYPOINTS + {MP_ROM_QSTR(MP_QSTR_find_keypoints), MP_ROM_PTR(&py_image_find_keypoints_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_keypoints), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_BINARY_OPS + {MP_ROM_QSTR(MP_QSTR_find_edges), MP_ROM_PTR(&py_image_find_edges_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_edges), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_HOG + {MP_ROM_QSTR(MP_QSTR_find_hog), MP_ROM_PTR(&py_image_find_hog_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_find_hog), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +#ifdef IMLIB_ENABLE_SELECTIVE_SEARCH + {MP_ROM_QSTR(MP_QSTR_selective_search), MP_ROM_PTR(&py_image_selective_search_obj)}, +#else + {MP_ROM_QSTR(MP_QSTR_selective_search), MP_ROM_PTR(&py_func_unavailable_obj)}, +#endif +}; + +STATIC MP_DEFINE_CONST_DICT(locals_dict, locals_dict_table); + +static const mp_obj_type_t py_image_type = { + { &mp_type_type }, + .name = MP_QSTR_Image, + .print = py_image_print, + .buffer_p = { .get_buffer = py_image_get_buffer }, + .subscr = py_image_subscr, + .getiter = py_image_getiter, + .unary_op = py_image_unary_op, + .locals_dict = (mp_obj_t) &locals_dict +}; + +mp_obj_t py_image_binary_to_grayscale(mp_obj_t arg) +{ + int8_t b = mp_obj_get_int(arg) & 1; + return mp_obj_new_int(COLOR_BINARY_TO_GRAYSCALE(b)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_binary_to_grayscale_obj, py_image_binary_to_grayscale); + +mp_obj_t py_image_binary_to_rgb(mp_obj_t arg) +{ + int8_t b = mp_obj_get_int(arg) & 1; + uint16_t rgb565 = COLOR_BINARY_TO_RGB565(b); + return mp_obj_new_tuple(3, (mp_obj_t[3]) + {mp_obj_new_int(COLOR_RGB565_TO_R8(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_G8(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_B8(rgb565))}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_binary_to_rgb_obj, py_image_binary_to_rgb); + +mp_obj_t py_image_binary_to_lab(mp_obj_t arg) +{ + int8_t b = mp_obj_get_int(arg) & 1; + uint16_t rgb565 = COLOR_BINARY_TO_RGB565(b); + return mp_obj_new_tuple(3, (mp_obj_t[3]) + {mp_obj_new_int(COLOR_RGB565_TO_L(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_A(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_B(rgb565))}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_binary_to_lab_obj, py_image_binary_to_lab); + +mp_obj_t py_image_binary_to_yuv(mp_obj_t arg) +{ + int8_t b = mp_obj_get_int(arg) & 1; + uint16_t rgb565 = COLOR_BINARY_TO_RGB565(b); + return mp_obj_new_tuple(3, (mp_obj_t[3]) + {mp_obj_new_int(COLOR_RGB565_TO_Y(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_U(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_V(rgb565))}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_binary_to_yuv_obj, py_image_binary_to_yuv); + +mp_obj_t py_image_grayscale_to_binary(mp_obj_t arg) +{ + int8_t g = mp_obj_get_int(arg) & 255; + return mp_obj_new_int(COLOR_GRAYSCALE_TO_BINARY(g)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_grayscale_to_binary_obj, py_image_grayscale_to_binary); + +mp_obj_t py_image_grayscale_to_rgb(mp_obj_t arg) +{ + int8_t g = mp_obj_get_int(arg) & 255; + uint16_t rgb565 = COLOR_GRAYSCALE_TO_RGB565(g); + return mp_obj_new_tuple(3, (mp_obj_t[3]) + {mp_obj_new_int(COLOR_RGB565_TO_R8(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_G8(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_B8(rgb565))}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_grayscale_to_rgb_obj, py_image_grayscale_to_rgb); + +mp_obj_t py_image_grayscale_to_lab(mp_obj_t arg) +{ + int8_t g = mp_obj_get_int(arg) & 255; + uint16_t rgb565 = COLOR_GRAYSCALE_TO_RGB565(g); + return mp_obj_new_tuple(3, (mp_obj_t[3]) + {mp_obj_new_int(COLOR_RGB565_TO_L(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_A(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_B(rgb565))}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_grayscale_to_lab_obj, py_image_grayscale_to_lab); + +mp_obj_t py_image_grayscale_to_yuv(mp_obj_t arg) +{ + int8_t g = mp_obj_get_int(arg) & 255; + uint16_t rgb565 = COLOR_GRAYSCALE_TO_RGB565(g); + return mp_obj_new_tuple(3, (mp_obj_t[3]) + {mp_obj_new_int(COLOR_RGB565_TO_Y(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_U(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_V(rgb565))}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_image_grayscale_to_yuv_obj, py_image_grayscale_to_yuv); + +mp_obj_t py_image_rgb_to_binary(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 0, 3, &arg_vec); + uint8_t r = mp_obj_get_int(arg_vec[0]) & 255; + uint8_t g = mp_obj_get_int(arg_vec[1]) & 255; + uint8_t b = mp_obj_get_int(arg_vec[2]) & 255; + uint16_t rgb565 = COLOR_R8_G8_B8_TO_RGB565(r, g, b); + return mp_obj_new_int(COLOR_RGB565_TO_BINARY(rgb565)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_rgb_to_binary_obj, 1, py_image_rgb_to_binary); + +mp_obj_t py_image_rgb_to_grayscale(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 0, 3, &arg_vec); + uint8_t r = mp_obj_get_int(arg_vec[0]) & 255; + uint8_t g = mp_obj_get_int(arg_vec[1]) & 255; + uint8_t b = mp_obj_get_int(arg_vec[2]) & 255; + uint16_t rgb565 = COLOR_R8_G8_B8_TO_RGB565(r, g, b); + return mp_obj_new_int(COLOR_RGB565_TO_GRAYSCALE(rgb565)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_rgb_to_grayscale_obj, 1, py_image_rgb_to_grayscale); + +mp_obj_t py_image_rgb_to_lab(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 0, 3, &arg_vec); + uint8_t r = mp_obj_get_int(arg_vec[0]) & 255; + uint8_t g = mp_obj_get_int(arg_vec[1]) & 255; + uint8_t b = mp_obj_get_int(arg_vec[2]) & 255; + uint16_t rgb565 = COLOR_R8_G8_B8_TO_RGB565(r, g, b); + return mp_obj_new_tuple(3, (mp_obj_t[3]) + {mp_obj_new_int(COLOR_RGB565_TO_L(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_A(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_B(rgb565))}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_rgb_to_lab_obj, 1, py_image_rgb_to_lab); + +mp_obj_t py_image_rgb_to_yuv(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 0, 3, &arg_vec); + uint8_t r = mp_obj_get_int(arg_vec[0]) & 255; + uint8_t g = mp_obj_get_int(arg_vec[1]) & 255; + uint8_t b = mp_obj_get_int(arg_vec[2]) & 255; + uint16_t rgb565 = COLOR_R8_G8_B8_TO_RGB565(r, g, b); + return mp_obj_new_tuple(3, (mp_obj_t[3]) + {mp_obj_new_int(COLOR_RGB565_TO_Y(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_U(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_V(rgb565))}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_rgb_to_yuv_obj, 1, py_image_rgb_to_yuv); + +mp_obj_t py_image_lab_to_binary(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 0, 3, &arg_vec); + int8_t l = (mp_obj_get_int(arg_vec[0]) & 255) % 100; + int8_t a = mp_obj_get_int(arg_vec[1]) & 255; + int8_t b = mp_obj_get_int(arg_vec[2]) & 255; + uint16_t rgb565 = COLOR_LAB_TO_RGB565(l, a, b); + return mp_obj_new_int(COLOR_RGB565_TO_BINARY(rgb565)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_lab_to_binary_obj, 1, py_image_lab_to_binary); + +mp_obj_t py_image_lab_to_grayscale(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 0, 3, &arg_vec); + int8_t l = (mp_obj_get_int(arg_vec[0]) & 255) % 100; + int8_t a = mp_obj_get_int(arg_vec[1]) & 255; + int8_t b = mp_obj_get_int(arg_vec[2]) & 255; + uint16_t rgb565 = COLOR_LAB_TO_RGB565(l, a, b); + return mp_obj_new_int(COLOR_RGB565_TO_GRAYSCALE(rgb565)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_lab_to_grayscale_obj, 1, py_image_lab_to_grayscale); + +mp_obj_t py_image_lab_to_rgb(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 0, 3, &arg_vec); + int8_t l = (mp_obj_get_int(arg_vec[0]) & 255) % 100; + int8_t a = mp_obj_get_int(arg_vec[1]) & 255; + int8_t b = mp_obj_get_int(arg_vec[2]) & 255; + uint16_t rgb565 = COLOR_LAB_TO_RGB565(l, a, b); + return mp_obj_new_tuple(3, (mp_obj_t[3]) + {mp_obj_new_int(COLOR_RGB565_TO_R8(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_G8(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_B8(rgb565))}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_lab_to_rgb_obj, 1, py_image_lab_to_rgb); + +mp_obj_t py_image_lab_to_yuv(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 0, 3, &arg_vec); + int8_t l = (mp_obj_get_int(arg_vec[0]) & 255) % 100; + int8_t a = mp_obj_get_int(arg_vec[1]) & 255; + int8_t b = mp_obj_get_int(arg_vec[2]) & 255; + uint16_t rgb565 = COLOR_LAB_TO_RGB565(l, a, b); + return mp_obj_new_tuple(3, (mp_obj_t[3]) + {mp_obj_new_int(COLOR_RGB565_TO_Y(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_U(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_V(rgb565))}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_lab_to_yuv_obj, 1, py_image_lab_to_yuv); + +mp_obj_t py_image_yuv_to_binary(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 0, 3, &arg_vec); + int8_t y = mp_obj_get_int(arg_vec[0]) & 255; + int8_t u = mp_obj_get_int(arg_vec[1]) & 255; + int8_t v = mp_obj_get_int(arg_vec[2]) & 255; + uint16_t rgb565 = COLOR_YUV_TO_RGB565(y, u, v); + return mp_obj_new_int(COLOR_RGB565_TO_BINARY(rgb565)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_yuv_to_binary_obj, 1, py_image_yuv_to_binary); + +mp_obj_t py_image_yuv_to_grayscale(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 0, 3, &arg_vec); + int8_t y = mp_obj_get_int(arg_vec[0]) & 255; + int8_t u = mp_obj_get_int(arg_vec[1]) & 255; + int8_t v = mp_obj_get_int(arg_vec[2]) & 255; + uint16_t rgb565 = COLOR_YUV_TO_RGB565(y, u, v); + return mp_obj_new_int(COLOR_RGB565_TO_GRAYSCALE(rgb565)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_yuv_to_grayscale_obj, 1, py_image_yuv_to_grayscale); + +mp_obj_t py_image_yuv_to_rgb(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 0, 3, &arg_vec); + int8_t y = mp_obj_get_int(arg_vec[0]) & 255; + int8_t u = mp_obj_get_int(arg_vec[1]) & 255; + int8_t v = mp_obj_get_int(arg_vec[2]) & 255; + uint16_t rgb565 = COLOR_YUV_TO_RGB565(y, u, v); + return mp_obj_new_tuple(3, (mp_obj_t[3]) + {mp_obj_new_int(COLOR_RGB565_TO_R8(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_G8(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_B8(rgb565))}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_yuv_to_rgb_obj, 1, py_image_yuv_to_rgb); + +mp_obj_t py_image_yuv_to_lab(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + const mp_obj_t *arg_vec; + py_helper_consume_array(n_args, args, 0, 3, &arg_vec); + int8_t y = mp_obj_get_int(arg_vec[0]) & 255; + int8_t u = mp_obj_get_int(arg_vec[1]) & 255; + int8_t v = mp_obj_get_int(arg_vec[2]) & 255; + uint16_t rgb565 = COLOR_YUV_TO_RGB565(y, u, v); + return mp_obj_new_tuple(3, (mp_obj_t[3]) + {mp_obj_new_int(COLOR_RGB565_TO_L(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_A(rgb565)), + mp_obj_new_int(COLOR_RGB565_TO_B(rgb565))}); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_yuv_to_lab_obj, 1, py_image_yuv_to_lab); + +mp_obj_t py_image(int w, int h, pixformat_t pixfmt, uint32_t size, void *pixels) +{ + py_image_obj_t *o = m_new_obj(py_image_obj_t); + o->base.type = &py_image_type; + o->_cobj.w = w; + o->_cobj.h = h; + o->_cobj.size = size; + o->_cobj.pixfmt = pixfmt; + o->_cobj.pixels = pixels; + return o; +} + +mp_obj_t py_image_from_struct(image_t *img) +{ + py_image_obj_t *o = m_new_obj(py_image_obj_t); + o->base.type = &py_image_type; + o->_cobj = *img; + return o; +} + +mp_obj_t py_image_load_image(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + // mode == false -> load behavior + // mode == true -> make behavior + bool mode = mp_obj_is_integer(args[0]); + const char *path = mode ? NULL : mp_obj_str_get_str(args[0]); + + mp_obj_t copy_to_fb_obj = py_helper_keyword_object(n_args, args, + mode ? 3 : 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_copy_to_fb), NULL); + bool copy_to_fb = false; + image_t *arg_other = NULL; + + if (copy_to_fb_obj) { + if (mp_obj_is_integer(copy_to_fb_obj)) { + copy_to_fb = mp_obj_get_int(copy_to_fb_obj); + } else { + arg_other = py_helper_arg_to_image_mutable(copy_to_fb_obj); + } + } + + image_t image = {0}; + + if (mode) { + PY_ASSERT_TRUE_MSG(n_args >= 3, "Expected width, height, and type"); + + image.w = mp_obj_get_int(args[0]); + PY_ASSERT_TRUE_MSG(image.w > 0, "Width must be > 0"); + + image.h = mp_obj_get_int(args[1]); + PY_ASSERT_TRUE_MSG(image.h > 0, "Height must be > 0"); + + switch(mp_obj_get_int(args[2])) { + case PIXFORMAT_BINARY: + image.pixfmt = PIXFORMAT_BINARY; + break; + case PIXFORMAT_GRAYSCALE: + image.pixfmt = PIXFORMAT_GRAYSCALE; + break; + case PIXFORMAT_RGB565: + image.pixfmt = PIXFORMAT_RGB565; + break; + default: + PY_ASSERT_TRUE_MSG(false, "Unsupported type"); + break; + } + } else { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + fb_alloc_mark(); + FIL fp; + img_read_settings_t rs; + imlib_read_geometry(&fp, &image, path, &rs); + file_buffer_off(&fp); + file_close(&fp); + #else + (void) path; + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image I/O is not supported")); + #endif //IMLIB_ENABLE_IMAGE_FILE_IO + } + + size_t size = image_size(&image); + + if (copy_to_fb) { + py_helper_set_to_framebuffer(&image); + } else if (arg_other) { + PY_ASSERT_TRUE_MSG((size <= image_size(arg_other)), + "The new image won't fit in the target frame buffer!"); + image.data = arg_other->data; + } else if (mode) { + image.data = xalloc(size); + } + + if (mode) { + memset(image.data, 0, size); + } else { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + imlib_load_image(&image, path); + fb_alloc_free_till_mark(); + #endif + } + + py_helper_update_framebuffer(&image); + + if (arg_other) { + memcpy(arg_other, &image, sizeof(image_t)); + } + + if (copy_to_fb) { + framebuffer_update_jpeg_buffer(); + } + + return py_image_from_struct(&image); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_load_image_obj, 1, py_image_load_image); + +mp_obj_t py_image_load_cascade(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + cascade_t cascade; + const char *path = mp_obj_str_get_str(args[0]); + + // Load cascade from file or flash + int res = imlib_load_cascade(&cascade, path); + if (res != FR_OK) { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + // cascade is not built-in and failed to load it from file. + mp_raise_msg(&mp_type_OSError, (mp_rom_error_text_t) ffs_strerror(res)); + #else + // cascade is not built-in. + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image I/O is not supported")); + #endif + } + + // Read the number of stages + int stages = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(qstr_from_str("stages")), cascade.n_stages); + // Check the number of stages + if (stages > 0 && stages < cascade.n_stages) { + cascade.n_stages = stages; + } + + // Return micropython cascade object + py_cascade_obj_t *o = m_new_obj(py_cascade_obj_t); + o->base.type = &py_cascade_type; + o->_cobj = cascade; + return o; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_load_cascade_obj, 1, py_image_load_cascade); + +#if defined(IMLIB_ENABLE_DESCRIPTOR) +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) +mp_obj_t py_image_load_descriptor(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + FIL fp; + UINT bytes; + FRESULT res; + + uint32_t desc_type; + mp_obj_t desc = mp_const_none; + const char *path = mp_obj_str_get_str(args[0]); + + if ((res = f_open_helper(&fp, path, FA_READ|FA_OPEN_EXISTING)) == FR_OK) { + // Read descriptor type + res = f_read(&fp, &desc_type, sizeof(desc_type), &bytes); + if (res != FR_OK || bytes != sizeof(desc_type)) { + goto error; + } + + // Load descriptor + switch (desc_type) { + #if defined(IMLIB_ENABLE_FIND_LBP) + case DESC_LBP: { + py_lbp_obj_t *lbp = m_new_obj(py_lbp_obj_t); + lbp->base.type = &py_lbp_type; + + res = imlib_lbp_desc_load(&fp, &lbp->hist); + if (res == FR_OK) { + desc = lbp; + } + break; + } + #endif //IMLIB_ENABLE_FIND_LBP + #if defined(IMLIB_ENABLE_FIND_KEYPOINTS) + case DESC_ORB: { + array_t *kpts = NULL; + array_alloc(&kpts, xfree); + + res = orb_load_descriptor(&fp, kpts); + if (res == FR_OK) { + // Return keypoints MP object + py_kp_obj_t *kp_obj = m_new_obj(py_kp_obj_t); + kp_obj->base.type = &py_kp_type; + kp_obj->kpts = kpts; + kp_obj->threshold = 10; + kp_obj->normalized = false; + desc = kp_obj; + } + break; + } + #endif //IMLIB_ENABLE_FIND_KEYPOINTS + } + + f_close(&fp); + } + +error: + // File open or write error + if (res != FR_OK) { + mp_raise_msg(&mp_type_OSError, (mp_rom_error_text_t) ffs_strerror(res)); + } + + // If no file error and descriptor is still none, then it's not supported. + if (desc == mp_const_none) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Descriptor type is not supported")); + } + return desc; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_load_descriptor_obj, 1, py_image_load_descriptor); + +mp_obj_t py_image_save_descriptor(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + FIL fp; + UINT bytes; + FRESULT res; + + uint32_t desc_type; + const char *path = mp_obj_str_get_str(args[1]); + + if ((res = f_open_helper(&fp, path, FA_WRITE|FA_CREATE_ALWAYS)) == FR_OK) { + // Find descriptor type + const mp_obj_type_t *desc_obj_type = mp_obj_get_type(args[0]); + if (0) { + #if defined(IMLIB_ENABLE_FIND_LBP) + } else if (desc_obj_type == &py_lbp_type) { + desc_type = DESC_LBP; + #endif //IMLIB_ENABLE_FIND_LBP + #if defined(IMLIB_ENABLE_FIND_KEYPOINTS) + } else if (desc_obj_type == &py_kp_type) { + desc_type = DESC_ORB; + #endif //IMLIB_ENABLE_FIND_KEYPOINTS + } else { + (void) desc_obj_type; + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Descriptor type is not supported")); + } + + // Write descriptor type + res = f_write(&fp, &desc_type, sizeof(desc_type), &bytes); + if (res != FR_OK || bytes != sizeof(desc_type)) { + goto error; + } + + // Write descriptor + switch (desc_type) { + #if defined(IMLIB_ENABLE_FIND_LBP) + case DESC_LBP: { + py_lbp_obj_t *lbp = ((py_lbp_obj_t*)args[0]); + res = imlib_lbp_desc_save(&fp, lbp->hist); + break; + } + #endif //IMLIB_ENABLE_FIND_LBP + #if defined(IMLIB_ENABLE_FIND_KEYPOINTS) + case DESC_ORB: { + py_kp_obj_t *kpts = ((py_kp_obj_t*)args[0]); + res = orb_save_descriptor(&fp, kpts->kpts); + break; + } + #endif //IMLIB_ENABLE_FIND_KEYPOINTS + } + // ignore unsupported descriptors when saving + f_close(&fp); + } + +error: + // File open or read error + if (res != FR_OK) { + mp_raise_msg(&mp_type_OSError, (mp_rom_error_text_t) ffs_strerror(res)); + } + return mp_const_true; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_save_descriptor_obj, 2, py_image_save_descriptor); +#endif //IMLIB_ENABLE_IMAGE_FILE_IO + +static mp_obj_t py_image_match_descriptor(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + mp_obj_t match_obj = mp_const_none; + const mp_obj_type_t *desc1_type = mp_obj_get_type(args[0]); + const mp_obj_type_t *desc2_type = mp_obj_get_type(args[1]); + PY_ASSERT_TRUE_MSG((desc1_type == desc2_type), "Descriptors have different types!"); + + if (0) { + #if defined(IMLIB_ENABLE_FIND_LBP) + } else if (desc1_type == &py_lbp_type) { + py_lbp_obj_t *lbp1 = ((py_lbp_obj_t*)args[0]); + py_lbp_obj_t *lbp2 = ((py_lbp_obj_t*)args[1]); + + // Sanity checks + PY_ASSERT_TYPE(lbp1, &py_lbp_type); + PY_ASSERT_TYPE(lbp2, &py_lbp_type); + + // Match descriptors + match_obj = mp_obj_new_int(imlib_lbp_desc_distance(lbp1->hist, lbp2->hist)); + #endif //IMLIB_ENABLE_FIND_LBP + #if defined(IMLIB_ENABLE_FIND_KEYPOINTS) + } else if (desc1_type == &py_kp_type) { + py_kp_obj_t *kpts1 = ((py_kp_obj_t*)args[0]); + py_kp_obj_t *kpts2 = ((py_kp_obj_t*)args[1]); + int threshold = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_threshold), 85); + int filter_outliers = py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_filter_outliers), false); + + // Sanity checks + PY_ASSERT_TYPE(kpts1, &py_kp_type); + PY_ASSERT_TYPE(kpts2, &py_kp_type); + PY_ASSERT_TRUE_MSG((threshold >=0 && threshold <= 100), "Expected threshold between 0 and 100"); + + int theta = 0; // Estimated angle of rotation + int count = 0; // Number of matches + point_t c = {0}; // Centroid + rectangle_t r = {0}; // Bounding rectangle + // List of matching keypoints indices + mp_obj_t match_list = mp_obj_new_list(0, NULL); + + if (array_length(kpts1->kpts) && array_length(kpts1->kpts)) { + fb_alloc_mark(); + int *match = fb_alloc(array_length(kpts1->kpts) * sizeof(int) * 2, FB_ALLOC_NO_HINT); + + // Match the two keypoint sets + count = orb_match_keypoints(kpts1->kpts, kpts2->kpts, match, threshold, &r, &c, &theta); + + // Add matching keypoints to Python list. + for (int i=0; ikpts, &r, &c); + } + } + + py_kptmatch_obj_t *o = m_new_obj(py_kptmatch_obj_t); + o->base.type = &py_kptmatch_type; + o->cx = mp_obj_new_int(c.x); + o->cy = mp_obj_new_int(c.y); + o->x = mp_obj_new_int(r.x); + o->y = mp_obj_new_int(r.y); + o->w = mp_obj_new_int(r.w); + o->h = mp_obj_new_int(r.h); + o->count = mp_obj_new_int(count); + o->theta = mp_obj_new_int(theta); + o->match = match_list; + match_obj = o; + #endif //IMLIB_ENABLE_FIND_KEYPOINTS + } else { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Descriptor type is not supported")); + } + + return match_obj; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_image_match_descriptor_obj, 2, py_image_match_descriptor); +#endif //IMLIB_ENABLE_DESCRIPTOR + +#if defined(IMLIB_ENABLE_FIND_KEYPOINTS) && defined(IMLIB_ENABLE_IMAGE_FILE_IO) +int py_image_descriptor_from_roi(image_t *img, const char *path, rectangle_t *roi) +{ + FIL fp; + FRESULT res = FR_OK; + + printf("Save Descriptor: ROI(%d %d %d %d)\n", roi->x, roi->y, roi->w, roi->h); + array_t *kpts = orb_find_keypoints(img, false, 20, 1.5f, 100, CORNER_AGAST, roi); + printf("Save Descriptor: KPTS(%d)\n", array_length(kpts)); + + if (array_length(kpts)) { + if ((res = f_open_helper(&fp, path, FA_WRITE|FA_CREATE_ALWAYS)) == FR_OK) { + res = orb_save_descriptor(&fp, kpts); + f_close(&fp); + } + // File open/write error + if (res != FR_OK) { + mp_raise_msg(&mp_type_OSError, (mp_rom_error_text_t) ffs_strerror(res)); + } + } + return 0; +} +#endif // IMLIB_ENABLE_KEYPOINTS && IMLIB_ENABLE_IMAGE_FILE_IO + +static const mp_rom_map_elem_t globals_dict_table[] = { + {MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_image)}, + // Pixel formats + {MP_ROM_QSTR(MP_QSTR_BINARY), MP_ROM_INT(PIXFORMAT_BINARY)}, /* 1BPP/BINARY*/ + {MP_ROM_QSTR(MP_QSTR_GRAYSCALE), MP_ROM_INT(PIXFORMAT_GRAYSCALE)},/* 1BPP/GRAYSCALE*/ + {MP_ROM_QSTR(MP_QSTR_RGB565), MP_ROM_INT(PIXFORMAT_RGB565)}, /* 2BPP/RGB565*/ + {MP_ROM_QSTR(MP_QSTR_BAYER), MP_ROM_INT(PIXFORMAT_BAYER)}, /* 1BPP/RAW*/ + {MP_ROM_QSTR(MP_QSTR_YUV422), MP_ROM_INT(PIXFORMAT_YUV422)}, /* 2BPP/YUV422*/ + {MP_ROM_QSTR(MP_QSTR_JPEG), MP_ROM_INT(PIXFORMAT_JPEG)}, /* JPEG/COMPRESSED*/ + {MP_ROM_QSTR(MP_QSTR_AREA), MP_ROM_INT(IMAGE_HINT_AREA)}, + {MP_ROM_QSTR(MP_QSTR_BILINEAR), MP_ROM_INT(IMAGE_HINT_BILINEAR)}, + {MP_ROM_QSTR(MP_QSTR_BICUBIC), MP_ROM_INT(IMAGE_HINT_BICUBIC)}, + {MP_ROM_QSTR(MP_QSTR_CENTER), MP_ROM_INT(IMAGE_HINT_CENTER)}, + {MP_ROM_QSTR(MP_QSTR_EXTRACT_RGB_CHANNEL_FIRST), MP_ROM_INT(IMAGE_HINT_EXTRACT_RGB_CHANNEL_FIRST)}, + {MP_ROM_QSTR(MP_QSTR_APPLY_COLOR_PALETTE_FIRST), MP_ROM_INT(IMAGE_HINT_APPLY_COLOR_PALETTE_FIRST)}, + #ifdef IMLIB_FIND_TEMPLATE + {MP_ROM_QSTR(MP_QSTR_SEARCH_EX), MP_ROM_INT(SEARCH_EX)}, + {MP_ROM_QSTR(MP_QSTR_SEARCH_DS), MP_ROM_INT(SEARCH_DS)}, + #endif + {MP_ROM_QSTR(MP_QSTR_EDGE_CANNY), MP_ROM_INT(EDGE_CANNY)}, + {MP_ROM_QSTR(MP_QSTR_EDGE_SIMPLE), MP_ROM_INT(EDGE_SIMPLE)}, + {MP_ROM_QSTR(MP_QSTR_CORNER_FAST), MP_ROM_INT(CORNER_FAST)}, + {MP_ROM_QSTR(MP_QSTR_CORNER_AGAST), MP_ROM_INT(CORNER_AGAST)}, + #ifdef IMLIB_ENABLE_APRILTAGS + {MP_ROM_QSTR(MP_QSTR_TAG16H5), MP_ROM_INT(TAG16H5)}, + {MP_ROM_QSTR(MP_QSTR_TAG25H7), MP_ROM_INT(TAG25H7)}, + {MP_ROM_QSTR(MP_QSTR_TAG25H9), MP_ROM_INT(TAG25H9)}, + {MP_ROM_QSTR(MP_QSTR_TAG36H10), MP_ROM_INT(TAG36H10)}, + {MP_ROM_QSTR(MP_QSTR_TAG36H11), MP_ROM_INT(TAG36H11)}, + {MP_ROM_QSTR(MP_QSTR_ARTOOLKIT), MP_ROM_INT(ARTOOLKIT)}, + #endif + #ifdef IMLIB_ENABLE_BARCODES + {MP_ROM_QSTR(MP_QSTR_EAN2), MP_ROM_INT(BARCODE_EAN2)}, + {MP_ROM_QSTR(MP_QSTR_EAN5), MP_ROM_INT(BARCODE_EAN5)}, + {MP_ROM_QSTR(MP_QSTR_EAN8), MP_ROM_INT(BARCODE_EAN8)}, + {MP_ROM_QSTR(MP_QSTR_UPCE), MP_ROM_INT(BARCODE_UPCE)}, + {MP_ROM_QSTR(MP_QSTR_ISBN10), MP_ROM_INT(BARCODE_ISBN10)}, + {MP_ROM_QSTR(MP_QSTR_UPCA), MP_ROM_INT(BARCODE_UPCA)}, + {MP_ROM_QSTR(MP_QSTR_EAN13), MP_ROM_INT(BARCODE_EAN13)}, + {MP_ROM_QSTR(MP_QSTR_ISBN13), MP_ROM_INT(BARCODE_ISBN13)}, + {MP_ROM_QSTR(MP_QSTR_I25), MP_ROM_INT(BARCODE_I25)}, + {MP_ROM_QSTR(MP_QSTR_DATABAR), MP_ROM_INT(BARCODE_DATABAR)}, + {MP_ROM_QSTR(MP_QSTR_DATABAR_EXP), MP_ROM_INT(BARCODE_DATABAR_EXP)}, + {MP_ROM_QSTR(MP_QSTR_CODABAR), MP_ROM_INT(BARCODE_CODABAR)}, + {MP_ROM_QSTR(MP_QSTR_CODE39), MP_ROM_INT(BARCODE_CODE39)}, + {MP_ROM_QSTR(MP_QSTR_PDF417), MP_ROM_INT(BARCODE_PDF417)}, + {MP_ROM_QSTR(MP_QSTR_CODE93), MP_ROM_INT(BARCODE_CODE93)}, + {MP_ROM_QSTR(MP_QSTR_CODE128), MP_ROM_INT(BARCODE_CODE128)}, + #endif + #if defined(IMLIB_ENABLE_IMAGE_IO) + {MP_ROM_QSTR(MP_QSTR_ImageIO), MP_ROM_PTR(&py_imageio_type) }, + #else + {MP_ROM_QSTR(MP_QSTR_ImageIO), MP_ROM_PTR(&py_func_unavailable_obj)}, + #endif + {MP_ROM_QSTR(MP_QSTR_binary_to_grayscale), MP_ROM_PTR(&py_image_binary_to_grayscale_obj)}, + {MP_ROM_QSTR(MP_QSTR_binary_to_rgb), MP_ROM_PTR(&py_image_binary_to_rgb_obj)}, + {MP_ROM_QSTR(MP_QSTR_binary_to_lab), MP_ROM_PTR(&py_image_binary_to_lab_obj)}, + {MP_ROM_QSTR(MP_QSTR_binary_to_yuv), MP_ROM_PTR(&py_image_binary_to_yuv_obj)}, + {MP_ROM_QSTR(MP_QSTR_grayscale_to_binary), MP_ROM_PTR(&py_image_grayscale_to_binary_obj)}, + {MP_ROM_QSTR(MP_QSTR_grayscale_to_rgb), MP_ROM_PTR(&py_image_grayscale_to_rgb_obj)}, + {MP_ROM_QSTR(MP_QSTR_grayscale_to_lab), MP_ROM_PTR(&py_image_grayscale_to_lab_obj)}, + {MP_ROM_QSTR(MP_QSTR_grayscale_to_yuv), MP_ROM_PTR(&py_image_grayscale_to_yuv_obj)}, + {MP_ROM_QSTR(MP_QSTR_rgb_to_binary), MP_ROM_PTR(&py_image_rgb_to_binary_obj)}, + {MP_ROM_QSTR(MP_QSTR_rgb_to_grayscale), MP_ROM_PTR(&py_image_rgb_to_grayscale_obj)}, + {MP_ROM_QSTR(MP_QSTR_rgb_to_lab), MP_ROM_PTR(&py_image_rgb_to_lab_obj)}, + {MP_ROM_QSTR(MP_QSTR_rgb_to_yuv), MP_ROM_PTR(&py_image_rgb_to_yuv_obj)}, + {MP_ROM_QSTR(MP_QSTR_lab_to_binary), MP_ROM_PTR(&py_image_lab_to_binary_obj)}, + {MP_ROM_QSTR(MP_QSTR_lab_to_grayscale), MP_ROM_PTR(&py_image_lab_to_grayscale_obj)}, + {MP_ROM_QSTR(MP_QSTR_lab_to_rgb), MP_ROM_PTR(&py_image_lab_to_rgb_obj)}, + {MP_ROM_QSTR(MP_QSTR_lab_to_yuv), MP_ROM_PTR(&py_image_lab_to_yuv_obj)}, + {MP_ROM_QSTR(MP_QSTR_yuv_to_binary), MP_ROM_PTR(&py_image_yuv_to_binary_obj)}, + {MP_ROM_QSTR(MP_QSTR_yuv_to_grayscale), MP_ROM_PTR(&py_image_yuv_to_grayscale_obj)}, + {MP_ROM_QSTR(MP_QSTR_yuv_to_rgb), MP_ROM_PTR(&py_image_yuv_to_rgb_obj)}, + {MP_ROM_QSTR(MP_QSTR_yuv_to_lab), MP_ROM_PTR(&py_image_yuv_to_lab_obj)}, + {MP_ROM_QSTR(MP_QSTR_Image), MP_ROM_PTR(&py_image_load_image_obj)}, + {MP_ROM_QSTR(MP_QSTR_HaarCascade), MP_ROM_PTR(&py_image_load_cascade_obj)}, + #if defined(IMLIB_ENABLE_DESCRIPTOR) && defined(IMLIB_ENABLE_IMAGE_FILE_IO) + {MP_ROM_QSTR(MP_QSTR_load_descriptor), MP_ROM_PTR(&py_image_load_descriptor_obj)}, + {MP_ROM_QSTR(MP_QSTR_save_descriptor), MP_ROM_PTR(&py_image_save_descriptor_obj)}, + #else + {MP_ROM_QSTR(MP_QSTR_load_descriptor), MP_ROM_PTR(&py_func_unavailable_obj)}, + {MP_ROM_QSTR(MP_QSTR_save_descriptor), MP_ROM_PTR(&py_func_unavailable_obj)}, + #endif //IMLIB_ENABLE_DESCRIPTOR && IMLIB_ENABLE_IMAGE_FILE_IO + #if defined(IMLIB_ENABLE_DESCRIPTOR) + {MP_ROM_QSTR(MP_QSTR_match_descriptor), MP_ROM_PTR(&py_image_match_descriptor_obj)} + #else + {MP_ROM_QSTR(MP_QSTR_match_descriptor), MP_ROM_PTR(&py_func_unavailable_obj)} + #endif +}; + +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t image_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t) &globals_dict +}; + +MP_REGISTER_MODULE(MP_QSTR_image, image_module, 1); diff --git a/github_source/minicv2/reference/py_image.h b/github_source/minicv2/reference/py_image.h new file mode 100644 index 0000000..93dd9f1 --- /dev/null +++ b/github_source/minicv2/reference/py_image.h @@ -0,0 +1,18 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Image Python module. + */ +#ifndef __PY_IMAGE_H__ +#define __PY_IMAGE_H__ +#include "imlib.h" +mp_obj_t py_image(int width, int height, pixformat_t pixfmt, uint32_t size, void *pixels); +mp_obj_t py_image_from_struct(image_t *img); +void *py_image_cobj(mp_obj_t img_obj); +int py_image_descriptor_from_roi(image_t *img, const char *path, rectangle_t *roi); +#endif // __PY_IMAGE_H__ diff --git a/github_source/minicv2/reference/py_imageio.c b/github_source/minicv2/reference/py_imageio.c new file mode 100644 index 0000000..ed16888 --- /dev/null +++ b/github_source/minicv2/reference/py_imageio.c @@ -0,0 +1,647 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Image I/O Python module. + */ +#include "imlib_config.h" +#if defined(IMLIB_ENABLE_IMAGE_IO) + +#include "py/obj.h" +#include "py/nlr.h" +#include "py/mphal.h" +#include "py/runtime.h" + +#include "py_assert.h" +#include "py_helper.h" +#include "py_image.h" +#include "py_imageio.h" + +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) +#include "ff_wrapper.h" +#endif +#include "framebuffer.h" +#include "omv_boardconfig.h" + +#define OLD_BINARY_BPP 0 +#define OLD_GRAYSCALE_BPP 1 +#define OLD_RGB565_BPP 2 +#define OLD_BAYER_BPP 3 +#define OLD_JPG_BPP 4 + +#define MAGIC_SIZE 16 +#define ALIGN_SIZE 16 +#define AFTER_SIZE_PADDING 12 + +#define ORIGINAL_VER 10 +#define RGB565_FIXED_VER 11 +#define NEW_PIXFORMAT_VER 20 + +#ifndef __DCACHE_PRESENT +#define IMAGE_ALIGNMENT 32 // Use 32-byte alignment on MCUs with no cache for DMA buffer alignment. +#else +#define IMAGE_ALIGNMENT __SCB_DCACHE_LINE_SIZE +#endif + +#define IMAGE_T_SIZE_ALIGNED (((sizeof(uint32_t) + sizeof(image_t) + (IMAGE_ALIGNMENT) - 1) \ + / (IMAGE_ALIGNMENT)) \ + * (IMAGE_ALIGNMENT)) + +STATIC size_t image_size_aligned(image_t *image) { + return ((image_size(image) + (IMAGE_ALIGNMENT) - 1) / (IMAGE_ALIGNMENT)) * (IMAGE_ALIGNMENT); +} + +typedef enum image_io_stream_type { + IMAGE_IO_FILE_STREAM, + IMAGE_IO_MEMORY_STREAM, +} image_io_stream_type_t; + +typedef struct py_imageio_obj { + mp_obj_base_t base; + image_io_stream_type_t type; + bool closed; + uint32_t count; + uint32_t offset; + uint32_t ms; + union { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + struct { + FIL fp; + int version; + }; + #endif + struct { + uint32_t size; + uint8_t *buffer; + }; + }; +} py_imageio_obj_t; + +STATIC py_imageio_obj_t *py_imageio_obj(mp_obj_t self) +{ + py_imageio_obj_t *stream = MP_OBJ_TO_PTR(self); + + if (stream->closed) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Stream closed")); + } + + return stream; +} + +STATIC void py_imageio_print(const mp_print_t *print, mp_obj_t self, mp_print_kind_t kind) +{ + py_imageio_obj_t *stream = MP_OBJ_TO_PTR(self); + mp_printf(print, "{\"type\":%s, \"closed\":%s, \"count\":%u, \"offset\":%u, " + "\"version\":%u, \"buffer_size\":%u, \"size\":%u}", + (stream->type == IMAGE_IO_FILE_STREAM) ? "\"file stream\"" : "\"memory stream\"", + stream->closed ? "\"true\"" : "\"false\"", + stream->count, + stream->offset, +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + (stream->type == IMAGE_IO_FILE_STREAM) ? stream->version : 0, +#else + 0, +#endif + (stream->type == IMAGE_IO_FILE_STREAM) ? 0 : stream->size, +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + (stream->type == IMAGE_IO_FILE_STREAM) ? f_size(&stream->fp) : (stream->count * stream->size)); +#else + stream->count * stream->size); +#endif +} + +STATIC mp_obj_t py_imageio_get_type(mp_obj_t self) +{ + py_imageio_obj_t *stream = MP_OBJ_TO_PTR(self); + return mp_obj_new_int(stream->type); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_imageio_get_type_obj, py_imageio_get_type); + +STATIC mp_obj_t py_imageio_is_closed(mp_obj_t self) +{ + py_imageio_obj_t *stream = MP_OBJ_TO_PTR(self); + return mp_obj_new_int(stream->closed); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_imageio_is_closed_obj, py_imageio_is_closed); + +STATIC mp_obj_t py_imageio_count(mp_obj_t self) +{ + py_imageio_obj_t *stream = MP_OBJ_TO_PTR(self); + return mp_obj_new_int(stream->count); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_imageio_count_obj, py_imageio_count); + +STATIC mp_obj_t py_imageio_offset(mp_obj_t self) +{ + py_imageio_obj_t *stream = MP_OBJ_TO_PTR(self); + return mp_obj_new_int(stream->offset); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_imageio_offset_obj, py_imageio_offset); + +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) +STATIC mp_obj_t py_imageio_version(mp_obj_t self) +{ + py_imageio_obj_t *stream = MP_OBJ_TO_PTR(self); + return (stream->type == IMAGE_IO_FILE_STREAM) ? mp_obj_new_int(stream->version) : mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_imageio_version_obj, py_imageio_version); +#endif + +STATIC mp_obj_t py_imageio_buffer_size(mp_obj_t self) +{ + py_imageio_obj_t *stream = MP_OBJ_TO_PTR(self); + + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + if (stream->type == IMAGE_IO_FILE_STREAM) { + return mp_const_none; + } + #endif + + return mp_obj_new_int(stream->size - IMAGE_T_SIZE_ALIGNED); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_imageio_buffer_size_obj, py_imageio_buffer_size); + +STATIC mp_obj_t py_imageio_size(mp_obj_t self) +{ + py_imageio_obj_t *stream = MP_OBJ_TO_PTR(self); + + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + if (stream->type == IMAGE_IO_FILE_STREAM) { + return mp_obj_new_int(f_size(&stream->fp)); + } + #endif + + return mp_obj_new_int(stream->count * stream->size); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_imageio_size_obj, py_imageio_size); + +STATIC mp_obj_t py_imageio_write(mp_obj_t self, mp_obj_t img_obj) +{ + py_imageio_obj_t *stream = py_imageio_obj(self); + image_t *image = py_image_cobj(img_obj); + + uint32_t ms = mp_hal_ticks_ms(), elapsed_ms = ms - stream->ms; + stream->ms = ms; + + if (0) { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + } else if (stream->type == IMAGE_IO_FILE_STREAM) { + FIL *fp = &stream->fp; + + write_long(fp, elapsed_ms); + write_long(fp, image->w); + write_long(fp, image->h); + + char padding[ALIGN_SIZE] = {}; + + if (stream->version < NEW_PIXFORMAT_VER) { + if (image->pixfmt == PIXFORMAT_BINARY) { + write_long(fp, OLD_BINARY_BPP); + } else if (image->pixfmt == PIXFORMAT_GRAYSCALE) { + write_long(fp, OLD_GRAYSCALE_BPP); + } else if (image->pixfmt == PIXFORMAT_RGB565) { + write_long(fp, OLD_RGB565_BPP); + } else if (image->pixfmt == PIXFORMAT_BAYER) { + write_long(fp, OLD_BAYER_BPP); + } else if (image->pixfmt == PIXFORMAT_JPEG) { + write_long(fp, image->size); + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid image stream bpp")); + } + } else { + write_long(fp, image->pixfmt); + write_long(fp, image->size); + write_data(fp, padding, AFTER_SIZE_PADDING); + } + + uint32_t size = image_size(image); + write_data(fp, image->data, size); + + if (size % ALIGN_SIZE) { + write_data(fp, padding, ALIGN_SIZE - (size % ALIGN_SIZE)); + } + + // Seeking to the middle of a file and writing data corrupts the remainder of the file. So, + // truncate the rest of the file when this happens to prevent crashing because of this. + if (!f_eof(fp)) { + file_truncate(fp); + } + + stream->count = stream->offset + 1; + #endif + } else if (stream->type == IMAGE_IO_MEMORY_STREAM) { + if (stream->offset == stream->count) { + mp_raise_msg(&mp_type_EOFError, MP_ERROR_TEXT("End of stream")); + } + + uint32_t size = image_size(image); + + if (stream->size < size) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid frame size")); + } + + *((uint32_t *) (stream->buffer + (stream->offset * stream->size))) = elapsed_ms; + memcpy(stream->buffer + (stream->offset * stream->size) + sizeof(uint32_t), image, sizeof(image_t)); + memcpy(stream->buffer + (stream->offset * stream->size) + IMAGE_T_SIZE_ALIGNED, image->data, size); + } + + stream->offset += 1; + + return self; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_imageio_write_obj, py_imageio_write); + +STATIC void int_py_imageio_pause(py_imageio_obj_t *stream, bool pause) +{ + uint32_t elapsed_ms; + + if (0) { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + } else if (stream->type == IMAGE_IO_FILE_STREAM) { + read_long(&stream->fp, &elapsed_ms); + #endif + } else if (stream->type == IMAGE_IO_MEMORY_STREAM) { + elapsed_ms = *((uint32_t *) (stream->buffer + (stream->offset * stream->size))); + } + + while (pause && ((mp_hal_ticks_ms() - stream->ms) < elapsed_ms)) { + __WFI(); + } + + stream->ms += elapsed_ms; +} + +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) +STATIC void int_py_imageio_read_chunk(py_imageio_obj_t *stream, image_t *image, bool pause) +{ + FIL *fp = &stream->fp; + + if (f_eof(fp)) { + mp_raise_msg(&mp_type_EOFError, MP_ERROR_TEXT("End of stream")); + } + + int_py_imageio_pause(stream, pause); + + read_long(fp, (uint32_t *) &image->w); + read_long(fp, (uint32_t *) &image->h); + + uint32_t bpp; + read_long(fp, (uint32_t *) &bpp); + + if (stream->version < NEW_PIXFORMAT_VER) { + if (bpp < 0) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Invalid image stream bpp")); + } else if (bpp == OLD_BINARY_BPP) { + image->pixfmt = PIXFORMAT_BINARY; + } else if (bpp == OLD_GRAYSCALE_BPP) { + image->pixfmt = PIXFORMAT_GRAYSCALE; + } else if (bpp == OLD_RGB565_BPP) { + image->pixfmt = PIXFORMAT_RGB565; + } else if (bpp == OLD_BAYER_BPP) { + image->pixfmt = PIXFORMAT_BAYER; + } else if (bpp >= OLD_JPG_BPP) { + image->pixfmt = PIXFORMAT_JPEG; + } + } else { + if (!IMLIB_PIXFORMAT_IS_VALID(bpp)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Invalid image stream pixformat")); + } + + image->pixfmt = bpp; + read_long(fp, (uint32_t *) &image->size); + + char ignore[AFTER_SIZE_PADDING]; + read_data(fp, ignore, AFTER_SIZE_PADDING); + } +} +#endif + +STATIC mp_obj_t py_imageio_read(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + + py_imageio_obj_t *stream = py_imageio_obj(args[0]); + + mp_obj_t copy_to_fb_obj = py_helper_keyword_object(n_args, args, 1, kw_args, + MP_OBJ_NEW_QSTR(MP_QSTR_copy_to_fb), NULL); + bool copy_to_fb = true; + image_t *arg_other = NULL; + + if (copy_to_fb_obj) { + if (mp_obj_is_integer(copy_to_fb_obj)) { + copy_to_fb = mp_obj_get_int(copy_to_fb_obj); + } else { + arg_other = py_helper_arg_to_image_mutable(copy_to_fb_obj); + } + } + + image_t image = {}; + + bool pause = py_helper_keyword_int(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_pause), true); + + if (0) { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + } else if (stream->type == IMAGE_IO_FILE_STREAM) { + FIL *fp = &stream->fp; + + if (f_eof(fp)) { + if (!py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_loop), true)) { + return mp_const_none; + } + + file_seek(fp, MAGIC_SIZE); // skip past the header + + stream->offset = 0; + + if (f_eof(fp)) { // empty file + return mp_const_none; + } + } + + int_py_imageio_read_chunk(stream, &image, pause); + #endif + } else if (stream->type == IMAGE_IO_MEMORY_STREAM) { + if (stream->offset == stream->count) { + mp_raise_msg(&mp_type_EOFError, MP_ERROR_TEXT("End of stream")); + } + + int_py_imageio_pause(stream, pause); + + memcpy(&image, stream->buffer + (stream->offset * stream->size) + sizeof(uint32_t), sizeof(image_t)); + } + + uint32_t size = image_size(&image); + + if (copy_to_fb) { + py_helper_set_to_framebuffer(&image); + } else if (arg_other) { + PY_ASSERT_TRUE_MSG((size <= image_size(arg_other)), + "The new image won't fit in the target frame buffer!"); + image.data = arg_other->data; + } else { + image.data = xalloc(size); + } + + if (0) { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + } else if (stream->type == IMAGE_IO_FILE_STREAM) { + FIL *fp = &stream->fp; + read_data(fp, image.data, size); + + // Check if original byte reversed data. + if ((image.pixfmt == PIXFORMAT_RGB565) && (stream->version == ORIGINAL_VER)) { + uint32_t *data_ptr = (uint32_t *) image.data; + size_t data_len = image.w * image.h; + + for (; data_len >= 2; data_len -= 2, data_ptr += 1) { + *data_ptr = __REV16(*data_ptr); // long aligned + } + + if (data_len) { + *((uint16_t *) data_ptr) = __REV16(*((uint16_t *) data_ptr)); // word aligned + } + } + + if (size % ALIGN_SIZE) { + char ignore[ALIGN_SIZE]; + read_data(fp, ignore, ALIGN_SIZE - (size % ALIGN_SIZE)); + } + + if (stream->offset >= stream->count) { + stream->count = stream->offset + 1; + } + #endif + } else if (stream->type == IMAGE_IO_MEMORY_STREAM) { + memcpy(image.data, stream->buffer + (stream->offset * stream->size) + IMAGE_T_SIZE_ALIGNED, size); + } + + stream->offset += 1; + + py_helper_update_framebuffer(&image); + + if (arg_other) { + memcpy(arg_other, &image, sizeof(image_t)); + } + + if (copy_to_fb) { + framebuffer_update_jpeg_buffer(); + } + + return py_image_from_struct(&image); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_imageio_read_obj, 1, py_imageio_read); + +STATIC mp_obj_t py_imageio_seek(mp_obj_t self, mp_obj_t offs) +{ + py_imageio_obj_t *stream = py_imageio_obj(self); + int offset = mp_obj_get_int(offs); + + if ((offset < 0) || ((stream->type == IMAGE_IO_MEMORY_STREAM) && (stream->count <= offset))) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid stream offset")); + } + + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + if (stream->type == IMAGE_IO_FILE_STREAM) { + FIL *fp = &stream->fp; + file_seek(fp, MAGIC_SIZE); // skip past the file header + + for (int i = 0; i < offset; i++) { + image_t image = {}; + int_py_imageio_read_chunk(stream, &image, false); + uint32_t size = image_size(&image); + + if (size % ALIGN_SIZE) { + size += ALIGN_SIZE - (size % ALIGN_SIZE); + } + + file_seek(fp, f_tell(fp) + size); + } + + if (stream->offset >= stream->count) { + stream->count = offset + 1; + } + } + #endif + + stream->offset = offset; + + return self; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_imageio_seek_obj, py_imageio_seek); + +STATIC mp_obj_t py_imageio_sync(mp_obj_t self) +{ + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + py_imageio_obj_t *stream = py_imageio_obj(self); + + if (stream->type == IMAGE_IO_FILE_STREAM) { + file_sync(&stream->fp); + } + #endif + + return self; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_imageio_sync_obj, py_imageio_sync); + +STATIC mp_obj_t py_imageio_close(mp_obj_t self) +{ + py_imageio_obj_t *stream = py_imageio_obj(self); + + if (0) { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + } else if (stream->type == IMAGE_IO_FILE_STREAM) { + file_close(&stream->fp); + #endif + } else if (stream->type == IMAGE_IO_MEMORY_STREAM) { + fb_alloc_free_till_mark_past_mark_permanent(); + } + + stream->closed = true; + + return self; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_imageio_close_obj, py_imageio_close); + +STATIC mp_obj_t py_imageio_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) +{ + mp_arg_check_num(n_args, n_kw, 2, 2, false); + py_imageio_obj_t *stream = m_new_obj(py_imageio_obj_t); + stream->base.type = &py_imageio_type; + stream->closed = false; + + if (0) { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + } else if (mp_obj_is_str(args[0])) { // File Stream I/O + FIL *fp = &stream->fp; + stream->type = IMAGE_IO_FILE_STREAM; + stream->count = 0; + + char mode = mp_obj_str_get_str(args[1])[0]; + + if ((mode == 'W') || (mode == 'w')) { + file_read_write_open_always(fp, mp_obj_str_get_str(args[0])); + const char string[] = "OMV IMG STR V2.0"; + stream->version = NEW_PIXFORMAT_VER; + + // Overwrite if file is too small. + if (f_size(fp) < MAGIC_SIZE) { + write_data(fp, string, sizeof(string) - 1); // exclude null terminator + } else { + uint8_t version_hi, period, version_lo; + char temp[sizeof(string) - 3] = {}; + read_data(fp, temp, sizeof(temp) - 1); + read_byte(fp, &version_hi); + read_byte(fp, &period); + read_byte(fp, &version_lo); + int version = ((version_hi - '0') * 10) + (version_lo - '0'); + + // Overwrite if file magic does not match. + if (strcmp(string, temp) + || (period != ((uint8_t) '.')) + || (version != ORIGINAL_VER) + || (version != RGB565_FIXED_VER) + || (version != NEW_PIXFORMAT_VER)) { + file_seek(fp, 0); + write_data(fp, string, sizeof(string) - 1); // exclude null terminator + } else { + file_close(fp); + mode = 'R'; + } + } + } + + if ((mode == 'R') || (mode == 'r')) { + uint8_t version_hi, version_lo; + file_read_write_open_existing(fp, mp_obj_str_get_str(args[0])); + read_long_expect(fp, *((uint32_t *) "OMV ")); // OpenMV + read_long_expect(fp, *((uint32_t *) "IMG ")); // Image + read_long_expect(fp, *((uint32_t *) "STR ")); // Stream + read_byte_expect(fp, 'V'); + read_byte(fp, &version_hi); + read_byte_expect(fp, '.'); + read_byte(fp, &version_lo); + + stream->version = ((version_hi - '0') * 10) + (version_lo - '0'); + + if ((stream->version != ORIGINAL_VER) + && (stream->version != RGB565_FIXED_VER) + && (stream->version != NEW_PIXFORMAT_VER)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected version V1.0, V1.1, or V2.0")); + } + } else if ((mode != 'W') && (mode != 'w')) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid stream mode, expected 'R/r' or 'W/w'")); + } + #endif + } else if (mp_obj_is_type(args[0], &mp_type_tuple)) { // Memory Stream I/O + stream->type = IMAGE_IO_MEMORY_STREAM; + + mp_obj_t *image_info; + mp_obj_get_array_fixed_n(args[0], 3, &image_info); + int w = mp_obj_get_int(image_info[0]); + int h = mp_obj_get_int(image_info[1]); + int pixfmt = mp_obj_get_int(image_info[2]); + + if (!IMLIB_PIXFORMAT_IS_VALID(pixfmt)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Invalid image stream pixformat")); + } + + image_t image = {.w = w, .h = h, .pixfmt = pixfmt}; + + // Estimate that the compressed image will fit in less than 2 bits per pixel. + if (image.is_compressed) { + image.h *= 2; // double calculated image size + image.pixfmt = PIXFORMAT_BINARY; + } + + stream->count = mp_obj_get_int(args[1]); + stream->size = IMAGE_T_SIZE_ALIGNED + image_size_aligned(&image); + + fb_alloc_mark(); + stream->buffer = fb_alloc(stream->count * stream->size, FB_ALLOC_PREFER_SIZE | FB_ALLOC_CACHE_ALIGN); + fb_alloc_mark_permanent(); + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid stream type")); + } + + stream->offset = 0; + stream->ms = mp_hal_ticks_ms(); + + return MP_OBJ_FROM_PTR(stream); +} + +STATIC const mp_rom_map_elem_t py_imageio_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_imageio) }, + { MP_ROM_QSTR(MP_QSTR_FILE_STREAM), MP_ROM_INT(IMAGE_IO_FILE_STREAM) }, + { MP_ROM_QSTR(MP_QSTR_MEMORY_STREAM), MP_ROM_INT(IMAGE_IO_MEMORY_STREAM) }, + { MP_ROM_QSTR(MP_QSTR_type), MP_ROM_PTR(&py_imageio_get_type_obj) }, + { MP_ROM_QSTR(MP_QSTR_is_closed), MP_ROM_PTR(&py_imageio_is_closed_obj) }, + { MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&py_imageio_count_obj) }, + { MP_ROM_QSTR(MP_QSTR_offset), MP_ROM_PTR(&py_imageio_offset_obj) }, + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + { MP_ROM_QSTR(MP_QSTR_version), MP_ROM_PTR(&py_imageio_version_obj) }, + #else + { MP_ROM_QSTR(MP_QSTR_version), MP_ROM_PTR(&py_func_unavailable_obj) }, + #endif + { MP_ROM_QSTR(MP_QSTR_buffer_size), MP_ROM_PTR(&py_imageio_buffer_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_size), MP_ROM_PTR(&py_imageio_size_obj) }, + { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&py_imageio_write_obj) }, + { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&py_imageio_read_obj) }, + { MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&py_imageio_seek_obj) }, + { MP_ROM_QSTR(MP_QSTR_sync), MP_ROM_PTR(&py_imageio_sync_obj) }, + { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&py_imageio_close_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_imageio_locals_dict, py_imageio_locals_dict_table); + +const mp_obj_type_t py_imageio_type = { + { &mp_type_type }, + .name = MP_QSTR_ImageIO, + .print = py_imageio_print, + .make_new = py_imageio_make_new, + .locals_dict = (mp_obj_dict_t *) &py_imageio_locals_dict, +}; + +#endif // IMLIB_ENABLE_IMAGE_IO diff --git a/github_source/minicv2/reference/py_imageio.h b/github_source/minicv2/reference/py_imageio.h new file mode 100644 index 0000000..2b26009 --- /dev/null +++ b/github_source/minicv2/reference/py_imageio.h @@ -0,0 +1,14 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Image I/O Python module. + */ +#ifndef __PY_IMAGE_IO_H__ +#define __PY_IMAGE_IO_H__ +extern const mp_obj_type_t py_imageio_type; +#endif // __PY_IMAGE_IO_H__ diff --git a/github_source/minicv2/reference/py_mjpeg.c b/github_source/minicv2/reference/py_mjpeg.c new file mode 100644 index 0000000..385ea26 --- /dev/null +++ b/github_source/minicv2/reference/py_mjpeg.c @@ -0,0 +1,140 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * MJPEG Python module. + */ +#include "py/obj.h" +#include "py/runtime.h" +#include "ff_wrapper.h" +#include "framebuffer.h" +#include "py_assert.h" +#include "py_helper.h" +#include "py_image.h" + +static const mp_obj_type_t py_mjpeg_type; // forward declare +// Mjpeg class +typedef struct py_mjpeg_obj { + mp_obj_base_t base; + int width; + int height; + uint32_t frames; + uint32_t bytes; + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + FIL fp; + #endif +} py_mjpeg_obj_t; + +static mp_obj_t py_mjpeg_open(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + py_mjpeg_obj_t *mjpeg = m_new_obj(py_mjpeg_obj_t); + mjpeg->width = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_width), MAIN_FB()->w); + mjpeg->height = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_height), MAIN_FB()->h); + mjpeg->frames = 0; // private + mjpeg->bytes = 0; // private + mjpeg->base.type = &py_mjpeg_type; + + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + file_write_open(&mjpeg->fp, mp_obj_str_get_str(args[0])); + mjpeg_open(&mjpeg->fp, mjpeg->width, mjpeg->height); + #else + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image I/O is not supported")); + #endif + return mjpeg; +} + +static mp_obj_t py_mjpeg_width(mp_obj_t mjpeg_obj) +{ + py_mjpeg_obj_t *arg_mjpeg = mjpeg_obj; + return mp_obj_new_int(arg_mjpeg->width); +} + +static mp_obj_t py_mjpeg_height(mp_obj_t mjpeg_obj) +{ + py_mjpeg_obj_t *arg_mjpeg = mjpeg_obj; + return mp_obj_new_int(arg_mjpeg->height); +} + +static mp_obj_t py_mjpeg_size(mp_obj_t mjpeg_obj) +{ + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + py_mjpeg_obj_t *arg_mjpeg = mjpeg_obj; + return mp_obj_new_int(f_size(&arg_mjpeg->fp)); + #else + return mp_obj_new_int(0); + #endif +} + +static mp_obj_t py_mjpeg_add_frame(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + py_mjpeg_obj_t *arg_mjpeg = args[0]; + image_t *arg_img = py_image_cobj(args[1]); + PY_ASSERT_FALSE_MSG((arg_mjpeg->width != arg_img->w) + || (arg_mjpeg->height != arg_img->h), + "Unexpected image geometry"); + + int arg_q = py_helper_keyword_int(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_quality), 50); + arg_q = IM_MIN(IM_MAX(arg_q, 1), 100); + mjpeg_add_frame(&arg_mjpeg->fp, &arg_mjpeg->frames, &arg_mjpeg->bytes, arg_img, arg_q); + #else + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image I/O is not supported")); + #endif + return mp_const_none; +} + +static mp_obj_t py_mjpeg_close(mp_obj_t mjpeg_obj, mp_obj_t fps_obj) +{ + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + py_mjpeg_obj_t *arg_mjpeg = mjpeg_obj; + mjpeg_close(&arg_mjpeg->fp, &arg_mjpeg->frames, &arg_mjpeg->bytes, mp_obj_get_float(fps_obj)); + #endif + return mp_const_none; +} + +static void py_mjpeg_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_mjpeg_obj_t *self = self_in; + mp_printf(print, "", self->width, self->height); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_mjpeg_width_obj, py_mjpeg_width); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_mjpeg_height_obj, py_mjpeg_height); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_mjpeg_size_obj, py_mjpeg_size); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_mjpeg_add_frame_obj, 2, py_mjpeg_add_frame); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_mjpeg_close_obj, py_mjpeg_close); +static const mp_map_elem_t locals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR_width), (mp_obj_t)&py_mjpeg_width_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_height), (mp_obj_t)&py_mjpeg_height_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_size), (mp_obj_t)&py_mjpeg_size_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_add_frame), (mp_obj_t)&py_mjpeg_add_frame_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_close), (mp_obj_t)&py_mjpeg_close_obj }, + { NULL, NULL }, +}; +STATIC MP_DEFINE_CONST_DICT(locals_dict, locals_dict_table); + +static const mp_obj_type_t py_mjpeg_type = { + { &mp_type_type }, + .name = MP_QSTR_Mjpeg, + .print = py_mjpeg_print, + .locals_dict = (mp_obj_t)&locals_dict, +}; + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_mjpeg_open_obj, 1, py_mjpeg_open); +static const mp_map_elem_t globals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_mjpeg) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_Mjpeg), (mp_obj_t)&py_mjpeg_open_obj }, + { NULL, NULL }, +}; +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t mjpeg_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t)&globals_dict, +}; + +MP_REGISTER_MODULE(MP_QSTR_mjpeg, mjpeg_module, 1); diff --git a/github_source/minicv2/reference/py_omv.c b/github_source/minicv2/reference/py_omv.c new file mode 100644 index 0000000..879586e --- /dev/null +++ b/github_source/minicv2/reference/py_omv.c @@ -0,0 +1,84 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * OMV Python module. + */ +#include +#include +#include +#include "py/obj.h" +#include "usbdbg.h" +#include "framebuffer.h" +#include "omv_boardconfig.h" + +static mp_obj_t py_omv_version_string() +{ + char str[12]; + snprintf(str, 12, "%d.%d.%d", + FIRMWARE_VERSION_MAJOR, + FIRMWARE_VERSION_MINOR, + FIRMWARE_VERSION_PATCH); + return mp_obj_new_str(str, strlen(str)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_omv_version_string_obj, py_omv_version_string); + +static mp_obj_t py_omv_arch() +{ + char *str = OMV_ARCH_STR; + return mp_obj_new_str(str, strlen(str)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_omv_arch_obj, py_omv_arch); + +static mp_obj_t py_omv_board_type() +{ + char *str = OMV_BOARD_TYPE; + return mp_obj_new_str(str, strlen(str)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_omv_board_type_obj, py_omv_board_type); + +static mp_obj_t py_omv_board_id() +{ + char str[25]; + snprintf(str, 25, "%08X%08X%08X", + *((unsigned int *) (OMV_UNIQUE_ID_ADDR + 8)), + *((unsigned int *) (OMV_UNIQUE_ID_ADDR + 4)), + *((unsigned int *) (OMV_UNIQUE_ID_ADDR + 0))); + return mp_obj_new_str(str, strlen(str)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_omv_board_id_obj, py_omv_board_id); + +static mp_obj_t py_omv_disable_fb(uint n_args, const mp_obj_t *args) +{ + if (!n_args) { + return mp_obj_new_bool(!fb_get_streaming_enabled()); + } + fb_set_streaming_enabled(!mp_obj_get_int(args[0])); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_omv_disable_fb_obj, 0, 1, py_omv_disable_fb); + +static const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_omv) }, + { MP_ROM_QSTR(MP_QSTR_version_major), MP_ROM_INT(FIRMWARE_VERSION_MAJOR) }, + { MP_ROM_QSTR(MP_QSTR_version_minor), MP_ROM_INT(FIRMWARE_VERSION_MINOR) }, + { MP_ROM_QSTR(MP_QSTR_version_patch), MP_ROM_INT(FIRMWARE_VERSION_PATCH) }, + { MP_ROM_QSTR(MP_QSTR_version_string), MP_ROM_PTR(&py_omv_version_string_obj) }, + { MP_ROM_QSTR(MP_QSTR_arch), MP_ROM_PTR(&py_omv_arch_obj) }, + { MP_ROM_QSTR(MP_QSTR_board_type), MP_ROM_PTR(&py_omv_board_type_obj) }, + { MP_ROM_QSTR(MP_QSTR_board_id), MP_ROM_PTR(&py_omv_board_id_obj) }, + { MP_ROM_QSTR(MP_QSTR_disable_fb), MP_ROM_PTR(&py_omv_disable_fb_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t omv_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t) &globals_dict, +}; + +MP_REGISTER_MODULE(MP_QSTR_omv, omv_module, 1); diff --git a/github_source/minicv2/reference/py_sensor.c b/github_source/minicv2/reference/py_sensor.c new file mode 100644 index 0000000..dc9f3dd --- /dev/null +++ b/github_source/minicv2/reference/py_sensor.c @@ -0,0 +1,1173 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Sensor Python module. + */ +#include +#include "py/mphal.h" +#include "py/runtime.h" + +#if MICROPY_PY_SENSOR + +#include "cambus.h" +#include "sensor.h" +#include "imlib.h" +#include "xalloc.h" +#include "py_assert.h" +#include "py_image.h" +#if MICROPY_PY_IMU +#include "py_imu.h" +#endif +#include "omv_boardconfig.h" +#include "py_helper.h" +#include "framebuffer.h" + +extern sensor_t sensor; +static mp_obj_t vsync_callback = mp_const_none; +static mp_obj_t frame_callback = mp_const_none; + +#define sensor_raise_error(err) mp_raise_msg(&mp_type_RuntimeError, (mp_rom_error_text_t) sensor_strerror(err)) + +#if MICROPY_PY_IMU +static void do_auto_rotation(int pitch_deadzone, int roll_activezone) +{ + if (sensor_get_auto_rotation()) { + float pitch = py_imu_pitch_rotation(); + if (((pitch <= (90 - pitch_deadzone)) || ((90 + pitch_deadzone) < pitch)) + && ((pitch <= (270 - pitch_deadzone)) || ((270 + pitch_deadzone) < pitch))) { // disable when 90 or 270 + float roll = py_imu_roll_rotation(); + if (((360 - roll_activezone) <= roll) || (roll < (0 + roll_activezone)) ) { // center is 0/360, upright + sensor_set_hmirror(false); + sensor_set_vflip(false); + sensor_set_transpose(false); + } else if (((270 - roll_activezone) <= roll) && (roll < (270 + roll_activezone))) { // center is 270, rotated right + sensor_set_hmirror(true); + sensor_set_vflip(false); + sensor_set_transpose(true); + } else if (((180 - roll_activezone) <= roll) && (roll < (180 + roll_activezone))) { // center is 180, upside down + sensor_set_hmirror(true); + sensor_set_vflip(true); + sensor_set_transpose(false); + } else if (((90 - roll_activezone) <= roll) && (roll < (90 + roll_activezone))) { // center is 90, rotated left + sensor_set_hmirror(false); + sensor_set_vflip(true); + sensor_set_transpose(true); + } + } + } +} +#endif // MICROPY_PY_IMU + +static mp_obj_t py_sensor__init__() +{ + // This is the module init function, not the sensor init function, + // it gets called when the module is imported. This is good + // place to check if the sensor was detected or not. + if (sensor_is_detected() == false) { + sensor_raise_error(SENSOR_ERROR_ISC_UNDETECTED); + } + return mp_const_none; +} + +static mp_obj_t py_sensor_reset() +{ + int error = sensor_reset(); + if (error != 0) { + sensor_raise_error(error); + } +#if MICROPY_PY_IMU + // +-10 degree dead-zone around pitch 90/270. + // +-45 degree active-zone around roll 0/90/180/270/360. + do_auto_rotation(10, 45); + // We're setting the dead-zone on pitch because roll readings are invalid there. + // We're setting the full range on roll to set the initial state. +#endif // MICROPY_PY_IMU + return mp_const_none; +} + +static mp_obj_t py_sensor_sleep(mp_obj_t enable) +{ + PY_ASSERT_FALSE_MSG(sensor_sleep(mp_obj_is_true(enable)) != 0, "Sleep Failed"); + return mp_const_none; +} + +static mp_obj_t py_sensor_shutdown(mp_obj_t enable) +{ + PY_ASSERT_FALSE_MSG(sensor_shutdown(mp_obj_is_true(enable)) != 0, "Shutdown Failed"); + return mp_const_none; +} + +static mp_obj_t py_sensor_flush() +{ + framebuffer_update_jpeg_buffer(); + return mp_const_none; +} + +static mp_obj_t py_sensor_snapshot(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ +#if MICROPY_PY_IMU + // +-10 degree dead-zone around pitch 90/270. + // +-35 degree active-zone around roll 0/90/180/270/360. + do_auto_rotation(10, 35); + // We're setting the dead-zone on pitch because roll readings are invalid there. + // We're not setting the full range on roll to prevent oscillation. +#endif // MICROPY_PY_IMU + + mp_obj_t image = py_image(0, 0, 0, 0, 0); + int error = sensor.snapshot(&sensor, (image_t *) py_image_cobj(image), 0); + if (error != 0) { + sensor_raise_error(error); + } + return image; +} + +static mp_obj_t py_sensor_skip_frames(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + mp_map_elem_t *kw_arg = mp_map_lookup(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_time), MP_MAP_LOOKUP); + mp_int_t time = 300; // OV Recommended. + + if (kw_arg != NULL) { + time = mp_obj_get_int(kw_arg->value); + } + + uint32_t millis = mp_hal_ticks_ms(); + + if (!n_args) { + while ((mp_hal_ticks_ms() - millis) < time) { // 32-bit math handles wrap around... + py_sensor_snapshot(0, NULL, NULL); + } + } else { + for (int i = 0, j = mp_obj_get_int(args[0]); i < j; i++) { + if ((kw_arg != NULL) && ((mp_hal_ticks_ms() - millis) >= time)) { + break; + } + + py_sensor_snapshot(0, NULL, NULL); + } + } + + return mp_const_none; +} + +static mp_obj_t py_sensor_width() +{ + return mp_obj_new_int(resolution[sensor.framesize][0]); +} + +static mp_obj_t py_sensor_height() +{ + return mp_obj_new_int(resolution[sensor.framesize][1]); +} + +static mp_obj_t py_sensor_get_fb() +{ + if (framebuffer_get_depth() < 0) { + return mp_const_none; + } + + image_t image; + framebuffer_init_image(&image); + return py_image_from_struct(&image); +} + +static mp_obj_t py_sensor_get_id() +{ + return mp_obj_new_int(sensor_get_id()); +} + +static mp_obj_t py_sensor_get_frame_available() +{ + return mp_obj_new_bool(framebuffer->tail != framebuffer->head); +} + +static mp_obj_t py_sensor_alloc_extra_fb(mp_obj_t w_obj, mp_obj_t h_obj, mp_obj_t pixfmt_obj) +{ + int w = mp_obj_get_int(w_obj); + PY_ASSERT_TRUE_MSG(w > 0, "Width must be > 0"); + + int h = mp_obj_get_int(h_obj); + PY_ASSERT_TRUE_MSG(h > 0, "Height must be > 0"); + + pixformat_t pixfmt = mp_obj_get_int(pixfmt_obj); + PY_ASSERT_TRUE_MSG(IMLIB_PIXFORMAT_IS_VALID(pixfmt), "Invalid Pixel Format"); + + image_t img = {.w = w, .h = h, .pixfmt = pixfmt, .size = 0, .pixels = 0}; + + // Alloc image first (could fail) then alloc RAM so that there's no leak on failure. + mp_obj_t r = py_image_from_struct(&img); + + fb_alloc_mark(); + ((image_t *) py_image_cobj(r))->pixels = fb_alloc0(image_size(&img), FB_ALLOC_NO_HINT); + fb_alloc_mark_permanent(); // pixels will not be popped on exception + return r; +} + +static mp_obj_t py_sensor_dealloc_extra_fb() +{ + fb_alloc_free_till_mark_past_mark_permanent(); + return mp_const_none; +} + +static mp_obj_t py_sensor_set_pixformat(mp_obj_t pixformat) +{ + int error = sensor_set_pixformat(mp_obj_get_int(pixformat)); + if (error != 0) { + sensor_raise_error(error); + } + return mp_const_none; +} + +static mp_obj_t py_sensor_get_pixformat() +{ + if (sensor.pixformat == PIXFORMAT_INVALID) { + sensor_raise_error(SENSOR_ERROR_INVALID_PIXFORMAT); + } + return mp_obj_new_int(sensor.pixformat); +} + +static mp_obj_t py_sensor_set_framesize(mp_obj_t framesize) +{ + int error = sensor_set_framesize(mp_obj_get_int(framesize)); + if (error != 0) { + sensor_raise_error(error); + } + return mp_const_none; +} + +static mp_obj_t py_sensor_get_framesize() +{ + if (sensor.framesize == FRAMESIZE_INVALID) { + sensor_raise_error(SENSOR_ERROR_INVALID_FRAMESIZE); + } + return mp_obj_new_int(sensor.framesize); +} + +static mp_obj_t py_sensor_set_framerate(mp_obj_t framerate) +{ + int error = sensor_set_framerate(mp_obj_get_int(framerate)); + if (error != 0) { + sensor_raise_error(error); + } + return mp_const_none; +} + +static mp_obj_t py_sensor_get_framerate() +{ + if (sensor.framerate == 0) { + sensor_raise_error(SENSOR_ERROR_INVALID_FRAMERATE); + } + return mp_obj_new_int(sensor.framerate); +} + +static mp_obj_t py_sensor_set_windowing(uint n_args, const mp_obj_t *args) +{ + if (sensor.framesize == FRAMESIZE_INVALID) { + sensor_raise_error(SENSOR_ERROR_INVALID_FRAMESIZE); + } + + rectangle_t temp; + temp.x = 0; + temp.y = 0; + temp.w = resolution[sensor.framesize][0]; + temp.h = resolution[sensor.framesize][1]; + + mp_obj_t *array = (mp_obj_t *) args; + mp_uint_t array_len = n_args; + + if (n_args == 1) { + mp_obj_get_array(args[0], &array_len, &array); + } + + rectangle_t r; + + if (array_len == 2) { + r.w = mp_obj_get_int(array[0]); + r.h = mp_obj_get_int(array[1]); + r.x = (temp.w / 2) - (r.w / 2); + r.y = (temp.h / 2) - (r.h / 2); + } else if (array_len == 4) { + r.x = mp_obj_get_int(array[0]); + r.y = mp_obj_get_int(array[1]); + r.w = mp_obj_get_int(array[2]); + r.h = mp_obj_get_int(array[3]); + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("The tuple/list must either be (x, y, w, h) or (w, h)")); + } + + if ((r.w < 1) || (r.h < 1)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Invalid ROI dimensions!")); + } + + if (!rectangle_overlap(&r, &temp)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("ROI does not overlap on the image!")); + } + + rectangle_intersected(&r, &temp); + + int error = sensor_set_windowing(r.x, r.y, r.w, r.h); + if (error != 0) { + sensor_raise_error(error); + } + + return mp_const_none; +} + +static mp_obj_t py_sensor_get_windowing() +{ + if (sensor.framesize == FRAMESIZE_INVALID) { + sensor_raise_error(SENSOR_ERROR_INVALID_FRAMESIZE); + } + + return mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(framebuffer_get_x()), + mp_obj_new_int(framebuffer_get_y()), + mp_obj_new_int(framebuffer_get_u()), + mp_obj_new_int(framebuffer_get_v())}); +} + +static mp_obj_t py_sensor_set_gainceiling(mp_obj_t gainceiling) +{ + gainceiling_t gain; + switch (mp_obj_get_int(gainceiling)) { + case 2: + gain = GAINCEILING_2X; + break; + case 4: + gain = GAINCEILING_4X; + break; + case 8: + gain = GAINCEILING_8X; + break; + case 16: + gain = GAINCEILING_16X; + break; + case 32: + gain = GAINCEILING_32X; + break; + case 64: + gain = GAINCEILING_64X; + break; + case 128: + gain = GAINCEILING_128X; + break; + default: + sensor_raise_error(SENSOR_ERROR_INVALID_ARGUMENT); + break; + } + + if (sensor_set_gainceiling(gain) != 0) { + return mp_const_false; + } + return mp_const_true; +} + +static mp_obj_t py_sensor_set_brightness(mp_obj_t brightness) +{ + if (sensor_set_brightness(mp_obj_get_int(brightness)) != 0) { + return mp_const_false; + } + return mp_const_true; +} + +static mp_obj_t py_sensor_set_contrast(mp_obj_t contrast) +{ + if (sensor_set_contrast(mp_obj_get_int(contrast)) != 0) { + return mp_const_false; + } + return mp_const_true; +} + +static mp_obj_t py_sensor_set_saturation(mp_obj_t saturation) +{ + if (sensor_set_saturation(mp_obj_get_int(saturation)) != 0) { + return mp_const_false; + } + return mp_const_true; +} + +static mp_obj_t py_sensor_set_quality(mp_obj_t qs) +{ + int q = mp_obj_get_int(qs); + PY_ASSERT_TRUE((q >= 0 && q <= 100)); + + q = 100-q; //invert quality + q = 255*q/100; //map to 0->255 + if (sensor_set_quality(q) != 0) { + return mp_const_false; + } + return mp_const_true; +} + +static mp_obj_t py_sensor_set_colorbar(mp_obj_t enable) +{ + if (sensor_set_colorbar(mp_obj_is_true(enable)) != 0) { + return mp_const_false; + } + return mp_const_true; +} + +static mp_obj_t py_sensor_set_auto_gain(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + int enable = mp_obj_get_int(args[0]); + float gain_db = py_helper_keyword_float(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_gain_db), NAN); + float gain_db_ceiling = py_helper_keyword_float(n_args, args, 2, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_gain_db_ceiling), NAN); + + int error = sensor_set_auto_gain(enable, gain_db, gain_db_ceiling); + if (error != 0) { + sensor_raise_error(error); + } + return mp_const_none; +} + +static mp_obj_t py_sensor_get_gain_db() +{ + float gain_db; + int error = sensor_get_gain_db(&gain_db); + if (error != 0) { + sensor_raise_error(error); + } + return mp_obj_new_float(gain_db); +} + +static mp_obj_t py_sensor_set_auto_exposure(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + int exposure_us = py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_exposure_us), -1); + int error = sensor_set_auto_exposure(mp_obj_get_int(args[0]), exposure_us); + if (error != 0) { + sensor_raise_error(error); + } + return mp_const_none; +} + +static mp_obj_t py_sensor_get_exposure_us() +{ + int exposure_us; + int error = sensor_get_exposure_us(&exposure_us); + if (error != 0) { + sensor_raise_error(error); + } + return mp_obj_new_int(exposure_us); +} + +static mp_obj_t py_sensor_set_auto_whitebal(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + int enable = mp_obj_get_int(args[0]); + float rgb_gain_db[3] = {NAN, NAN, NAN}; + py_helper_keyword_float_array(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_rgb_gain_db), rgb_gain_db, 3); + + int error = sensor_set_auto_whitebal(enable, rgb_gain_db[0], rgb_gain_db[1], rgb_gain_db[2]); + if (error != 0) { + sensor_raise_error(error); + } + return mp_const_none; +} + +static mp_obj_t py_sensor_get_rgb_gain_db() +{ + float r_gain_db = 0.0, g_gain_db = 0.0, b_gain_db = 0.0; + int error = sensor_get_rgb_gain_db(&r_gain_db, &g_gain_db, &b_gain_db); + if (error != 0) { + sensor_raise_error(error); + } + return mp_obj_new_tuple(3, (mp_obj_t []) { + mp_obj_new_float(r_gain_db), + mp_obj_new_float(g_gain_db), + mp_obj_new_float(b_gain_db)}); +} + +static mp_obj_t py_sensor_set_hmirror(mp_obj_t enable) +{ + int error = sensor_set_hmirror(mp_obj_is_true(enable)); + if (error != 0) { + sensor_raise_error(error); + } + return mp_const_none; +} + +static mp_obj_t py_sensor_get_hmirror() +{ + return mp_obj_new_bool(sensor_get_hmirror()); +} + +static mp_obj_t py_sensor_set_vflip(mp_obj_t enable) +{ + int error = sensor_set_vflip(mp_obj_is_true(enable)); + if (error != 0) { + sensor_raise_error(error); + } + return mp_const_none; +} + +static mp_obj_t py_sensor_get_vflip() +{ + return mp_obj_new_bool(sensor_get_vflip()); +} + +static mp_obj_t py_sensor_set_transpose(mp_obj_t enable) +{ + int error = sensor_set_transpose(mp_obj_is_true(enable)); + if (error != 0) { + sensor_raise_error(error); + } + return mp_const_none; +} + +static mp_obj_t py_sensor_get_transpose() +{ + return mp_obj_new_bool(sensor_get_transpose()); +} + +static mp_obj_t py_sensor_set_auto_rotation(mp_obj_t enable) +{ + int error = sensor_set_auto_rotation(mp_obj_is_true(enable)); + if (error != 0) { + sensor_raise_error(error); + } + return mp_const_none; +} + +static mp_obj_t py_sensor_get_auto_rotation() +{ + return mp_obj_new_bool(sensor_get_auto_rotation()); +} + +static mp_obj_t py_sensor_set_framebuffers(mp_obj_t count) +{ + mp_int_t c = mp_obj_get_int(count); + + if (framebuffer->n_buffers == c) { + return mp_const_none; + } + + if (c < 1) { + sensor_raise_error(SENSOR_ERROR_INVALID_ARGUMENT); + } + + int error = sensor_set_framebuffers(c); + if (error != 0) { + sensor_raise_error(error); + } + + return mp_const_none; +} + +static mp_obj_t py_sensor_get_framebuffers() +{ + return mp_obj_new_int(framebuffer->n_buffers); +} + +static mp_obj_t py_sensor_disable_full_flush(uint n_args, const mp_obj_t *args) +{ + if (!n_args) { + return mp_obj_new_bool(sensor.disable_full_flush); + } + + sensor.disable_full_flush = mp_obj_get_int(args[0]); + return mp_const_none; +} + +static mp_obj_t py_sensor_set_special_effect(mp_obj_t sde) +{ + if (sensor_set_special_effect(mp_obj_get_int(sde)) != 0) { + return mp_const_false; + } + return mp_const_true; +} + +static mp_obj_t py_sensor_set_lens_correction(mp_obj_t enable, mp_obj_t radi, mp_obj_t coef) +{ + if (sensor_set_lens_correction(mp_obj_is_true(enable), + mp_obj_get_int(radi), mp_obj_get_int(coef)) != 0) { + return mp_const_false; + } + return mp_const_true; +} + +static void sensor_vsync_callback(uint32_t vsync) +{ + if (mp_obj_is_callable(vsync_callback)) { + mp_call_function_1(vsync_callback, mp_obj_new_int(vsync)); + } +} + +static mp_obj_t py_sensor_set_vsync_callback(mp_obj_t vsync_callback_obj) +{ + if (!mp_obj_is_callable(vsync_callback_obj)) { + vsync_callback = mp_const_none; + sensor_set_vsync_callback(NULL); + } else { + vsync_callback = vsync_callback_obj; + sensor_set_vsync_callback(sensor_vsync_callback); + } + + return mp_const_none; +} + +static void sensor_frame_callback() +{ + if (mp_obj_is_callable(frame_callback)) { + mp_call_function_0(frame_callback); + } +} + +static mp_obj_t py_sensor_set_frame_callback(mp_obj_t frame_callback_obj) +{ + if (!mp_obj_is_callable(frame_callback_obj)) { + frame_callback = mp_const_none; + sensor_set_frame_callback(NULL); + } else { + frame_callback = frame_callback_obj; + sensor_set_frame_callback(sensor_frame_callback); + } + + return mp_const_none; +} + +static mp_obj_t py_sensor_ioctl(uint n_args, const mp_obj_t *args) +{ + mp_obj_t ret_obj = mp_const_none; + int request = mp_obj_get_int(args[0]); + int error = SENSOR_ERROR_INVALID_ARGUMENT; + + switch (request) { + case IOCTL_SET_READOUT_WINDOW: { + if (n_args >= 2) { + int x, y, w, h; + mp_obj_t *array; + mp_uint_t array_len; + mp_obj_get_array(args[1], &array_len, &array); + + if (array_len == 4) { + x = mp_obj_get_int(array[0]); + y = mp_obj_get_int(array[1]); + w = mp_obj_get_int(array[2]); + h = mp_obj_get_int(array[3]); + } else if (array_len == 2) { + w = mp_obj_get_int(array[0]); + h = mp_obj_get_int(array[1]); + x = 0; + y = 0; + } else { + mp_raise_msg(&mp_type_ValueError, + MP_ERROR_TEXT("The tuple/list must either be (x, y, w, h) or (w, h)")); + } + + error = sensor_ioctl(request, x, y, w, h); + } + break; + } + + case IOCTL_GET_READOUT_WINDOW: { + int x, y, w, h; + error = sensor_ioctl(request, &x, &y, &w, &h); + if (error == 0) { + ret_obj = mp_obj_new_tuple(4, (mp_obj_t []) {mp_obj_new_int(x), + mp_obj_new_int(y), + mp_obj_new_int(w), + mp_obj_new_int(h)}); + } + break; + } + + case IOCTL_SET_TRIGGERED_MODE: { + if (n_args >= 2) { + error = sensor_ioctl(request, mp_obj_get_int(args[1])); + } + break; + } + + case IOCTL_GET_TRIGGERED_MODE: { + int enabled; + error = sensor_ioctl(request, &enabled); + if (error == 0) { + ret_obj = mp_obj_new_bool(enabled); + } + break; + } + + #if (OMV_ENABLE_OV5640_AF == 1) + case IOCTL_TRIGGER_AUTO_FOCUS: + case IOCTL_PAUSE_AUTO_FOCUS: + case IOCTL_RESET_AUTO_FOCUS: { + error = sensor_ioctl(request); + break; + } + case IOCTL_WAIT_ON_AUTO_FOCUS: { + error = sensor_ioctl(request, (n_args < 2) ? 5000 : mp_obj_get_int(args[1])); + break; + } + #endif + + case IOCTL_LEPTON_GET_WIDTH: { + int width; + error = sensor_ioctl(request, &width); + if (error == 0) { + ret_obj = mp_obj_new_int(width); + } + break; + } + + case IOCTL_LEPTON_GET_HEIGHT: { + int height; + error = sensor_ioctl(request, &height); + if (error == 0) { + ret_obj = mp_obj_new_int(height); + } + break; + } + + case IOCTL_LEPTON_GET_RADIOMETRY: { + int radiometry; + error = sensor_ioctl(request, &radiometry); + if (error == 0) { + ret_obj = mp_obj_new_int(radiometry); + } + break; + } + + case IOCTL_LEPTON_GET_REFRESH: { + int refresh; + error = sensor_ioctl(request, &refresh); + if (error == 0) { + ret_obj = mp_obj_new_int(refresh); + } + break; + } + + case IOCTL_LEPTON_GET_RESOLUTION: { + int resolution; + error = sensor_ioctl(request, &resolution); + if (error == 0) { + ret_obj = mp_obj_new_int(resolution); + } + break; + } + + case IOCTL_LEPTON_RUN_COMMAND: { + if (n_args >= 2) { + error = sensor_ioctl(request, mp_obj_get_int(args[1])); + } + break; + } + + case IOCTL_LEPTON_SET_ATTRIBUTE: { + if (n_args >= 3) { + size_t data_len; + int command = mp_obj_get_int(args[1]); + uint16_t *data = (uint16_t *) mp_obj_str_get_data(args[2], &data_len); + PY_ASSERT_TRUE_MSG(data_len > 0, "0 bytes transferred!"); + error = sensor_ioctl(request, command, data, data_len / sizeof(uint16_t)); + } + break; + } + + case IOCTL_LEPTON_GET_ATTRIBUTE: { + if (n_args >= 3) { + int command = mp_obj_get_int(args[1]); + size_t data_len = mp_obj_get_int(args[2]); + PY_ASSERT_TRUE_MSG(data_len > 0, "0 bytes transferred!"); + uint16_t *data = xalloc(data_len * sizeof(uint16_t)); + error = sensor_ioctl(request, command, data, data_len); + if (error == 0) { + ret_obj = mp_obj_new_bytearray_by_ref(data_len * sizeof(uint16_t), data); + } + } + break; + } + + case IOCTL_LEPTON_GET_FPA_TEMPERATURE: + case IOCTL_LEPTON_GET_AUX_TEMPERATURE: { + int temp; + error = sensor_ioctl(request, &temp); + if (error == 0) { + ret_obj = mp_obj_new_float((((float) temp) / 100) - 273.15f); + } + break; + } + + case IOCTL_LEPTON_SET_MEASUREMENT_MODE: + if (n_args >= 2) { + error = sensor_ioctl(request, mp_obj_get_int(args[1])); + } + break; + + case IOCTL_LEPTON_GET_MEASUREMENT_MODE: { + int enabled; + error = sensor_ioctl(request, &enabled); + if (error == 0) { + ret_obj = mp_obj_new_bool(enabled); + } + break; + } + + case IOCTL_LEPTON_SET_MEASUREMENT_RANGE: + if (n_args >= 3) { + // GCC will not let us pass floats to ... so we have to pass float pointers instead. + float min = mp_obj_get_float(args[1]); + float max = mp_obj_get_float(args[2]); + error = sensor_ioctl(request, &min, &max); + } + break; + + case IOCTL_LEPTON_GET_MEASUREMENT_RANGE: { + float min, max; + error = sensor_ioctl(request, &min, &max); + if (error == 0) { + ret_obj = mp_obj_new_tuple(2, (mp_obj_t []) {mp_obj_new_float(min), mp_obj_new_float(max)}); + } + break; + } + + #if (OMV_ENABLE_HM01B0 == 1) + case IOCTL_HIMAX_MD_ENABLE: { + if (n_args >= 2) { + error = sensor_ioctl(request, mp_obj_get_int(args[1])); + } + break; + } + + case IOCTL_HIMAX_MD_WINDOW: { + if (n_args >= 2) { + int x, y, w, h; + mp_obj_t *array; + mp_uint_t array_len; + mp_obj_get_array(args[1], &array_len, &array); + + if (array_len == 4) { + x = mp_obj_get_int(array[0]); + y = mp_obj_get_int(array[1]); + w = mp_obj_get_int(array[2]); + h = mp_obj_get_int(array[3]); + } else if (array_len == 2) { + w = mp_obj_get_int(array[0]); + h = mp_obj_get_int(array[1]); + x = 0; + y = 0; + } else { + mp_raise_msg(&mp_type_ValueError, + MP_ERROR_TEXT("The tuple/list must either be (x, y, w, h) or (w, h)")); + } + + error = sensor_ioctl(request, x, y, w, h); + } + break; + } + + case IOCTL_HIMAX_MD_THRESHOLD: { + if (n_args >= 2) { + error = sensor_ioctl(request, mp_obj_get_int(args[1])); + } + break; + } + + case IOCTL_HIMAX_MD_CLEAR: { + error = sensor_ioctl(request); + break; + } + + case IOCTL_HIMAX_OSC_ENABLE: { + if (n_args >= 2) { + error = sensor_ioctl(request, mp_obj_get_int(args[1])); + } + break; + } + + #endif // (OMV_ENABLE_HM01B0 == 1) + + default: { + sensor_raise_error(SENSOR_ERROR_CTL_UNSUPPORTED); + break; + } + } + + if (error != 0) { + sensor_raise_error(error); + } + + return ret_obj; +} + +static mp_obj_t py_sensor_set_color_palette(mp_obj_t palette_obj) +{ + int palette = mp_obj_get_int(palette_obj); + switch (palette) { + case COLOR_PALETTE_RAINBOW: + sensor_set_color_palette(rainbow_table); + break; + case COLOR_PALETTE_IRONBOW: + sensor_set_color_palette(ironbow_table); + break; + default: + sensor_raise_error(SENSOR_ERROR_INVALID_ARGUMENT); + break; + } + return mp_const_none; +} + +static mp_obj_t py_sensor_get_color_palette() +{ + const uint16_t *palette = sensor_get_color_palette(); + if (palette == rainbow_table) { + return mp_obj_new_int(COLOR_PALETTE_RAINBOW); + } else if (palette == ironbow_table) { + return mp_obj_new_int(COLOR_PALETTE_IRONBOW); + } + return mp_const_none; +} + +static mp_obj_t py_sensor_write_reg(mp_obj_t addr, mp_obj_t val) +{ + sensor_write_reg(mp_obj_get_int(addr), mp_obj_get_int(val)); + return mp_const_none; +} + +static mp_obj_t py_sensor_read_reg(mp_obj_t addr) +{ + return mp_obj_new_int(sensor_read_reg(mp_obj_get_int(addr))); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor__init__obj, py_sensor__init__); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_reset_obj, py_sensor_reset); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_sleep_obj, py_sensor_sleep); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_shutdown_obj, py_sensor_shutdown); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_flush_obj, py_sensor_flush); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_sensor_snapshot_obj, 0, py_sensor_snapshot); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_sensor_skip_frames_obj, 0, py_sensor_skip_frames); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_width_obj, py_sensor_width); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_height_obj, py_sensor_height); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_fb_obj, py_sensor_get_fb); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_id_obj, py_sensor_get_id); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_frame_available_obj, py_sensor_get_frame_available); +STATIC MP_DEFINE_CONST_FUN_OBJ_3(py_sensor_alloc_extra_fb_obj, py_sensor_alloc_extra_fb); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_dealloc_extra_fb_obj, py_sensor_dealloc_extra_fb); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_pixformat_obj, py_sensor_set_pixformat); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_pixformat_obj, py_sensor_get_pixformat); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_framesize_obj, py_sensor_set_framesize); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_framesize_obj, py_sensor_get_framesize); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_framerate_obj, py_sensor_set_framerate); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_framerate_obj, py_sensor_get_framerate); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_sensor_set_windowing_obj, 1, 4, py_sensor_set_windowing); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_windowing_obj, py_sensor_get_windowing); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_gainceiling_obj, py_sensor_set_gainceiling); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_contrast_obj, py_sensor_set_contrast); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_brightness_obj, py_sensor_set_brightness); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_saturation_obj, py_sensor_set_saturation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_quality_obj, py_sensor_set_quality); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_colorbar_obj, py_sensor_set_colorbar); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_sensor_set_auto_gain_obj, 1,py_sensor_set_auto_gain); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_gain_db_obj, py_sensor_get_gain_db); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_sensor_set_auto_exposure_obj,1,py_sensor_set_auto_exposure); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_exposure_us_obj, py_sensor_get_exposure_us); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_sensor_set_auto_whitebal_obj,1,py_sensor_set_auto_whitebal); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_rgb_gain_db_obj, py_sensor_get_rgb_gain_db); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_hmirror_obj, py_sensor_set_hmirror); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_hmirror_obj, py_sensor_get_hmirror); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_vflip_obj, py_sensor_set_vflip); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_vflip_obj, py_sensor_get_vflip); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_transpose_obj, py_sensor_set_transpose); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_transpose_obj, py_sensor_get_transpose); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_auto_rotation_obj, py_sensor_set_auto_rotation); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_auto_rotation_obj, py_sensor_get_auto_rotation); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_framebuffers_obj, py_sensor_set_framebuffers); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_framebuffers_obj, py_sensor_get_framebuffers); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_sensor_disable_full_flush_obj, 0, 1, py_sensor_disable_full_flush); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_special_effect_obj, py_sensor_set_special_effect); +STATIC MP_DEFINE_CONST_FUN_OBJ_3(py_sensor_set_lens_correction_obj, py_sensor_set_lens_correction); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_vsync_callback_obj, py_sensor_set_vsync_callback); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_frame_callback_obj, py_sensor_set_frame_callback); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(py_sensor_ioctl_obj, 1, 5, py_sensor_ioctl); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_set_color_palette_obj, py_sensor_set_color_palette); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_sensor_get_color_palette_obj, py_sensor_get_color_palette); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_sensor_write_reg_obj, py_sensor_write_reg); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_sensor_read_reg_obj, py_sensor_read_reg); + +STATIC const mp_map_elem_t globals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_sensor)}, + + // Pixel Formats + { MP_OBJ_NEW_QSTR(MP_QSTR_BINARY), MP_OBJ_NEW_SMALL_INT(PIXFORMAT_BINARY)}, /* 1BPP/BINARY*/ + { MP_OBJ_NEW_QSTR(MP_QSTR_GRAYSCALE), MP_OBJ_NEW_SMALL_INT(PIXFORMAT_GRAYSCALE)},/* 1BPP/GRAYSCALE*/ + { MP_OBJ_NEW_QSTR(MP_QSTR_RGB565), MP_OBJ_NEW_SMALL_INT(PIXFORMAT_RGB565)}, /* 2BPP/RGB565*/ + { MP_OBJ_NEW_QSTR(MP_QSTR_BAYER), MP_OBJ_NEW_SMALL_INT(PIXFORMAT_BAYER)}, /* 1BPP/RAW*/ + { MP_OBJ_NEW_QSTR(MP_QSTR_YUV422), MP_OBJ_NEW_SMALL_INT(PIXFORMAT_YUV422)}, /* 2BPP/YUV422*/ + { MP_OBJ_NEW_QSTR(MP_QSTR_JPEG), MP_OBJ_NEW_SMALL_INT(PIXFORMAT_JPEG)}, /* JPEG/COMPRESSED*/ + + // Image Sensors + { MP_OBJ_NEW_QSTR(MP_QSTR_OV2640), MP_OBJ_NEW_SMALL_INT(OV2640_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_OV5640), MP_OBJ_NEW_SMALL_INT(OV5640_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_OV7670), MP_OBJ_NEW_SMALL_INT(OV7670_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_OV7690), MP_OBJ_NEW_SMALL_INT(OV7690_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_OV7725), MP_OBJ_NEW_SMALL_INT(OV7725_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_OV9650), MP_OBJ_NEW_SMALL_INT(OV9650_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_MT9V022), MP_OBJ_NEW_SMALL_INT(MT9V0X2_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_MT9V024), MP_OBJ_NEW_SMALL_INT(MT9V0X4_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_MT9V032), MP_OBJ_NEW_SMALL_INT(MT9V0X2_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_MT9V034), MP_OBJ_NEW_SMALL_INT(MT9V0X4_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_MT9M114), MP_OBJ_NEW_SMALL_INT(MT9M114_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_LEPTON), MP_OBJ_NEW_SMALL_INT(LEPTON_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_HM01B0), MP_OBJ_NEW_SMALL_INT(HM01B0_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_GC2145), MP_OBJ_NEW_SMALL_INT(GC2145_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_PAJ6100), MP_OBJ_NEW_SMALL_INT(PAJ6100_ID)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_FROGEYE2020), MP_OBJ_NEW_SMALL_INT(FROGEYE2020_ID)}, + + // Special effects + { MP_OBJ_NEW_QSTR(MP_QSTR_NORMAL), MP_OBJ_NEW_SMALL_INT(SDE_NORMAL)}, /* Normal/No SDE */ + { MP_OBJ_NEW_QSTR(MP_QSTR_NEGATIVE), MP_OBJ_NEW_SMALL_INT(SDE_NEGATIVE)}, /* Negative image */ + + // C/SIF Resolutions + { MP_OBJ_NEW_QSTR(MP_QSTR_QQCIF), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_QQCIF)}, /* 88x72 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_QCIF), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_QCIF)}, /* 176x144 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_CIF), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_CIF)}, /* 352x288 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_QQSIF), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_QQSIF)}, /* 88x60 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_QSIF), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_QSIF)}, /* 176x120 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_SIF), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_SIF)}, /* 352x240 */ + // VGA Resolutions + { MP_OBJ_NEW_QSTR(MP_QSTR_QQQQVGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_QQQQVGA)}, /* 40x30 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_QQQVGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_QQQVGA)}, /* 80x60 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_QQVGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_QQVGA)}, /* 160x120 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_QVGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_QVGA)}, /* 320x240 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_VGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_VGA)}, /* 640x480 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_HQQQQVGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_HQQQQVGA)}, /* 40x20 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_HQQQVGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_HQQQVGA)}, /* 80x40 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_HQQVGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_HQQVGA)}, /* 160x80 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_HQVGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_HQVGA)}, /* 240x160 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_HVGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_HVGA)}, /* 480x320 */ + // FFT Resolutions + { MP_OBJ_NEW_QSTR(MP_QSTR_B64X32), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_64X32)}, /* 64x32 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_B64X64), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_64X64)}, /* 64x64 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_B128X64), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_128X64)}, /* 128x64 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_B128X128), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_128X128)}, /* 128x128 */ + // Himax Resolutions + { MP_OBJ_NEW_QSTR(MP_QSTR_B160X160), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_160X160)}, /* 160x160 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_B320X320), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_320X320)}, /* 320x320 */ + // Other + { MP_OBJ_NEW_QSTR(MP_QSTR_LCD), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_LCD)}, /* 128x160 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_QQVGA2), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_QQVGA2)}, /* 128x160 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_WVGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_WVGA)}, /* 720x480 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_WVGA2), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_WVGA2)}, /* 752x480 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_SVGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_SVGA)}, /* 800x600 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_XGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_XGA)}, /* 1024x768 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_WXGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_WXGA)}, /* 1280x768 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_SXGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_SXGA)}, /* 1280x1024 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_SXGAM), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_SXGAM)}, /* 1280x960 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_UXGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_UXGA)}, /* 1600x1200 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_HD), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_HD)}, /* 1280x720 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_FHD), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_FHD)}, /* 1920x1080 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_QHD), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_QHD)}, /* 2560x1440 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_QXGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_QXGA)}, /* 2048x1536 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_WQXGA), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_WQXGA)}, /* 2560x1600 */ + { MP_OBJ_NEW_QSTR(MP_QSTR_WQXGA2), MP_OBJ_NEW_SMALL_INT(FRAMESIZE_WQXGA2)}, /* 2592x1944 */ + + // Color Palettes + { MP_OBJ_NEW_QSTR(MP_QSTR_PALETTE_RAINBOW), MP_OBJ_NEW_SMALL_INT(COLOR_PALETTE_RAINBOW)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_PALETTE_IRONBOW), MP_OBJ_NEW_SMALL_INT(COLOR_PALETTE_IRONBOW)}, + + // IOCTLs + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_SET_READOUT_WINDOW), MP_OBJ_NEW_SMALL_INT(IOCTL_SET_READOUT_WINDOW)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_GET_READOUT_WINDOW), MP_OBJ_NEW_SMALL_INT(IOCTL_GET_READOUT_WINDOW)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_SET_TRIGGERED_MODE), MP_OBJ_NEW_SMALL_INT(IOCTL_SET_TRIGGERED_MODE)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_GET_TRIGGERED_MODE), MP_OBJ_NEW_SMALL_INT(IOCTL_GET_TRIGGERED_MODE)}, + #if (OMV_ENABLE_OV5640_AF == 1) + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_TRIGGER_AUTO_FOCUS), MP_OBJ_NEW_SMALL_INT(IOCTL_TRIGGER_AUTO_FOCUS)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_PAUSE_AUTO_FOCUS), MP_OBJ_NEW_SMALL_INT(IOCTL_PAUSE_AUTO_FOCUS)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_RESET_AUTO_FOCUS), MP_OBJ_NEW_SMALL_INT(IOCTL_RESET_AUTO_FOCUS)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_WAIT_ON_AUTO_FOCUS), MP_OBJ_NEW_SMALL_INT(IOCTL_WAIT_ON_AUTO_FOCUS)}, + #endif + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_GET_WIDTH), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_GET_WIDTH)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_GET_HEIGHT), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_GET_HEIGHT)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_GET_RADIOMETRY), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_GET_RADIOMETRY)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_GET_REFRESH), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_GET_REFRESH)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_GET_RESOLUTION), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_GET_RESOLUTION)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_RUN_COMMAND), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_RUN_COMMAND)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_SET_ATTRIBUTE), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_SET_ATTRIBUTE)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_GET_ATTRIBUTE), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_GET_ATTRIBUTE)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_GET_FPA_TEMPERATURE), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_GET_FPA_TEMPERATURE)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_GET_AUX_TEMPERATURE), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_GET_AUX_TEMPERATURE)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_SET_MEASUREMENT_MODE), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_SET_MEASUREMENT_MODE)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_GET_MEASUREMENT_MODE), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_GET_MEASUREMENT_MODE)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_SET_MEASUREMENT_RANGE), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_SET_MEASUREMENT_RANGE)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_LEPTON_GET_MEASUREMENT_RANGE), MP_OBJ_NEW_SMALL_INT(IOCTL_LEPTON_GET_MEASUREMENT_RANGE)}, + #if (OMV_ENABLE_HM01B0 == 1) + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_HIMAX_MD_ENABLE), MP_OBJ_NEW_SMALL_INT(IOCTL_HIMAX_MD_ENABLE)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_HIMAX_MD_WINDOW), MP_OBJ_NEW_SMALL_INT(IOCTL_HIMAX_MD_WINDOW)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_HIMAX_MD_THRESHOLD), MP_OBJ_NEW_SMALL_INT(IOCTL_HIMAX_MD_THRESHOLD)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_HIMAX_MD_CLEAR), MP_OBJ_NEW_SMALL_INT(IOCTL_HIMAX_MD_CLEAR)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_IOCTL_HIMAX_OSC_ENABLE), MP_OBJ_NEW_SMALL_INT(IOCTL_HIMAX_OSC_ENABLE)}, + #endif + + // Framebuffer Sizes + { MP_OBJ_NEW_QSTR(MP_QSTR_SINGLE_BUFFER), MP_OBJ_NEW_SMALL_INT(1)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_DOUBLE_BUFFER), MP_OBJ_NEW_SMALL_INT(2)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_TRIPLE_BUFFER), MP_OBJ_NEW_SMALL_INT(3)}, + { MP_OBJ_NEW_QSTR(MP_QSTR_VIDEO_FIFO), MP_OBJ_NEW_SMALL_INT(4)}, + + // Sensor functions + { MP_OBJ_NEW_QSTR(MP_QSTR___init__), (mp_obj_t)&py_sensor__init__obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_reset), (mp_obj_t)&py_sensor_reset_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_sleep), (mp_obj_t)&py_sensor_sleep_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_shutdown), (mp_obj_t)&py_sensor_shutdown_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_flush), (mp_obj_t)&py_sensor_flush_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_snapshot), (mp_obj_t)&py_sensor_snapshot_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_skip_frames), (mp_obj_t)&py_sensor_skip_frames_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_width), (mp_obj_t)&py_sensor_width_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_height), (mp_obj_t)&py_sensor_height_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_fb), (mp_obj_t)&py_sensor_get_fb_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_id), (mp_obj_t)&py_sensor_get_id_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_frame_available), (mp_obj_t)&py_sensor_get_frame_available_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_alloc_extra_fb), (mp_obj_t)&py_sensor_alloc_extra_fb_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_dealloc_extra_fb), (mp_obj_t)&py_sensor_dealloc_extra_fb_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_pixformat), (mp_obj_t)&py_sensor_set_pixformat_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_pixformat), (mp_obj_t)&py_sensor_get_pixformat_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_framesize), (mp_obj_t)&py_sensor_set_framesize_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_framesize), (mp_obj_t)&py_sensor_get_framesize_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_framerate), (mp_obj_t)&py_sensor_set_framerate_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_framerate), (mp_obj_t)&py_sensor_get_framerate_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_windowing), (mp_obj_t)&py_sensor_set_windowing_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_windowing), (mp_obj_t)&py_sensor_get_windowing_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_gainceiling), (mp_obj_t)&py_sensor_set_gainceiling_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_contrast), (mp_obj_t)&py_sensor_set_contrast_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_brightness), (mp_obj_t)&py_sensor_set_brightness_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_saturation), (mp_obj_t)&py_sensor_set_saturation_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_quality), (mp_obj_t)&py_sensor_set_quality_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_colorbar), (mp_obj_t)&py_sensor_set_colorbar_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_auto_gain), (mp_obj_t)&py_sensor_set_auto_gain_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_gain_db), (mp_obj_t)&py_sensor_get_gain_db_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_auto_exposure), (mp_obj_t)&py_sensor_set_auto_exposure_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_exposure_us), (mp_obj_t)&py_sensor_get_exposure_us_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_auto_whitebal), (mp_obj_t)&py_sensor_set_auto_whitebal_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_rgb_gain_db), (mp_obj_t)&py_sensor_get_rgb_gain_db_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_hmirror), (mp_obj_t)&py_sensor_set_hmirror_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_hmirror), (mp_obj_t)&py_sensor_get_hmirror_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_vflip), (mp_obj_t)&py_sensor_set_vflip_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_vflip), (mp_obj_t)&py_sensor_get_vflip_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_transpose), (mp_obj_t)&py_sensor_set_transpose_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_transpose), (mp_obj_t)&py_sensor_get_transpose_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_auto_rotation), (mp_obj_t)&py_sensor_set_auto_rotation_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_auto_rotation), (mp_obj_t)&py_sensor_get_auto_rotation_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_framebuffers), (mp_obj_t)&py_sensor_set_framebuffers_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_framebuffers), (mp_obj_t)&py_sensor_get_framebuffers_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_disable_full_flush), (mp_obj_t)&py_sensor_disable_full_flush_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_special_effect), (mp_obj_t)&py_sensor_set_special_effect_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_lens_correction), (mp_obj_t)&py_sensor_set_lens_correction_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_vsync_callback), (mp_obj_t)&py_sensor_set_vsync_callback_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_frame_callback), (mp_obj_t)&py_sensor_set_frame_callback_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_ioctl), (mp_obj_t)&py_sensor_ioctl_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_set_color_palette), (mp_obj_t)&py_sensor_set_color_palette_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_color_palette), (mp_obj_t)&py_sensor_get_color_palette_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR___write_reg), (mp_obj_t)&py_sensor_write_reg_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR___read_reg), (mp_obj_t)&py_sensor_read_reg_obj }, +}; + +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t sensor_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t)&globals_dict, +}; + +MP_REGISTER_MODULE(MP_QSTR_sensor, sensor_module, MICROPY_PY_SENSOR); +#endif // MICROPY_PY_SENSOR diff --git a/github_source/minicv2/reference/py_tf.c b/github_source/minicv2/reference/py_tf.c new file mode 100644 index 0000000..9db34e9 --- /dev/null +++ b/github_source/minicv2/reference/py_tf.c @@ -0,0 +1,799 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Python Tensorflow library wrapper. + */ +#include "py/runtime.h" +#include "py/obj.h" +#include "py/objlist.h" +#include "py/objtuple.h" + +#include "py_helper.h" +#include "imlib_config.h" + +#ifdef IMLIB_ENABLE_TF +#include "py_image.h" +#include "ff_wrapper.h" +#include "py_tf.h" + +#define GRAYSCALE_RANGE ((COLOR_GRAYSCALE_MAX) - (COLOR_GRAYSCALE_MIN)) +#define GRAYSCALE_MID (((GRAYSCALE_RANGE) + 1) / 2) + +void py_tf_alloc_putchar_buffer() +{ + py_tf_putchar_buffer = (char *) fb_alloc0(PY_TF_PUTCHAR_BUFFER_LEN + 1, FB_ALLOC_NO_HINT); + py_tf_putchar_buffer_index = 0; + py_tf_putchar_buffer_len = PY_TF_PUTCHAR_BUFFER_LEN; +} + +STATIC const char *py_tf_map_datatype(libtf_datatype_t datatype) +{ + if (datatype == LIBTF_DATATYPE_UINT8) { + return "uint8"; + } else if (datatype == LIBTF_DATATYPE_INT8) { + return "int8"; + } else { + return "float"; + } +} + +STATIC void py_tf_model_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_tf_model_obj_t *self = self_in; + mp_printf(print, + "{\"len\":%d, \"ram\":%d, " + "\"input_height\":%d, \"input_width\":%d, \"input_channels\":%d, \"input_datatype\":\"%s\", " + "\"input_scale\":%f, \"input_zero_point\":%d, " + "\"output_height\":%d, \"output_width\":%d, \"output_channels\":%d, \"output_datatype\":\"%s\", " + "\"output_scale\":%f, \"output_zero_point\":%d}", + self->model_data_len, self->params.tensor_arena_size, + self->params.input_height, self->params.input_width, self->params.input_channels, + py_tf_map_datatype(self->params.input_datatype), + (double) self->params.input_scale, self->params.input_zero_point, + self->params.output_height, self->params.output_width, self->params.output_channels, + py_tf_map_datatype(self->params.output_datatype), + (double) self->params.output_scale, self->params.output_zero_point); +} + +// TF Classification Object +#define py_tf_classification_obj_size 5 +typedef struct py_tf_classification_obj { + mp_obj_base_t base; + mp_obj_t x, y, w, h, output; +} py_tf_classification_obj_t; + +STATIC void py_tf_classification_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_tf_classification_obj_t *self = self_in; + mp_printf(print, + "{\"x\":%d, \"y\":%d, \"w\":%d, \"h\":%d, \"output\":", + mp_obj_get_int(self->x), + mp_obj_get_int(self->y), + mp_obj_get_int(self->w), + mp_obj_get_int(self->h)); + mp_obj_print_helper(print, self->output, kind); + mp_printf(print, "}"); +} + +STATIC mp_obj_t py_tf_classification_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value) +{ + if (value == MP_OBJ_SENTINEL) { // load + py_tf_classification_obj_t *self = self_in; + if (MP_OBJ_IS_TYPE(index, &mp_type_slice)) { + mp_bound_slice_t slice; + if (!mp_seq_get_fast_slice_indexes(py_tf_classification_obj_size, index, &slice)) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("only slices with step=1 (aka None) are supported")); + } + mp_obj_tuple_t *result = mp_obj_new_tuple(slice.stop - slice.start, NULL); + mp_seq_copy(result->items, &(self->x) + slice.start, result->len, mp_obj_t); + return result; + } + switch (mp_get_index(self->base.type, py_tf_classification_obj_size, index, false)) { + case 0: return self->x; + case 1: return self->y; + case 2: return self->w; + case 3: return self->h; + case 4: return self->output; + } + } + return MP_OBJ_NULL; // op not supported +} + +mp_obj_t py_tf_classification_rect(mp_obj_t self_in) +{ + return mp_obj_new_tuple(4, (mp_obj_t []) {((py_tf_classification_obj_t *) self_in)->x, + ((py_tf_classification_obj_t *) self_in)->y, + ((py_tf_classification_obj_t *) self_in)->w, + ((py_tf_classification_obj_t *) self_in)->h}); +} + +mp_obj_t py_tf_classification_x(mp_obj_t self_in) { return ((py_tf_classification_obj_t *) self_in)->x; } +mp_obj_t py_tf_classification_y(mp_obj_t self_in) { return ((py_tf_classification_obj_t *) self_in)->y; } +mp_obj_t py_tf_classification_w(mp_obj_t self_in) { return ((py_tf_classification_obj_t *) self_in)->w; } +mp_obj_t py_tf_classification_h(mp_obj_t self_in) { return ((py_tf_classification_obj_t *) self_in)->h; } +mp_obj_t py_tf_classification_output(mp_obj_t self_in) { return ((py_tf_classification_obj_t *) self_in)->output; } + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_classification_rect_obj, py_tf_classification_rect); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_classification_x_obj, py_tf_classification_x); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_classification_y_obj, py_tf_classification_y); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_classification_w_obj, py_tf_classification_w); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_classification_h_obj, py_tf_classification_h); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_classification_output_obj, py_tf_classification_output); + +STATIC const mp_rom_map_elem_t py_tf_classification_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_rect), MP_ROM_PTR(&py_tf_classification_rect_obj) }, + { MP_ROM_QSTR(MP_QSTR_x), MP_ROM_PTR(&py_tf_classification_x_obj) }, + { MP_ROM_QSTR(MP_QSTR_y), MP_ROM_PTR(&py_tf_classification_y_obj) }, + { MP_ROM_QSTR(MP_QSTR_w), MP_ROM_PTR(&py_tf_classification_w_obj) }, + { MP_ROM_QSTR(MP_QSTR_h), MP_ROM_PTR(&py_tf_classification_h_obj) }, + { MP_ROM_QSTR(MP_QSTR_output), MP_ROM_PTR(&py_tf_classification_output_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(py_tf_classification_locals_dict, py_tf_classification_locals_dict_table); + +static const mp_obj_type_t py_tf_classification_type = { + { &mp_type_type }, + .name = MP_QSTR_tf_classification, + .print = py_tf_classification_print, + .subscr = py_tf_classification_subscr, + .locals_dict = (mp_obj_t) &py_tf_classification_locals_dict +}; + +static const mp_obj_type_t py_tf_model_type; + +STATIC mp_obj_t int_py_tf_load(mp_obj_t path_obj, bool alloc_mode, bool helper_mode) +{ + if (!helper_mode) { + fb_alloc_mark(); + } + + const char *path = mp_obj_str_get_str(path_obj); + py_tf_model_obj_t *tf_model = m_new_obj(py_tf_model_obj_t); + tf_model->base.type = &py_tf_model_type; + + if (!strcmp(path, "person_detection")) { + tf_model->model_data = (unsigned char *) g_person_detect_model_data; + tf_model->model_data_len = g_person_detect_model_data_len; + } else { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + FIL fp; + file_read_open(&fp, path); + tf_model->model_data_len = f_size(&fp); + tf_model->model_data = alloc_mode + ? fb_alloc(tf_model->model_data_len, FB_ALLOC_PREFER_SIZE) + : xalloc(tf_model->model_data_len); + read_data(&fp, tf_model->model_data, tf_model->model_data_len); + file_close(&fp); + #else + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image I/O is not supported")); + #endif + } + + if (!helper_mode) { + py_tf_alloc_putchar_buffer(); + } + + uint32_t tensor_arena_size; + uint8_t *tensor_arena = fb_alloc_all(&tensor_arena_size, FB_ALLOC_PREFER_SIZE); + + if (libtf_get_parameters(tf_model->model_data, tensor_arena, tensor_arena_size, &tf_model->params) != 0) { + // Note can't use MP_ERROR_TEXT here... + mp_raise_msg(&mp_type_OSError, (mp_rom_error_text_t) py_tf_putchar_buffer); + } + + fb_free(); // free fb_alloc_all() + + if (!helper_mode) { + fb_free(); // free py_tf_alloc_putchar_buffer() + } + + // In this mode we leave the model allocated on the frame buffer. + // py_tf_free_from_fb() must be called to free the model allocated on the frame buffer. + // On error everything is cleaned because of fb_alloc_mark(). + + if ((!helper_mode) && (!alloc_mode)) { + fb_alloc_free_till_mark(); + } else if ((!helper_mode) && alloc_mode) { + fb_alloc_mark_permanent(); // tf_model->model_data will not be popped on exception. + } + + return tf_model; +} + +STATIC mp_obj_t py_tf_load(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + return int_py_tf_load(args[0], + py_helper_keyword_int(n_args, args, 1, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_load_to_fb), false), + false); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tf_load_obj, 1, py_tf_load); + +STATIC mp_obj_t py_tf_free_from_fb() +{ + fb_alloc_free_till_mark_past_mark_permanent(); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(py_tf_free_from_fb_obj, py_tf_free_from_fb); + +STATIC py_tf_model_obj_t *py_tf_load_alloc(mp_obj_t path_obj) +{ + if (MP_OBJ_IS_TYPE(path_obj, &py_tf_model_type)) { + return (py_tf_model_obj_t *) path_obj; + } else { + return (py_tf_model_obj_t *) int_py_tf_load(path_obj, true, true); + } +} + +typedef struct py_tf_input_data_callback_data { + image_t *img; + rectangle_t *roi; +} py_tf_input_data_callback_data_t; + +STATIC void py_tf_input_data_callback(void *callback_data, + void *model_input, + libtf_parameters_t *params) +{ + py_tf_input_data_callback_data_t *arg = (py_tf_input_data_callback_data_t *) callback_data; + + // Disable checking input scaling and zero-point. Nets can be all over the place on the input + // scaling and zero-point but still work with the code below. + + // if (params->input_datatype == LIBTF_DATATYPE_UINT8) { + // if (fast_roundf(params->input_scale * GRAYSCALE_RANGE) != 1) { + // mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected model input scale to be 1/255!")); + // } + + // if (params->input_zero_point != 0) { + // mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected model input zero point to be 0!")); + // } + // } + + // if (params->input_datatype == LIBTF_DATATYPE_INT8) { + // if (fast_roundf(params->input_scale * GRAYSCALE_RANGE) != 1) { + // mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected model input scale to be 1/255!")); + // } + + // if (params->input_zero_point != -GRAYSCALE_MID) { + // mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected model input zero point to be -128!")); + // } + // } + + int shift = (params->input_datatype == LIBTF_DATATYPE_INT8) ? GRAYSCALE_MID : 0; + float fscale = 1.0f / GRAYSCALE_RANGE; + + float xscale = params->input_width / ((float) arg->roi->w); + float yscale = params->input_height / ((float) arg->roi->h); + // MAX == KeepAspectRationByExpanding - MIN == KeepAspectRatio + float scale = IM_MAX(xscale, yscale); + + image_t dst_img; + dst_img.w = params->input_width; + dst_img.h = params->input_height; + dst_img.data = (uint8_t *) model_input; + + if (params->input_channels == 1) { + dst_img.pixfmt = PIXFORMAT_GRAYSCALE; + } else if (params->input_channels == 3) { + dst_img.pixfmt = PIXFORMAT_RGB565; + } else { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected model input channels to be 1 or 3!")); + } + + imlib_draw_image(&dst_img, arg->img, 0, 0, scale, scale, arg->roi, + -1, 256, NULL, NULL, IMAGE_HINT_BILINEAR | IMAGE_HINT_BLACK_BACKGROUND, + NULL, NULL); + + int size = (params->input_width * params->input_height) - 1; // must be int per countdown loop + + if (params->input_channels == 1) { // GRAYSCALE + if (params->input_datatype == LIBTF_DATATYPE_FLOAT) { // convert u8 -> f32 + uint8_t *model_input_u8 = (uint8_t *) model_input; + float *model_input_f32 = (float *) model_input; + + for (; size >= 0; size -= 1) { + model_input_f32[size] = model_input_u8[size] * fscale; + } + } else { + if (shift) { // convert u8 -> s8 + uint8_t *model_input_8 = (uint8_t *) model_input; + + #if (__ARM_ARCH > 6) + for (; size >= 3; size -= 4) { + *((uint32_t *) (model_input_8 + size - 3)) ^= 0x80808080; + } + #endif + + for (; size >= 0; size -= 1) { + model_input_8[size] ^= GRAYSCALE_MID; + } + } + } + } else if (params->input_channels == 3) { // RGB888 + int rgb_size = size * 3; // must be int per countdown loop + + if (params->input_datatype == LIBTF_DATATYPE_FLOAT) { + uint16_t *model_input_u16 = (uint16_t *) model_input; + float *model_input_f32 = (float *) model_input; + + for (; size >= 0; size -= 1, rgb_size -= 3) { + int pixel = model_input_u16[size]; + model_input_f32[rgb_size] = COLOR_RGB565_TO_R8(pixel) * fscale; + model_input_f32[rgb_size + 1] = COLOR_RGB565_TO_G8(pixel) * fscale; + model_input_f32[rgb_size + 2] = COLOR_RGB565_TO_B8(pixel) * fscale; + } + } else { + uint16_t *model_input_u16 = (uint16_t *) model_input; + uint8_t *model_input_8 = (uint8_t *) model_input; + + for (; size >= 0; size -= 1, rgb_size -= 3) { + int pixel = model_input_u16[size]; + model_input_8[rgb_size] = COLOR_RGB565_TO_R8(pixel) ^ shift; + model_input_8[rgb_size + 1] = COLOR_RGB565_TO_G8(pixel) ^ shift; + model_input_8[rgb_size + 2] = COLOR_RGB565_TO_B8(pixel) ^ shift; + } + } + } +} + +typedef struct py_tf_classify_output_data_callback_data { + mp_obj_t out; +} py_tf_classify_output_data_callback_data_t; + +STATIC void py_tf_classify_output_data_callback(void *callback_data, + void *model_output, + libtf_parameters_t *params) +{ + py_tf_classify_output_data_callback_data_t *arg = (py_tf_classify_output_data_callback_data_t *) callback_data; + + if (params->output_height != 1) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected model output height to be 1!")); + } + + if (params->output_width != 1) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected model output width to be 1!")); + } + + arg->out = mp_obj_new_list(params->output_channels, NULL); + + if (params->output_datatype == LIBTF_DATATYPE_FLOAT) { + for (int i = 0, ii = params->output_channels; i < ii; i++) { + ((mp_obj_list_t *) arg->out)->items[i] = + mp_obj_new_float(((float *) model_output)[i]); + } + } else { + for (int i = 0, ii = params->output_channels; i < ii; i++) { + ((mp_obj_list_t *) arg->out)->items[i] = + mp_obj_new_float((((uint8_t *) model_output)[i] - params->output_zero_point) * params->output_scale); + } + } +} + +STATIC mp_obj_t py_tf_classify(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + fb_alloc_mark(); + py_tf_alloc_putchar_buffer(); + + py_tf_model_obj_t *arg_model = py_tf_load_alloc(args[0]); + image_t *arg_img = py_image_cobj(args[1]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 2, kw_args, &roi); + + float arg_min_scale = py_helper_keyword_float(n_args, args, 3, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_min_scale), 1.0f); + + if ((arg_min_scale <= 0.0f) || (1.0f < arg_min_scale)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 < min_scale <= 1")); + } + + float arg_scale_mul = py_helper_keyword_float(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_scale_mul), 0.5f); + + if ((arg_scale_mul < 0.0f) || (1.0f <= arg_scale_mul)) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= scale_mul < 1")); + } + + float arg_x_overlap = py_helper_keyword_float(n_args, args, 5, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_x_overlap), 0.0f); + + if ((arg_x_overlap != -1.f) && ((arg_x_overlap < 0.0f) || (1.0f <= arg_x_overlap))) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= x_overlap < 1")); + } + + float arg_y_overlap = py_helper_keyword_float(n_args, args, 6, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_y_overlap), 0.0f); + + if ((arg_y_overlap != -1.0f) && ((arg_y_overlap < 0.0f) || (1.0f <= arg_y_overlap))) { + mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("0 <= y_overlap < 1")); + } + + uint8_t *tensor_arena = fb_alloc(arg_model->params.tensor_arena_size, FB_ALLOC_PREFER_SPEED | FB_ALLOC_CACHE_ALIGN); + + mp_obj_t objects_list = mp_obj_new_list(0, NULL); + + for (float scale = 1.0f; scale >= arg_min_scale; scale *= arg_scale_mul) { + // Either provide a subtle offset to center multiple detection windows or center the only detection window. + for (int y = roi.y + ((arg_y_overlap != -1.0f) + ? (fmodf(roi.h, (roi.h * scale)) / 2.0f) + : ((roi.h - (roi.h * scale)) / 2.0f)); + // Finish when the detection window is outside of the ROI. + (y + (roi.h * scale)) <= (roi.y + roi.h); + // Step by an overlap amount accounting for scale or just terminate after one iteration. + y += ((arg_y_overlap != -1.0f) ? (roi.h * scale * (1.0f - arg_y_overlap)) : roi.h)) { + // Either provide a subtle offset to center multiple detection windows or center the only detection window. + for (int x = roi.x + ((arg_x_overlap != -1.0f) + ? (fmodf(roi.w, (roi.w * scale)) / 2.0f) + : ((roi.w - (roi.w * scale)) / 2.0f)); + // Finish when the detection window is outside of the ROI. + (x + (roi.w * scale)) <= (roi.x + roi.w); + // Step by an overlap amount accounting for scale or just terminate after one iteration. + x += ((arg_x_overlap != -1.0f) ? (roi.w * scale * (1.0f - arg_x_overlap)) : roi.w)) { + + rectangle_t new_roi; + rectangle_init(&new_roi, x, y, roi.w * scale, roi.h * scale); + + if (rectangle_overlap(&roi, &new_roi)) { // Check if new_roi is null... + + py_tf_input_data_callback_data_t py_tf_input_data_callback_data; + py_tf_input_data_callback_data.img = arg_img; + py_tf_input_data_callback_data.roi = &new_roi; + + py_tf_classify_output_data_callback_data_t py_tf_classify_output_data_callback_data; + + if (libtf_invoke(arg_model->model_data, + tensor_arena, + &arg_model->params, + py_tf_input_data_callback, + &py_tf_input_data_callback_data, + py_tf_classify_output_data_callback, + &py_tf_classify_output_data_callback_data) != 0) { + // Note can't use MP_ERROR_TEXT here. + mp_raise_msg(&mp_type_OSError, (mp_rom_error_text_t) py_tf_putchar_buffer); + } + + py_tf_classification_obj_t *o = m_new_obj(py_tf_classification_obj_t); + o->base.type = &py_tf_classification_type; + o->x = mp_obj_new_int(new_roi.x); + o->y = mp_obj_new_int(new_roi.y); + o->w = mp_obj_new_int(new_roi.w); + o->h = mp_obj_new_int(new_roi.h); + o->output = py_tf_classify_output_data_callback_data.out; + mp_obj_list_append(objects_list, o); + } + } + } + } + + fb_alloc_free_till_mark(); + + return objects_list; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tf_classify_obj, 2, py_tf_classify); + +typedef struct py_tf_segment_output_data_callback_data { + mp_obj_t out; +} py_tf_segment_output_data_callback_data_t; + +STATIC void py_tf_segment_output_data_callback(void *callback_data, + void *model_output, + libtf_parameters_t *params) +{ + py_tf_segment_output_data_callback_data_t *arg = (py_tf_segment_output_data_callback_data_t *) callback_data; + + // Disable checking output scaling and zero-point. Nets can be all over the place on the output + // scaling and zero-point but still work with the code below. + + // if (params->output_datatype == LIBTF_DATATYPE_UINT8) { + // if (fast_roundf(params->output_scale * GRAYSCALE_RANGE) != 1) { + // mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected model output scale to be 1/255!")); + // } + + // if (params->output_zero_point != 0) { + // mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected model output zero point to be 0!")); + // } + // } + + // if (params->output_datatype == LIBTF_DATATYPE_INT8) { + // if (fast_roundf(params->output_scale * GRAYSCALE_RANGE) != 1) { + // mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected model output scale to be 1/255!")); + // } + + // if (params->output_zero_point != -GRAYSCALE_MID) { + // mp_raise_msg(&mp_type_ValueError, MP_ERROR_TEXT("Expected model output zero point to be -128!")); + // } + // } + + int shift = (params->output_datatype == LIBTF_DATATYPE_INT8) ? GRAYSCALE_MID : 0; + + arg->out = mp_obj_new_list(params->output_channels, NULL); + + for (int i = 0, ii = params->output_channels; i < ii; i++) { + + image_t img = { + .w = params->output_width, + .h = params->output_height, + .pixfmt = PIXFORMAT_GRAYSCALE, + .pixels = xalloc(params->output_width * params->output_height * sizeof(uint8_t)) + }; + + ((mp_obj_list_t *) arg->out)->items[i] = py_image_from_struct(&img); + + for (int y = 0, yy = params->output_height, xx = params->output_width; y < yy; y++) { + int row = y * xx * ii; + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&img, y); + + for (int x = 0; x < xx; x++) { + int col = x * ii; + + if (params->output_datatype == LIBTF_DATATYPE_FLOAT) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, + ((float *) model_output)[row + col + i] * GRAYSCALE_RANGE); + } else { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, + ((uint8_t *) model_output)[row + col + i] ^ shift); + } + } + } + } +} + +STATIC mp_obj_t int_py_tf_segment(bool detecting_mode, uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + fb_alloc_mark(); + py_tf_alloc_putchar_buffer(); + + py_tf_model_obj_t *arg_model = py_tf_load_alloc(args[0]); + image_t *arg_img = py_image_cobj(args[1]); + + rectangle_t roi; + py_helper_keyword_rectangle_roi(arg_img, n_args, args, 2, kw_args, &roi); + + uint8_t *tensor_arena = fb_alloc(arg_model->params.tensor_arena_size, FB_ALLOC_PREFER_SPEED | FB_ALLOC_CACHE_ALIGN); + + py_tf_input_data_callback_data_t py_tf_input_data_callback_data; + py_tf_input_data_callback_data.img = arg_img; + py_tf_input_data_callback_data.roi = &roi; + + py_tf_segment_output_data_callback_data_t py_tf_segment_output_data_callback_data; + + if (libtf_invoke(arg_model->model_data, + tensor_arena, + &arg_model->params, + py_tf_input_data_callback, + &py_tf_input_data_callback_data, + py_tf_segment_output_data_callback, + &py_tf_segment_output_data_callback_data) != 0) { + // Note can't use MP_ERROR_TEXT here. + mp_raise_msg(&mp_type_OSError, (mp_rom_error_text_t) py_tf_putchar_buffer); + } + + fb_alloc_free_till_mark(); + + if (!detecting_mode) { + return py_tf_segment_output_data_callback_data.out; + } + + list_t thresholds; + list_init(&thresholds, sizeof(color_thresholds_list_lnk_data_t)); + py_helper_keyword_thresholds(n_args, args, 3, kw_args, &thresholds); + + if (!list_size(&thresholds)) { + color_thresholds_list_lnk_data_t lnk_data; + lnk_data.LMin = GRAYSCALE_MID; + lnk_data.LMax = GRAYSCALE_RANGE; + lnk_data.AMin = COLOR_A_MIN; + lnk_data.AMax = COLOR_A_MAX; + lnk_data.BMin = COLOR_B_MIN; + lnk_data.BMax = COLOR_B_MAX; + list_push_back(&thresholds, &lnk_data); + } + + bool invert = py_helper_keyword_int(n_args, args, 4, kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_invert), false); + + mp_obj_list_t *img_list = (mp_obj_list_t *) py_tf_segment_output_data_callback_data.out; + mp_obj_list_t *out_list = mp_obj_new_list(img_list->len, NULL); + + fb_alloc_mark(); + + float fscale = 1.f / GRAYSCALE_RANGE; + for (int i = 0, ii = img_list->len; i < ii; i++) { + image_t *img = py_image_cobj(img_list->items[i]); + float x_scale = roi.w / ((float) img->w); + float y_scale = roi.h / ((float) img->h); + + list_t out; + imlib_find_blobs(&out, img, &((rectangle_t) {0, 0, img->w, img->h}), 1, 1, + &thresholds, invert, 1, 1, false, 0, + NULL, NULL, NULL, NULL, 0, 0); + + mp_obj_list_t *objects_list = mp_obj_new_list(list_size(&out), NULL); + for (int j = 0, jj = list_size(&out); j < jj; j++) { + find_blobs_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + + histogram_t hist; + hist.LBinCount = GRAYSCALE_RANGE + 1; + hist.ABinCount = 0; + hist.BBinCount = 0; + hist.LBins = fb_alloc(hist.LBinCount * sizeof(float), FB_ALLOC_NO_HINT); + hist.ABins = NULL; + hist.BBins = NULL; + imlib_get_histogram(&hist, img, &lnk_data.rect, &thresholds, invert, NULL); + + statistics_t stats; + imlib_get_statistics(&stats, img->pixfmt, &hist); + fb_free(); // fb_alloc(hist.LBinCount * sizeof(float), FB_ALLOC_NO_HINT); + + py_tf_classification_obj_t *o = m_new_obj(py_tf_classification_obj_t); + o->base.type = &py_tf_classification_type; + o->x = mp_obj_new_int(fast_floorf(lnk_data.rect.x * x_scale) + roi.x); + o->y = mp_obj_new_int(fast_floorf(lnk_data.rect.y * y_scale) + roi.y); + o->w = mp_obj_new_int(fast_floorf(lnk_data.rect.w * x_scale)); + o->h = mp_obj_new_int(fast_floorf(lnk_data.rect.h * y_scale)); + o->output = mp_obj_new_float(stats.LMean * fscale); + objects_list->items[j] = o; + } + + out_list->items[i] = objects_list; + } + + fb_alloc_free_till_mark(); + + return out_list; +} + +STATIC mp_obj_t py_tf_segment(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + return int_py_tf_segment(false, n_args, args, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tf_segment_obj, 2, py_tf_segment); + +STATIC mp_obj_t py_tf_detect(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + return int_py_tf_segment(true, n_args, args, kw_args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_tf_detect_obj, 2, py_tf_detect); + +mp_obj_t py_tf_len(mp_obj_t self_in) +{ + return mp_obj_new_int(((py_tf_model_obj_t *) self_in)->model_data_len); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_len_obj, py_tf_len); + +mp_obj_t py_tf_ram(mp_obj_t self_in) +{ + return mp_obj_new_int(((py_tf_model_obj_t *) self_in)->params.tensor_arena_size); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_ram_obj, py_tf_ram); + +mp_obj_t py_tf_input_height(mp_obj_t self_in) +{ + return mp_obj_new_int(((py_tf_model_obj_t *) self_in)->params.input_height); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_input_height_obj, py_tf_input_height); + +mp_obj_t py_tf_input_width(mp_obj_t self_in) +{ + return mp_obj_new_int(((py_tf_model_obj_t *) self_in)->params.input_width); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_input_width_obj, py_tf_input_width); + +mp_obj_t py_tf_input_channels(mp_obj_t self_in) +{ + return mp_obj_new_int(((py_tf_model_obj_t *) self_in)->params.input_channels); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_input_channels_obj, py_tf_input_channels); + +mp_obj_t py_tf_input_datatype(mp_obj_t self_in) +{ + const char *str = py_tf_map_datatype(((py_tf_model_obj_t *) self_in)->params.input_datatype); + return mp_obj_new_str(str, strlen(str)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_input_datatype_obj, py_tf_input_datatype); + +mp_obj_t py_tf_input_scale(mp_obj_t self_in) +{ + return mp_obj_new_float(((py_tf_model_obj_t *) self_in)->params.input_scale); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_input_scale_obj, py_tf_input_scale); + +mp_obj_t py_tf_input_zero_point(mp_obj_t self_in) +{ + return mp_obj_new_int(((py_tf_model_obj_t *) self_in)->params.input_zero_point); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_input_zero_point_obj, py_tf_input_zero_point); + +mp_obj_t py_tf_output_height(mp_obj_t self_in) +{ + return mp_obj_new_int(((py_tf_model_obj_t *) self_in)->params.output_height); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_output_height_obj, py_tf_output_height); + +mp_obj_t py_tf_output_width(mp_obj_t self_in) +{ + return mp_obj_new_int(((py_tf_model_obj_t *) self_in)->params.output_width); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_output_width_obj, py_tf_output_width); + +mp_obj_t py_tf_output_channels(mp_obj_t self_in) +{ + return mp_obj_new_int(((py_tf_model_obj_t *) self_in)->params.output_channels); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_output_channels_obj, py_tf_output_channels); + +mp_obj_t py_tf_output_datatype(mp_obj_t self_in) +{ + const char *str = py_tf_map_datatype(((py_tf_model_obj_t *) self_in)->params.output_datatype); + return mp_obj_new_str(str, strlen(str)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_output_datatype_obj, py_tf_output_datatype); + +mp_obj_t py_tf_output_scale(mp_obj_t self_in) +{ + return mp_obj_new_float(((py_tf_model_obj_t *) self_in)->params.output_scale); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_output_scale_obj, py_tf_output_scale); + +mp_obj_t py_tf_output_zero_point(mp_obj_t self_in) +{ + return mp_obj_new_int(((py_tf_model_obj_t *) self_in)->params.output_zero_point); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_tf_output_zero_point_obj, py_tf_output_zero_point); + +STATIC const mp_rom_map_elem_t locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_len), MP_ROM_PTR(&py_tf_len_obj) }, + { MP_ROM_QSTR(MP_QSTR_ram), MP_ROM_PTR(&py_tf_ram_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_height), MP_ROM_PTR(&py_tf_input_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_width), MP_ROM_PTR(&py_tf_input_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_channels), MP_ROM_PTR(&py_tf_input_channels_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_datatype), MP_ROM_PTR(&py_tf_input_datatype_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_scale), MP_ROM_PTR(&py_tf_input_scale_obj) }, + { MP_ROM_QSTR(MP_QSTR_input_zero_point), MP_ROM_PTR(&py_tf_input_zero_point_obj) }, + { MP_ROM_QSTR(MP_QSTR_output_height), MP_ROM_PTR(&py_tf_output_height_obj) }, + { MP_ROM_QSTR(MP_QSTR_output_width), MP_ROM_PTR(&py_tf_output_width_obj) }, + { MP_ROM_QSTR(MP_QSTR_output_channels), MP_ROM_PTR(&py_tf_output_channels_obj) }, + { MP_ROM_QSTR(MP_QSTR_output_datatype), MP_ROM_PTR(&py_tf_output_datatype_obj) }, + { MP_ROM_QSTR(MP_QSTR_output_scale), MP_ROM_PTR(&py_tf_output_scale_obj) }, + { MP_ROM_QSTR(MP_QSTR_output_zero_point), MP_ROM_PTR(&py_tf_output_zero_point_obj) }, + { MP_ROM_QSTR(MP_QSTR_classify), MP_ROM_PTR(&py_tf_classify_obj) }, + { MP_ROM_QSTR(MP_QSTR_segment), MP_ROM_PTR(&py_tf_segment_obj) }, + { MP_ROM_QSTR(MP_QSTR_detect), MP_ROM_PTR(&py_tf_detect_obj) } +}; + +STATIC MP_DEFINE_CONST_DICT(locals_dict, locals_dict_table); + +STATIC const mp_obj_type_t py_tf_model_type = { + { &mp_type_type }, + .name = MP_QSTR_tf_model, + .print = py_tf_model_print, + .locals_dict = (mp_obj_t) &locals_dict +}; + +#endif // IMLIB_ENABLE_TF + +STATIC const mp_rom_map_elem_t globals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_tf) }, +#ifdef IMLIB_ENABLE_TF + { MP_ROM_QSTR(MP_QSTR_load), MP_ROM_PTR(&py_tf_load_obj) }, + { MP_ROM_QSTR(MP_QSTR_free_from_fb), MP_ROM_PTR(&py_tf_free_from_fb_obj) }, + { MP_ROM_QSTR(MP_QSTR_classify), MP_ROM_PTR(&py_tf_classify_obj) }, + { MP_ROM_QSTR(MP_QSTR_segment), MP_ROM_PTR(&py_tf_segment_obj) }, + { MP_ROM_QSTR(MP_QSTR_detect), MP_ROM_PTR(&py_tf_detect_obj) }, +#else + { MP_ROM_QSTR(MP_QSTR_load), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_free_from_fb), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_classify), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_segment), MP_ROM_PTR(&py_func_unavailable_obj) }, + { MP_ROM_QSTR(MP_QSTR_detect), MP_ROM_PTR(&py_func_unavailable_obj) } +#endif // IMLIB_ENABLE_TF +}; + +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t tf_module = { + .base = { &mp_type_module }, + .globals = (mp_obj_t) &globals_dict +}; + +MP_REGISTER_MODULE(MP_QSTR_tf, tf_module, 1); diff --git a/github_source/minicv2/reference/py_tf.h b/github_source/minicv2/reference/py_tf.h new file mode 100644 index 0000000..5c2e98b --- /dev/null +++ b/github_source/minicv2/reference/py_tf.h @@ -0,0 +1,29 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Python Tensorflow library wrapper. + */ +#ifndef __PY_TF_H__ +#define __PY_TF_H__ +#include "libtf.h" + +typedef struct py_tf_model_obj { + mp_obj_base_t base; + unsigned char *model_data; + unsigned int model_data_len; + libtf_parameters_t params; +} py_tf_model_obj_t; + +// Log buffer +#define PY_TF_PUTCHAR_BUFFER_LEN 1023 +extern char *py_tf_putchar_buffer; +extern size_t py_tf_putchar_buffer_index; +extern size_t py_tf_putchar_buffer_len; +void py_tf_alloc_putchar_buffer(); + +#endif // __PY_TF_H__ diff --git a/github_source/minicv2/reference/py_tof.h b/github_source/minicv2/reference/py_tof.h new file mode 100644 index 0000000..3a8c222 --- /dev/null +++ b/github_source/minicv2/reference/py_tof.h @@ -0,0 +1,14 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * ToF Python module. + */ +#ifndef __PY_TOF_H__ +#define __PY_TOF_H__ +void py_tof_init0(); +#endif // __PY_TOF_H__ diff --git a/github_source/minicv2/src/agast.c b/github_source/minicv2/src/agast.c new file mode 100644 index 0000000..c2bd1bb --- /dev/null +++ b/github_source/minicv2/src/agast.c @@ -0,0 +1,1287 @@ +/* + * NOTE: This code is mostly auto-generated. + * See: http://archive.www6.in.tum.de/www6/Main/ResearchAgast.html + * + * Copyright (C) 2010 Elmar Mair + * The program you can download here is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * This program 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 General Public License for more details. You should have received a copy of the + * GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. + */ +#include +#include +#include "imlib.h" +#include "xalloc.h" +// #include "fb_alloc.h" +// #include "gc.h" + +#define MAX_ROW (480) +#define MAX_CORNERS (2000) +#define Compare(X, Y) ((X)>=(Y)) + +typedef struct { + uint16_t x; + uint16_t y; + uint16_t score; +} corner_t; + +static int s_width=-1; +static int_fast16_t s_offset0; +static int_fast16_t s_offset1; +static int_fast16_t s_offset2; +static int_fast16_t s_offset3; +static int_fast16_t s_offset4; +static int_fast16_t s_offset5; +static int_fast16_t s_offset6; +static int_fast16_t s_offset7; + +static corner_t *agast58_detect(image_t *img, int b, int* num_corners, rectangle_t *roi); +static int agast58_score(const unsigned char* p, int bstart); +static void nonmax_suppression(corner_t *corners, int num_corners, array_t *keypoints); + +static kp_t *alloc_keypoint(uint16_t x, uint16_t y, uint16_t score) +{ + // Note must set keypoint descriptor to zeros + kp_t *kpt = xalloc0(sizeof*kpt); + kpt->x = x; + kpt->y = y; + kpt->score = score; + return kpt; +} + +static void init5_8_pattern(int image_width) +{ + if(image_width==s_width) + return; + + s_width=image_width; + + s_offset0=(-1)+(0)*s_width; + s_offset1=(-1)+(-1)*s_width; + s_offset2=(0)+(-1)*s_width; + s_offset3=(1)+(-1)*s_width; + s_offset4=(1)+(0)*s_width; + s_offset5=(1)+(1)*s_width; + s_offset6=(0)+(1)*s_width; + s_offset7=(-1)+(1)*s_width; +} + +void agast_detect(image_t *image, array_t *keypoints, int threshold, rectangle_t *roi) +{ + int num_corners=0; + init5_8_pattern(image->w); + + // Find corners + corner_t *corners = agast58_detect(image, threshold, &num_corners, roi); + if (num_corners) { + // Score corners + for(int i=0; ipixels + (corners[i].y*image->w + corners[i].x), threshold); + } + // Non-max suppression + nonmax_suppression(corners, num_corners, keypoints); + } + + // Free corners; + xfree(corners); +} + +static void nonmax_suppression(corner_t *corners, int num_corners, array_t *keypoints) +{ + // gc_info_t info; + + int last_row; + int16_t row_start[MAX_ROW+1]; + const int sz = num_corners; + + /* Point above points (roughly) to the pixel above + the one of interest, if there is a feature there.*/ + int point_above = 0; + int point_below = 0; + + /* Find where each row begins (the corners are output in raster scan order). + A beginning of -1 signifies that there are no corners on that row. */ + last_row = corners[sz-1].y; + + for(int i=0; iy != prev_row) { + row_start[c->y] = i; + prev_row = c->y; + } + } + + for(int i=0; i 0) { + if (corners[i-1].x == pos.x-1 && corners[i-1].y == pos.y && Compare(corners[i-1].score, score)) { + goto nonmax; + } + } + + /*Check right*/ + if (i < (sz - 1)) { + if (corners[i+1].x == pos.x+1 && corners[i+1].y == pos.y && Compare(corners[i+1].score, score)) { + goto nonmax; + } + } + + /*Check above (if there is a valid row above)*/ + if (pos.y != 0 && row_start[pos.y - 1] != -1) { + /*Make sure that current point_above is one row above.*/ + if(corners[point_above].y < pos.y - 1) + point_above = row_start[pos.y-1]; + + /*Make point_above point to the first of the pixels above the current point, if it exists.*/ + for (; corners[point_above].y < pos.y && corners[point_above].x < pos.x - 1; point_above++) { + + } + + for (int j=point_above; corners[j].y < pos.y && corners[j].x <= pos.x + 1; j++) { + int x = corners[j].x; + if( (x == pos.x - 1 || x ==pos.x || x == pos.x+1) && Compare(corners[j].score, score)) + goto nonmax; + } + } + + /*Check below (if there is anything below)*/ + if (pos.y != last_row && row_start[pos.y + 1] != -1 && point_below < sz) /*Nothing below*/ { + if (corners[point_below].y < pos.y + 1) + point_below = row_start[pos.y+1]; + + /* Make point below point to one of the pixels belowthe current point, if it exists.*/ + for (; point_below < sz && corners[point_below].y == pos.y+1 && corners[point_below].x < pos.x - 1; point_below++) { + } + + for (int j=point_below; j < sz && corners[j].y == pos.y+1 && corners[j].x <= pos.x + 1; j++) { + int x = corners[j].x; + if( (x == pos.x - 1 || x ==pos.x || x == pos.x+1) && Compare(corners[j].score, score)) + goto nonmax; + } + } + + // gc_info(&info); + // #define MIN_MEM (10*1024) + // // Allocate keypoints until we're almost out of memory + // if (info.free < MIN_MEM) { + // // Try collecting memory + // gc_collect(); + // // If it didn't work break + // gc_info(&info); + // if (info.free < MIN_MEM) { + // break; + // } + // } + + // #undef MIN_MEM + array_push_back(keypoints, alloc_keypoint(pos.x, pos.y, pos.score)); + nonmax: + ; + } +} + +static corner_t *agast58_detect(image_t *img, int b, int* num_corners, rectangle_t *roi) +{ + int total=0; + register int x, y; + register int xsizeB=(roi->x+roi->w) - 2; + register int ysizeB=(roi->y+roi->h) - 1; + register int_fast16_t offset0, offset1, offset2, offset3, offset4, offset5, offset6, offset7; + register int width; + + offset0=s_offset0; + offset1=s_offset1; + offset2=s_offset2; + offset3=s_offset3; + offset4=s_offset4; + offset5=s_offset5; + offset6=s_offset6; + offset7=s_offset7; + width=s_width; + + // Try to alloc MAX_CORNERS or the actual max corners we can alloc. + // int max_corners = IM_MIN(MAX_CORNERS, (fb_avail() / sizeof(corner_t))); + int max_corners = MAX_CORNERS; + corner_t *corners = (corner_t*) xalloc(max_corners * sizeof(corner_t)); + + for(y=roi->y+1; y < ysizeB; y++) + { + x=roi->x; + while(1) + { +homogeneous: +{ + x++; + if(x>xsizeB) + break; + else + { + register const unsigned char* const p = img->pixels + y*width + x; + register const int cb = *p + b; + register const int c_b = *p - b; + if(p[offset0] > cb) + if(p[offset2] > cb) + if(p[offset3] > cb) + if(p[offset5] > cb) + if(p[offset1] > cb) + if(p[offset4] > cb) + goto success_structured; + else + if(p[offset7] > cb) + goto success_structured; + else + goto homogeneous; + else + if(p[offset4] > cb) + if(p[offset6] > cb) + goto success_structured; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset1] > cb) + if(p[offset4] > cb) + goto success_homogeneous; + else + if(p[offset7] > cb) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset7] > cb) + if(p[offset6] > cb) + if(p[offset5] > cb) + if(p[offset1] > cb) + goto success_structured; + else + if(p[offset4] > cb) + goto success_structured; + else + goto homogeneous; + else + if(p[offset1] > cb) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset5] < c_b) + if(p[offset3] < c_b) + if(p[offset7] < c_b) + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_structured; + else + goto structured; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset5] > cb) + if(p[offset7] > cb) + if(p[offset6] > cb) + if(p[offset1] > cb) + goto success_homogeneous; + else + if(p[offset4] > cb) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset5] < c_b) + if(p[offset3] < c_b) + if(p[offset2] < c_b) + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto success_structured; + else + goto homogeneous; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_structured; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset7] < c_b) + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_structured; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else if(p[offset0] < c_b) + if(p[offset2] < c_b) + if(p[offset7] > cb) + if(p[offset3] < c_b) + if(p[offset5] < c_b) + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto success_structured; + else + goto structured; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_structured; + else + goto structured; + else + goto homogeneous; + else + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto success_structured; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset5] > cb) + if(p[offset3] > cb) + if(p[offset4] > cb) + if(p[offset6] > cb) + goto success_structured; + else + goto structured; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset7] < c_b) + if(p[offset3] < c_b) + if(p[offset5] < c_b) + if(p[offset1] < c_b) + goto success_structured; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_structured; + else + goto structured; + else + goto homogeneous; + else + if(p[offset1] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + if(p[offset6] < c_b) + if(p[offset5] < c_b) + if(p[offset1] < c_b) + goto success_structured; + else + if(p[offset4] < c_b) + goto success_structured; + else + goto homogeneous; + else + if(p[offset1] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset3] < c_b) + if(p[offset5] < c_b) + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto success_structured; + else + goto homogeneous; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_structured; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset5] > cb) + if(p[offset3] > cb) + if(p[offset2] > cb) + if(p[offset1] > cb) + if(p[offset4] > cb) + goto success_structured; + else + goto homogeneous; + else + if(p[offset4] > cb) + if(p[offset6] > cb) + goto success_structured; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset7] > cb) + if(p[offset4] > cb) + if(p[offset6] > cb) + goto success_structured; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset5] < c_b) + if(p[offset7] < c_b) + if(p[offset6] < c_b) + if(p[offset1] < c_b) + goto success_homogeneous; + else + if(p[offset4] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset3] > cb) + if(p[offset5] > cb) + if(p[offset2] > cb) + if(p[offset1] > cb) + if(p[offset4] > cb) + goto success_homogeneous; + else + goto homogeneous; + else + if(p[offset4] > cb) + if(p[offset6] > cb) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset7] > cb) + if(p[offset4] > cb) + if(p[offset6] > cb) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset3] < c_b) + if(p[offset5] < c_b) + if(p[offset2] < c_b) + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset7] < c_b) + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + } +} +structured: +{ + x++; + if(x>xsizeB) + break; + else + { + register const unsigned char* const p = img->pixels + y*width + x; + register const int cb = *p + b; + register const int c_b = *p - b; + if(p[offset0] > cb) + if(p[offset2] > cb) + if(p[offset3] > cb) + if(p[offset5] > cb) + if(p[offset7] > cb) + if(p[offset1] > cb) + goto success_structured; + else + if(p[offset4] > cb) + if(p[offset6] > cb) + goto success_structured; + else + goto structured; + else + goto structured; + else + if(p[offset1] > cb) + if(p[offset4] > cb) + goto success_structured; + else + goto structured; + else + if(p[offset4] > cb) + if(p[offset6] > cb) + goto success_structured; + else + goto structured; + else + goto structured; + else + if(p[offset7] > cb) + if(p[offset1] > cb) + goto success_structured; + else + goto structured; + else + if(p[offset1] > cb) + if(p[offset4] > cb) + goto success_structured; + else + goto structured; + else + goto structured; + else + if(p[offset7] > cb) + if(p[offset6] > cb) + if(p[offset5] > cb) + if(p[offset1] > cb) + goto success_structured; + else + if(p[offset4] > cb) + goto success_structured; + else + goto structured; + else + if(p[offset1] > cb) + goto success_structured; + else + goto structured; + else + goto structured; + else + if(p[offset5] < c_b) + if(p[offset3] < c_b) + if(p[offset7] < c_b) + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_structured; + else + goto structured; + else + goto structured; + else + goto homogeneous; + else + goto homogeneous; + else + goto structured; + else + if(p[offset5] > cb) + if(p[offset7] > cb) + if(p[offset6] > cb) + if(p[offset1] > cb) + goto success_structured; + else + if(p[offset4] > cb) + goto success_structured; + else + goto structured; + else + goto structured; + else + goto structured; + else + if(p[offset5] < c_b) + if(p[offset3] < c_b) + if(p[offset2] < c_b) + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto success_structured; + else + goto structured; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_structured; + else + goto structured; + else + goto structured; + else + if(p[offset7] < c_b) + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto structured; + else + goto homogeneous; + else if(p[offset0] < c_b) + if(p[offset2] < c_b) + if(p[offset7] > cb) + if(p[offset3] < c_b) + if(p[offset5] < c_b) + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto success_structured; + else + goto structured; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_structured; + else + goto structured; + else + goto structured; + else + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto success_structured; + else + goto structured; + else + goto structured; + else + if(p[offset5] > cb) + if(p[offset3] > cb) + if(p[offset4] > cb) + if(p[offset6] > cb) + goto success_structured; + else + goto structured; + else + goto structured; + else + goto homogeneous; + else + goto structured; + else + if(p[offset7] < c_b) + if(p[offset3] < c_b) + if(p[offset5] < c_b) + if(p[offset1] < c_b) + goto success_structured; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_structured; + else + goto structured; + else + goto structured; + else + if(p[offset1] < c_b) + goto success_structured; + else + goto structured; + else + if(p[offset6] < c_b) + if(p[offset5] < c_b) + if(p[offset1] < c_b) + goto success_structured; + else + if(p[offset4] < c_b) + goto success_structured; + else + goto structured; + else + if(p[offset1] < c_b) + goto success_structured; + else + goto structured; + else + goto structured; + else + if(p[offset3] < c_b) + if(p[offset5] < c_b) + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset5] > cb) + if(p[offset3] > cb) + if(p[offset2] > cb) + if(p[offset1] > cb) + if(p[offset4] > cb) + goto success_structured; + else + goto structured; + else + if(p[offset4] > cb) + if(p[offset6] > cb) + goto success_structured; + else + goto structured; + else + goto structured; + else + if(p[offset7] > cb) + if(p[offset4] > cb) + if(p[offset6] > cb) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto structured; + else + if(p[offset5] < c_b) + if(p[offset7] < c_b) + if(p[offset6] < c_b) + if(p[offset1] < c_b) + goto success_structured; + else + if(p[offset4] < c_b) + goto success_structured; + else + goto structured; + else + goto structured; + else + goto structured; + else + goto homogeneous; + else + if(p[offset3] > cb) + if(p[offset5] > cb) + if(p[offset2] > cb) + if(p[offset1] > cb) + if(p[offset4] > cb) + goto success_homogeneous; + else + goto homogeneous; + else + if(p[offset4] > cb) + if(p[offset6] > cb) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset7] > cb) + if(p[offset4] > cb) + if(p[offset6] > cb) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset3] < c_b) + if(p[offset5] < c_b) + if(p[offset2] < c_b) + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + if(p[offset7] < c_b) + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto success_homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + else + goto homogeneous; + } +} +success_homogeneous: + corners[total].x = x; + corners[total].y = y; + if(++total == max_corners) { + goto done; + } + goto homogeneous; +success_structured: + corners[total].x = x; + corners[total].y = y; + if(++total == max_corners) { + goto done; + } + goto structured; + } + } +done: + *num_corners = total; + return corners; +} + +//using also bisection as propsed by Edward Rosten in FAST, +//but it is based on the OAST +static int agast58_score(const unsigned char* p, int bstart) +{ + int bmin = bstart; + int bmax = 255; + int b = (bmax + bmin)/2; + + register int_fast16_t offset0=s_offset0; + register int_fast16_t offset1=s_offset1; + register int_fast16_t offset2=s_offset2; + register int_fast16_t offset3=s_offset3; + register int_fast16_t offset4=s_offset4; + register int_fast16_t offset5=s_offset5; + register int_fast16_t offset6=s_offset6; + register int_fast16_t offset7=s_offset7; + + while(1) + { + register const int cb = *p + b; + register const int c_b = *p - b; + if(p[offset0] > cb) + if(p[offset2] > cb) + if(p[offset3] > cb) + if(p[offset5] > cb) + if(p[offset1] > cb) + if(p[offset4] > cb) + goto is_a_corner; + else + if(p[offset7] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + if(p[offset4] > cb) + if(p[offset6] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset1] > cb) + if(p[offset4] > cb) + goto is_a_corner; + else + if(p[offset7] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset7] > cb) + if(p[offset6] > cb) + if(p[offset5] > cb) + if(p[offset1] > cb) + goto is_a_corner; + else + if(p[offset4] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + if(p[offset1] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset5] < c_b) + if(p[offset3] < c_b) + if(p[offset7] < c_b) + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset5] > cb) + if(p[offset7] > cb) + if(p[offset6] > cb) + if(p[offset1] > cb) + goto is_a_corner; + else + if(p[offset4] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset5] < c_b) + if(p[offset3] < c_b) + if(p[offset2] < c_b) + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset7] < c_b) + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if(p[offset0] < c_b) + if(p[offset2] < c_b) + if(p[offset7] > cb) + if(p[offset3] < c_b) + if(p[offset5] < c_b) + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset5] > cb) + if(p[offset3] > cb) + if(p[offset4] > cb) + if(p[offset6] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset7] < c_b) + if(p[offset3] < c_b) + if(p[offset5] < c_b) + if(p[offset1] < c_b) + goto is_a_corner; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset1] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + if(p[offset6] < c_b) + if(p[offset5] < c_b) + if(p[offset1] < c_b) + goto is_a_corner; + else + if(p[offset4] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + if(p[offset1] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset3] < c_b) + if(p[offset5] < c_b) + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset5] > cb) + if(p[offset3] > cb) + if(p[offset2] > cb) + if(p[offset1] > cb) + if(p[offset4] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + if(p[offset4] > cb) + if(p[offset6] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset7] > cb) + if(p[offset4] > cb) + if(p[offset6] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset5] < c_b) + if(p[offset7] < c_b) + if(p[offset6] < c_b) + if(p[offset1] < c_b) + goto is_a_corner; + else + if(p[offset4] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset3] > cb) + if(p[offset5] > cb) + if(p[offset2] > cb) + if(p[offset1] > cb) + if(p[offset4] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + if(p[offset4] > cb) + if(p[offset6] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset7] > cb) + if(p[offset4] > cb) + if(p[offset6] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset3] < c_b) + if(p[offset5] < c_b) + if(p[offset2] < c_b) + if(p[offset1] < c_b) + if(p[offset4] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if(p[offset7] < c_b) + if(p[offset4] < c_b) + if(p[offset6] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + + is_a_corner: + bmin=b; + goto end; + + is_not_a_corner: + bmax=b; + goto end; + + end: + + if(bmin == bmax - 1 || bmin == bmax) + return bmin; + b = (bmin + bmax) / 2; + } +} diff --git a/github_source/minicv2/src/apriltag.c b/github_source/minicv2/src/apriltag.c new file mode 100644 index 0000000..414ae20 --- /dev/null +++ b/github_source/minicv2/src/apriltag.c @@ -0,0 +1,12738 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * AprilTags library. + */ +#include +#include +#include +#include "imlib.h" +#ifdef IMLIB_ENABLE_APRILTAGS +// Enable new code optimizations +#define OPTIMIZED + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" + +/* Copyright (C) 2013-2016, The Regents of The University of Michigan. +All rights reserved. + +This software was developed in the APRIL Robotics Lab under the +direction of Edwin Olson, ebolson@umich.edu. This software may be +available under alternative licensing terms; contact the address above. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the Regents of The University of Michigan. +*/ +#define fprintf(format, ...) +#define free(ptr) ({ umm_free(ptr); }) +#define malloc(size) ({ void *_r = umm_malloc(size); if(!_r) umm_alloc_fail(); _r; }) +#define realloc(ptr, size) ({ void *_r = umm_realloc((ptr), (size)); if(!_r) umm_alloc_fail(); _r; }) +#define calloc(num, item_size) ({ void *_r = umm_calloc((num), (item_size)); if(!_r) umm_alloc_fail(); _r; }) +#undef assert +#define assert(expression) +#define sqrt(x) fast_sqrtf(x) +#define sqrtf(x) fast_sqrtf(x) +#define floor(x) fast_floorf(x) +#define floorf(x) fast_floorf(x) +#define ceil(x) fast_ceilf(x) +#define ceilf(x) fast_ceilf(x) +#define round(x) fast_roundf(x) +#define roundf(x) fast_roundf(x) +#define atan(x) fast_atanf(x) +#define atanf(x) fast_atanf(x) +#define atan2(y, x) fast_atan2f((y), (x)) +#define atan2f(y, x) fast_atan2f((y), (x)) +#define exp(x) fast_expf(x) +#define expf(x) fast_expf(x) +#define cbrt(x) fast_cbrtf(x) +#define cbrtf(x) fast_cbrtf(x) +#define fabs(x) fast_fabsf(x) +#define fabsf(x) fast_fabsf(x) +#define log(x) fast_log(x) +#define logf(x) fast_log(x) +#undef log2 +#define log2(x) fast_log2(x) +#undef log2f +#define log2f(x) fast_log2(x) +#define sin(x) arm_sin_f32(x) +#define cos(x) arm_cos_f32(x) +#define fmin(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) +#define fminf(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) +#define fmax(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) +#define fmaxf(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "zarray.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Defines a structure which acts as a resize-able array ala Java's ArrayList. + */ +typedef struct zarray zarray_t; +struct zarray +{ + size_t el_sz; // size of each element + + int size; // how many elements? + int alloc; // we've allocated storage for how many elements? + char *data; +}; + +/** + * Creates and returns a variable array structure capable of holding elements of + * the specified size. It is the caller's responsibility to call zarray_destroy() + * on the returned array when it is no longer needed. + */ +static inline zarray_t *zarray_create(size_t el_sz) +{ + assert(el_sz > 0); + + zarray_t *za = (zarray_t*) calloc(1, sizeof(zarray_t)); + za->el_sz = el_sz; + return za; +} + +/** + * Creates and returns a variable array structure capable of holding elements of + * the specified size. It is the caller's responsibility to call zarray_destroy() + * on the returned array when it is no longer needed. + */ +static inline zarray_t *zarray_create_fail_ok(size_t el_sz) +{ + assert(el_sz > 0); + + zarray_t *za = (zarray_t*) umm_calloc(1, sizeof(zarray_t)); + if (za) za->el_sz = el_sz; + return za; +} + +/** + * Frees all resources associated with the variable array structure which was + * created by zarray_create(). After calling, 'za' will no longer be valid for storage. + */ +static inline void zarray_destroy(zarray_t *za) +{ + if (za == NULL) + return; + + if (za->data != NULL) + free(za->data); + memset(za, 0, sizeof(zarray_t)); + free(za); +} + +/** Allocate a new zarray that contains a copy of the data in the argument. **/ +static inline zarray_t *zarray_copy(const zarray_t *za) +{ + assert(za != NULL); + + zarray_t *zb = (zarray_t*) calloc(1, sizeof(zarray_t)); + zb->el_sz = za->el_sz; + zb->size = za->size; + zb->alloc = za->alloc; + zb->data = (char*) malloc(zb->alloc * zb->el_sz); + memcpy(zb->data, za->data, za->size * za->el_sz); + return zb; +} + +static int iceillog2(int v) +{ + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; +} + +/** + * Allocate a new zarray that contains a subset of the original + * elements. NOTE: end index is EXCLUSIVE, that is one past the last + * element you want. + */ +static inline zarray_t *zarray_copy_subset(const zarray_t *za, + int start_idx, + int end_idx_exclusive) +{ + zarray_t *out = (zarray_t*) calloc(1, sizeof(zarray_t)); + out->el_sz = za->el_sz; + out->size = end_idx_exclusive - start_idx; + out->alloc = iceillog2(out->size); // round up pow 2 + out->data = (char*) malloc(out->alloc * out->el_sz); + memcpy(out->data, za->data +(start_idx*out->el_sz), out->size*out->el_sz); + return out; +} + +/** + * Retrieves the number of elements currently being contained by the passed + * array, which may be different from its capacity. The index of the last element + * in the array will be one less than the returned value. + */ +static inline int zarray_size(const zarray_t *za) +{ + assert(za != NULL); + + return za->size; +} + +/** + * Returns 1 if zarray_size(za) == 0, + * returns 0 otherwise. + */ +/* +JUST CALL zarray_size +int zarray_isempty(const zarray_t *za) +{ + assert(za != NULL); + if (za->size <= 0) + return 1; + else + return 0; +} +*/ + + +/** + * Allocates enough internal storage in the supplied variable array structure to + * guarantee that the supplied number of elements (capacity) can be safely stored. + */ +static inline void zarray_ensure_capacity(zarray_t *za, int capacity) +{ + assert(za != NULL); + + if (capacity <= za->alloc) + return; + + while (za->alloc < capacity) { + za->alloc += 8; // use less memory // *= 2; + if (za->alloc < 8) + za->alloc = 8; + } + + za->data = (char*) realloc(za->data, za->alloc * za->el_sz); +} + +/** + * Adds a new element to the end of the supplied array, and sets its value + * (by copying) from the data pointed to by the supplied pointer 'p'. + * Automatically ensures that enough storage space is available for the new element. + */ +static inline void zarray_add(zarray_t *za, const void *p) +{ + assert(za != NULL); + assert(p != NULL); + + zarray_ensure_capacity(za, za->size + 1); + + memcpy(&za->data[za->size*za->el_sz], p, za->el_sz); + za->size++; +} + +/** + * Adds a new element to the end of the supplied array, and sets its value + * (by copying) from the data pointed to by the supplied pointer 'p'. + * Automatically ensures that enough storage space is available for the new element. + */ +static inline void zarray_add_fail_ok(zarray_t *za, const void *p) +{ + assert(za != NULL); + assert(p != NULL); + + if ((za->size + 1) > za->alloc) + { + char *old_data = za->data; + int old_alloc = za->alloc; + + while (za->alloc < (za->size + 1)) { + za->alloc += 8; // use less memory // *= 2; + if (za->alloc < 8) + za->alloc = 8; + } + + za->data = (char*) umm_realloc(za->data, za->alloc * za->el_sz); + + if (!za->data) { + za->data = old_data; + za->alloc = old_alloc; + return; + } + } + + memcpy(&za->data[za->size*za->el_sz], p, za->el_sz); + za->size++; +} + +/** + * Retrieves the element from the supplied array located at the zero-based + * index of 'idx' and copies its value into the variable pointed to by the pointer + * 'p'. + */ +static inline void zarray_get(const zarray_t *za, int idx, void *p) +{ + assert(za != NULL); + assert(p != NULL); + assert(idx >= 0); + assert(idx < za->size); + + memcpy(p, &za->data[idx*za->el_sz], za->el_sz); +} + +/** + * Similar to zarray_get(), but returns a "live" pointer to the internal + * storage, avoiding a memcpy. This pointer is not valid across + * operations which might move memory around (i.e. zarray_remove_value(), + * zarray_remove_index(), zarray_insert(), zarray_sort(), zarray_clear()). + * 'p' should be a pointer to the pointer which will be set to the internal address. + */ +inline static void zarray_get_volatile(const zarray_t *za, int idx, void *p) +{ + assert(za != NULL); + assert(p != NULL); + assert(idx >= 0); + assert(idx < za->size); + + *((void**) p) = &za->data[idx*za->el_sz]; +} + +inline static void zarray_truncate(zarray_t *za, int sz) +{ + assert(za != NULL); + assert(sz <= za->size); + za->size = sz; +} + +/** + * Copies the memory array used internally by zarray to store its owned + * elements to the address pointed by 'buffer'. It is the caller's responsibility + * to allocate zarray_size()*el_sz bytes for the copy to be stored and + * to free the memory when no longer needed. The memory allocated at 'buffer' + * and the internal zarray storage must not overlap. 'buffer_bytes' should be + * the size of the 'buffer' memory space, in bytes, and must be at least + * zarray_size()*el_sz. + * + * Returns the number of bytes copied into 'buffer'. + */ +static inline size_t zarray_copy_data(const zarray_t *za, void *buffer, size_t buffer_bytes) +{ + assert(za != NULL); + assert(buffer != NULL); + assert(buffer_bytes >= za->el_sz * za->size); + memcpy(buffer, za->data, za->el_sz * za->size); + return za->el_sz * za->size; +} + +/** + * Removes the entry at index 'idx'. + * If shuffle is true, the last element in the array will be placed in + * the newly-open space; if false, the zarray is compacted. + */ +static inline void zarray_remove_index(zarray_t *za, int idx, int shuffle) +{ + assert(za != NULL); + assert(idx >= 0); + assert(idx < za->size); + + if (shuffle) { + if (idx < za->size-1) + memcpy(&za->data[idx*za->el_sz], &za->data[(za->size-1)*za->el_sz], za->el_sz); + za->size--; + return; + } else { + // size = 10, idx = 7. Should copy 2 entries (at idx=8 and idx=9). + // size = 10, idx = 9. Should copy 0 entries. + int ncopy = za->size - idx - 1; + if (ncopy > 0) + memmove(&za->data[idx*za->el_sz], &za->data[(idx+1)*za->el_sz], ncopy*za->el_sz); + za->size--; + return; + } +} + +/** + * Remove the entry whose value is equal to the value pointed to by 'p'. + * If shuffle is true, the last element in the array will be placed in + * the newly-open space; if false, the zarray is compacted. At most + * one element will be removed. + * + * Note that objects will be compared using memcmp over the full size + * of the value. If the value is a struct that contains padding, + * differences in the padding bytes can cause comparisons to + * fail. Thus, it remains best practice to bzero all structs so that + * the padding is set to zero. + * + * Returns the number of elements removed (0 or 1). + */ +// remove the entry whose value is equal to the value pointed to by p. +// if shuffle is true, the last element in the array will be placed in +// the newly-open space; if false, the zarray is compacted. +static inline int zarray_remove_value(zarray_t *za, const void *p, int shuffle) +{ + assert(za != NULL); + assert(p != NULL); + + for (int idx = 0; idx < za->size; idx++) { + if (!memcmp(p, &za->data[idx*za->el_sz], za->el_sz)) { + zarray_remove_index(za, idx, shuffle); + return 1; + } + } + + return 0; +} + + +/** + * Creates a new entry and inserts it into the array so that it will have the + * index 'idx' (i.e. before the item which currently has that index). The value + * of the new entry is set to (copied from) the data pointed to by 'p'. 'idx' + * can be one larger than the current max index to place the new item at the end + * of the array, or zero to add it to an empty array. + */ +static inline void zarray_insert(zarray_t *za, int idx, const void *p) +{ + assert(za != NULL); + assert(p != NULL); + assert(idx >= 0); + assert(idx <= za->size); + + zarray_ensure_capacity(za, za->size + 1); + // size = 10, idx = 7. Should copy three entries (idx=7, idx=8, idx=9) + int ncopy = za->size - idx; + + memmove(&za->data[(idx+1)*za->el_sz], &za->data[idx*za->el_sz], ncopy*za->el_sz); + memcpy(&za->data[idx*za->el_sz], p, za->el_sz); + + za->size++; +} + + +/** + * Sets the value of the current element at index 'idx' by copying its value from + * the data pointed to by 'p'. The previous value of the changed element will be + * copied into the data pointed to by 'outp' if it is not null. + */ +static inline void zarray_set(zarray_t *za, int idx, const void *p, void *outp) +{ + assert(za != NULL); + assert(p != NULL); + assert(idx >= 0); + assert(idx < za->size); + + if (outp != NULL) + memcpy(outp, &za->data[idx*za->el_sz], za->el_sz); + + memcpy(&za->data[idx*za->el_sz], p, za->el_sz); +} + +/** + * Calls the supplied function for every element in the array in index order. + * The map function will be passed a pointer to each element in turn and must + * have the following format: + * + * void map_function(element_type *element) + */ +static inline void zarray_map(zarray_t *za, void (*f)(void*)) +{ + assert(za != NULL); + assert(f != NULL); + + for (int idx = 0; idx < za->size; idx++) + f(&za->data[idx*za->el_sz]); +} + +/** + * Calls the supplied function for every element in the array in index order. + * HOWEVER values are passed to the function, not pointers to values. In the + * case where the zarray stores object pointers, zarray_vmap allows you to + * pass in the object's destroy function (or free) directly. Can only be used + * with zarray's which contain pointer data. The map function should have the + * following format: + * + * void map_function(element_type *element) + */ + void zarray_vmap(zarray_t *za, void (*f)()); + +/** + * Removes all elements from the array and sets its size to zero. Pointers to + * any data elements obtained i.e. by zarray_get_volatile() will no longer be + * valid. + */ +static inline void zarray_clear(zarray_t *za) +{ + assert(za != NULL); + za->size = 0; +} + +/** + * Determines whether any element in the array has a value which matches the + * data pointed to by 'p'. + * + * Returns 1 if a match was found anywhere in the array, else 0. + */ +static inline int zarray_contains(const zarray_t *za, const void *p) +{ + assert(za != NULL); + assert(p != NULL); + + for (int idx = 0; idx < za->size; idx++) { + if (!memcmp(p, &za->data[idx*za->el_sz], za->el_sz)) { + return 1; + } + } + + return 0; +} + +/** + * Uses qsort() to sort the elements contained by the array in ascending order. + * Uses the supplied comparison function to determine the appropriate order. + * + * The comparison function will be passed a pointer to two elements to be compared + * and should return a measure of the difference between them (see strcmp()). + * I.e. it should return a negative number if the first element is 'less than' + * the second, zero if they are equivalent, and a positive number if the first + * element is 'greater than' the second. The function should have the following format: + * + * int comparison_function(const element_type *first, const element_type *second) + * + * zstrcmp() can be used as the comparison function for string elements, which + * will call strcmp() internally. + */ +static inline void zarray_sort(zarray_t *za, int (*compar)(const void*, const void*)) +{ + assert(za != NULL); + assert(compar != NULL); + if (za->size == 0) + return; + + qsort(za->data, za->size, za->el_sz, compar); +} + +/** + * A comparison function for comparing strings which can be used by zarray_sort() + * to sort arrays with char* elements. + */ + int zstrcmp(const void * a_pp, const void * b_pp); + +/** + * Find the index of an element, or return -1 if not found. Remember that p is + * a pointer to the element. + **/ +// returns -1 if not in array. Remember p is a pointer to the item. +static inline int zarray_index_of(const zarray_t *za, const void *p) +{ + assert(za != NULL); + assert(p != NULL); + + for (int i = 0; i < za->size; i++) { + if (!memcmp(p, &za->data[i*za->el_sz], za->el_sz)) + return i; + } + + return -1; +} + + + +/** + * Add all elements from 'source' into 'dest'. el_size must be the same + * for both lists + **/ +static inline void zarray_add_all(zarray_t * dest, const zarray_t * source) +{ + assert(dest->el_sz == source->el_sz); + + // Don't allocate on stack because el_sz could be larger than ~8 MB + // stack size + char *tmp = (char*)calloc(1, dest->el_sz); + + for (int i = 0; i < zarray_size(source); i++) { + zarray_get(source, i, tmp); + zarray_add(dest, tmp); + } + + free(tmp); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "zarray.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +int zstrcmp(const void * a_pp, const void * b_pp) +{ + assert(a_pp != NULL); + assert(b_pp != NULL); + + char * a = *(void**)a_pp; + char * b = *(void**)b_pp; + + return strcmp(a,b); +} + +void zarray_vmap(zarray_t *za, void (*f)()) +{ + assert(za != NULL); + assert(f != NULL); + assert(za->el_sz == sizeof(void*)); + + for (int idx = 0; idx < za->size; idx++) { + void *pp = &za->data[idx*za->el_sz]; + void *p = *(void**) pp; + f(p); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "math_util.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef M_TWOPI +# define M_TWOPI 6.2831853071795862319959 /* 2*pi */ +#endif + +#ifndef M_PI +# define M_PI 3.141592653589793238462643383279502884196 +#endif + +#define to_radians(x) ( (x) * (M_PI / 180.0 )) +#define to_degrees(x) ( (x) * (180.0 / M_PI )) + +#define max(A, B) (A < B ? B : A) +#define min(A, B) (A < B ? A : B) + + /* DEPRECATE, threshold meaningless without context. +static inline int dequals(float a, float b) +{ + float thresh = 1e-9; + return (fabs(a-b) < thresh); +} + */ + +static inline int dequals_mag(float a, float b, float thresh) +{ + return (fabs(a-b) < thresh); +} + +static inline int isq(int v) +{ + return v*v; +} + +static inline float fsq(float v) +{ + return v*v; +} + +static inline float sq(float v) +{ + return v*v; +} + +static inline float sgn(float v) +{ + return (v>=0) ? 1 : -1; +} + +// random number between [0, 1) +static inline float randf() +{ + return ((float) rand()) / (RAND_MAX + 1.0); +} + + +static inline float signed_randf() +{ + return randf()*2 - 1; +} + +// return a random integer between [0, bound) +static inline int irand(int bound) +{ + int v = (int) (randf()*bound); + if (v == bound) + return (bound-1); + //assert(v >= 0); + //assert(v < bound); + return v; +} + +/** Map vin to [0, 2*PI) **/ +static inline float mod2pi_positive(float vin) +{ + return vin - M_TWOPI * floor(vin / M_TWOPI); +} + +/** Map vin to [-PI, PI) **/ +static inline float mod2pi(float vin) +{ + return mod2pi_positive(vin + M_PI) - M_PI; +} + +/** Return vin such that it is within PI degrees of ref **/ +static inline float mod2pi_ref(float ref, float vin) +{ + return ref + mod2pi(vin - ref); +} + +/** Map vin to [0, 360) **/ +static inline float mod360_positive(float vin) +{ + return vin - 360 * floor(vin / 360); +} + +/** Map vin to [-180, 180) **/ +static inline float mod360(float vin) +{ + return mod360_positive(vin + 180) - 180; +} + +static inline int theta_to_int(float theta, int max) +{ + theta = mod2pi_ref(M_PI, theta); + int v = (int) (theta / M_TWOPI * max); + + if (v == max) + v = 0; + + assert (v >= 0 && v < max); + + return v; +} + +static inline int imin(int a, int b) +{ + return (a < b) ? a : b; +} + +static inline int imax(int a, int b) +{ + return (a > b) ? a : b; +} + +static inline int64_t imin64(int64_t a, int64_t b) +{ + return (a < b) ? a : b; +} + +static inline int64_t imax64(int64_t a, int64_t b) +{ + return (a > b) ? a : b; +} + +static inline int iclamp(int v, int minv, int maxv) +{ + return imax(minv, imin(v, maxv)); +} + +static inline float dclamp(float a, float min, float max) +{ + if (a < min) + return min; + if (a > max) + return max; + return a; +} + +static inline int fltcmp (float f1, float f2) +{ + float epsilon = f1-f2; + if (epsilon < 0.0) + return -1; + else if (epsilon > 0.0) + return 1; + else + return 0; +} + +static inline int dblcmp (float d1, float d2) +{ + float epsilon = d1-d2; + if (epsilon < 0.0) + return -1; + else if (epsilon > 0.0) + return 1; + else + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "svd22.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void svd22(const float A[4], float U[4], float S[2], float V[4]); + +// for the matrix [a b; b d] +void svd_sym_singular_values(float A00, float A01, float A11, + float *Lmin, float *Lmax); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "svd22.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** SVD 2x2. + + Computes singular values and vectors without squaring the input + matrix. With double precision math, results are accurate to about + 1E-16. + + U = [ cos(theta) -sin(theta) ] + [ sin(theta) cos(theta) ] + + S = [ e 0 ] + [ 0 f ] + + V = [ cos(phi) -sin(phi) ] + [ sin(phi) cos(phi) ] + + + Our strategy is basically to analytically multiply everything out + and then rearrange so that we can solve for theta, phi, e, and + f. (Derivation by ebolson@umich.edu 5/2016) + + V' = [ CP SP ] + [ -SP CP ] + +USV' = [ CT -ST ][ e*CP e*SP ] + [ ST CT ][ -f*SP f*CP ] + + = [e*CT*CP + f*ST*SP e*CT*SP - f*ST*CP ] + [e*ST*CP - f*SP*CT e*SP*ST + f*CP*CT ] + +A00+A11 = e*CT*CP + f*ST*SP + e*SP*ST + f*CP*CT + = e*(CP*CT + SP*ST) + f*(SP*ST + CP*CT) + = (e+f)(CP*CT + SP*ST) +B0 = (e+f)*cos(P-T) + +A00-A11 = e*CT*CP + f*ST*SP - e*SP*ST - f*CP*CT + = e*(CP*CT - SP*ST) - f*(-ST*SP + CP*CT) + = (e-f)(CP*CT - SP*ST) +B1 = (e-f)*cos(P+T) + +A01+A10 = e*CT*SP - f*ST*CP + e*ST*CP - f*SP*CT + = e(CT*SP + ST*CP) - f*(ST*CP + SP*CT) + = (e-f)*(CT*SP + ST*CP) +B2 = (e-f)*sin(P+T) + +A01-A10 = e*CT*SP - f*ST*CP - e*ST*CP + f*SP*CT + = e*(CT*SP - ST*CP) + f(SP*CT - ST*CP) + = (e+f)*(CT*SP - ST*CP) +B3 = (e+f)*sin(P-T) + +B0 = (e+f)*cos(P-T) +B1 = (e-f)*cos(P+T) +B2 = (e-f)*sin(P+T) +B3 = (e+f)*sin(P-T) + +B3/B0 = tan(P-T) + +B2/B1 = tan(P+T) + **/ +void svd22(const float A[4], float U[4], float S[2], float V[4]) +{ + float A00 = A[0]; + float A01 = A[1]; + float A10 = A[2]; + float A11 = A[3]; + + float B0 = A00 + A11; + float B1 = A00 - A11; + float B2 = A01 + A10; + float B3 = A01 - A10; + + float PminusT = atan2(B3, B0); + float PplusT = atan2(B2, B1); + + float P = (PminusT + PplusT) / 2; + float T = (-PminusT + PplusT) / 2; + + float CP = cos(P), SP = sin(P); + float CT = cos(T), ST = sin(T); + + U[0] = CT; + U[1] = -ST; + U[2] = ST; + U[3] = CT; + + V[0] = CP; + V[1] = -SP; + V[2] = SP; + V[3] = CP; + + // C0 = e+f. There are two ways to compute C0; we pick the one + // that is better conditioned. + float CPmT = cos(P-T), SPmT = sin(P-T); + float C0 = 0; + if (fabs(CPmT) > fabs(SPmT)) + C0 = B0 / CPmT; + else + C0 = B3 / SPmT; + + // C1 = e-f. There are two ways to compute C1; we pick the one + // that is better conditioned. + float CPpT = cos(P+T), SPpT = sin(P+T); + float C1 = 0; + if (fabs(CPpT) > fabs(SPpT)) + C1 = B1 / CPpT; + else + C1 = B2 / SPpT; + + // e and f are the singular values + float e = (C0 + C1) / 2; + float f = (C0 - C1) / 2; + + if (e < 0) { + e = -e; + U[0] = -U[0]; + U[2] = -U[2]; + } + + if (f < 0) { + f = -f; + U[1] = -U[1]; + U[3] = -U[3]; + } + + // sort singular values. + if (e > f) { + // already in big-to-small order. + S[0] = e; + S[1] = f; + } else { + // Curiously, this code never seems to get invoked. Why is it + // that S[0] always ends up the dominant vector? However, + // this code has been tested (flipping the logic forces us to + // sort the singular values in ascending order). + // + // P = [ 0 1 ; 1 0 ] + // USV' = (UP)(PSP)(PV') + // = (UP)(PSP)(VP)' + // = (UP)(PSP)(P'V')' + S[0] = f; + S[1] = e; + + // exchange columns of U and V + float tmp[2]; + tmp[0] = U[0]; + tmp[1] = U[2]; + U[0] = U[1]; + U[2] = U[3]; + U[1] = tmp[0]; + U[3] = tmp[1]; + + tmp[0] = V[0]; + tmp[1] = V[2]; + V[0] = V[1]; + V[2] = V[3]; + V[1] = tmp[0]; + V[3] = tmp[1]; + } + + /* + float SM[4] = { S[0], 0, 0, S[1] }; + + doubles_print_mat(U, 2, 2, "%20.10g"); + doubles_print_mat(SM, 2, 2, "%20.10g"); + doubles_print_mat(V, 2, 2, "%20.10g"); + printf("A:\n"); + doubles_print_mat(A, 2, 2, "%20.10g"); + + float SVt[4]; + doubles_mat_ABt(SM, 2, 2, V, 2, 2, SVt, 2, 2); + float USVt[4]; + doubles_mat_AB(U, 2, 2, SVt, 2, 2, USVt, 2, 2); + + printf("USVt\n"); + doubles_print_mat(USVt, 2, 2, "%20.10g"); + + float diff[4]; + for (int i = 0; i < 4; i++) + diff[i] = A[i] - USVt[i]; + + printf("diff\n"); + doubles_print_mat(diff, 2, 2, "%20.10g"); + + */ + +} + + +// for the matrix [a b; b d] +void svd_sym_singular_values(float A00, float A01, float A11, + float *Lmin, float *Lmax) +{ + float A10 = A01; + + float B0 = A00 + A11; + float B1 = A00 - A11; + float B2 = A01 + A10; + float B3 = A01 - A10; + + float PminusT = atan2(B3, B0); + float PplusT = atan2(B2, B1); + + float P = (PminusT + PplusT) / 2; + float T = (-PminusT + PplusT) / 2; + + // C0 = e+f. There are two ways to compute C0; we pick the one + // that is better conditioned. + float CPmT = cos(P-T), SPmT = sin(P-T); + float C0 = 0; + if (fabs(CPmT) > fabs(SPmT)) + C0 = B0 / CPmT; + else + C0 = B3 / SPmT; + + // C1 = e-f. There are two ways to compute C1; we pick the one + // that is better conditioned. + float CPpT = cos(P+T), SPpT = sin(P+T); + float C1 = 0; + if (fabs(CPpT) > fabs(SPpT)) + C1 = B1 / CPpT; + else + C1 = B2 / SPpT; + + // e and f are the singular values + float e = (C0 + C1) / 2; + float f = (C0 - C1) / 2; + + *Lmin = fmin(e, f); + *Lmax = fmax(e, f); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "matd.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Defines a matrix structure for holding float-precision values with + * data in row-major order (i.e. index = row*ncols + col). + * + * nrows and ncols are 1-based counts with the exception that a scalar (non-matrix) + * is represented with nrows=0 and/or ncols=0. + */ +typedef struct +{ + unsigned int nrows, ncols; + float data[]; +// float *data; +} matd_t; + +#define MATD_ALLOC(name, nrows, ncols) float name ## _storage [nrows*ncols]; matd_t name = { .nrows = nrows, .ncols = ncols, .data = &name ## _storage }; + +/** + * Defines a small value which can be used in place of zero for approximating + * calculations which are singular at zero values (i.e. inverting a matrix with + * a zero or near-zero determinant). + */ +#define MATD_EPS 1e-8 + +/** + * A macro to reference a specific matd_t data element given it's zero-based + * row and column indexes. Suitable for both retrieval and assignment. + */ +#define MATD_EL(m, row, col) (m)->data[((row)*(m)->ncols + (col))] + +/** + * Creates a float matrix with the given number of rows and columns (or a scalar + * in the case where rows=0 and/or cols=0). All data elements will be initialized + * to zero. It is the caller's responsibility to call matd_destroy() on the + * returned matrix. + */ +matd_t *matd_create(int rows, int cols); + +/** + * Creates a float matrix with the given number of rows and columns (or a scalar + * in the case where rows=0 and/or cols=0). All data elements will be initialized + * using the supplied array of data, which must contain at least rows*cols elements, + * arranged in row-major order (i.e. index = row*ncols + col). It is the caller's + * responsibility to call matd_destroy() on the returned matrix. + */ +matd_t *matd_create_data(int rows, int cols, const float *data); + +/** + * Creates a float matrix with the given number of rows and columns (or a scalar + * in the case where rows=0 and/or cols=0). All data elements will be initialized + * using the supplied array of float data, which must contain at least rows*cols elements, + * arranged in row-major order (i.e. index = row*ncols + col). It is the caller's + * responsibility to call matd_destroy() on the returned matrix. + */ +matd_t *matd_create_dataf(int rows, int cols, const float *data); + +/** + * Creates a square identity matrix with the given number of rows (and + * therefore columns), or a scalar with value 1 in the case where dim=0. + * It is the caller's responsibility to call matd_destroy() on the + * returned matrix. + */ +matd_t *matd_identity(int dim); + +/** + * Creates a scalar with the supplied value 'v'. It is the caller's responsibility + * to call matd_destroy() on the returned matrix. + * + * NOTE: Scalars are different than 1x1 matrices (implementation note: + * they are encoded as 0x0 matrices). For example: for matrices A*B, A + * and B must both have specific dimensions. However, if A is a + * scalar, there are no restrictions on the size of B. + */ +matd_t *matd_create_scalar(float v); + +/** + * Retrieves the cell value for matrix 'm' at the given zero-based row and column index. + * Performs more thorough validation checking than MATD_EL(). + */ +float matd_get(const matd_t *m, int row, int col); + +/** + * Assigns the given value to the matrix cell at the given zero-based row and + * column index. Performs more thorough validation checking than MATD_EL(). + */ +void matd_put(matd_t *m, int row, int col, float value); + +/** + * Retrieves the scalar value of the given element ('m' must be a scalar). + * Performs more thorough validation checking than MATD_EL(). + */ +float matd_get_scalar(const matd_t *m); + +/** + * Assigns the given value to the supplied scalar element ('m' must be a scalar). + * Performs more thorough validation checking than MATD_EL(). + */ +void matd_put_scalar(matd_t *m, float value); + +/** + * Creates an exact copy of the supplied matrix 'm'. It is the caller's + * responsibility to call matd_destroy() on the returned matrix. + */ +matd_t *matd_copy(const matd_t *m); + +/** + * Creates a copy of a subset of the supplied matrix 'a'. The subset will include + * rows 'r0' through 'r1', inclusive ('r1' >= 'r0'), and columns 'c0' through 'c1', + * inclusive ('c1' >= 'c0'). All parameters are zero-based (i.e. matd_select(a, 0, 0, 0, 0) + * will return only the first cell). Cannot be used on scalars or to extend + * beyond the number of rows/columns of 'a'. It is the caller's responsibility to + * call matd_destroy() on the returned matrix. + */ +matd_t *matd_select(const matd_t *a, int r0, int r1, int c0, int c1); + +/** + * Prints the supplied matrix 'm' to standard output by applying the supplied + * printf format specifier 'fmt' for each individual element. Each row will + * be printed on a separate newline. + */ +void matd_print(const matd_t *m, const char *fmt); + +/** + * Prints the transpose of the supplied matrix 'm' to standard output by applying + * the supplied printf format specifier 'fmt' for each individual element. Each + * row will be printed on a separate newline. + */ +void matd_print_transpose(const matd_t *m, const char *fmt); + +/** + * Adds the two supplied matrices together, cell-by-cell, and returns the results + * as a new matrix of the same dimensions. The supplied matrices must have + * identical dimensions. It is the caller's responsibility to call matd_destroy() + * on the returned matrix. + */ +matd_t *matd_add(const matd_t *a, const matd_t *b); + +/** + * Adds the values of 'b' to matrix 'a', cell-by-cell, and overwrites the + * contents of 'a' with the results. The supplied matrices must have + * identical dimensions. + */ +void matd_add_inplace(matd_t *a, const matd_t *b); + +/** + * Subtracts matrix 'b' from matrix 'a', cell-by-cell, and returns the results + * as a new matrix of the same dimensions. The supplied matrices must have + * identical dimensions. It is the caller's responsibility to call matd_destroy() + * on the returned matrix. + */ +matd_t *matd_subtract(const matd_t *a, const matd_t *b); + +/** + * Subtracts the values of 'b' from matrix 'a', cell-by-cell, and overwrites the + * contents of 'a' with the results. The supplied matrices must have + * identical dimensions. + */ +void matd_subtract_inplace(matd_t *a, const matd_t *b); + +/** + * Scales all cell values of matrix 'a' by the given scale factor 's' and + * returns the result as a new matrix of the same dimensions. It is the caller's + * responsibility to call matd_destroy() on the returned matrix. + */ +matd_t *matd_scale(const matd_t *a, float s); + +/** + * Scales all cell values of matrix 'a' by the given scale factor 's' and + * overwrites the contents of 'a' with the results. + */ +void matd_scale_inplace(matd_t *a, float s); + +/** + * Multiplies the two supplied matrices together (matrix product), and returns the + * results as a new matrix. The supplied matrices must have dimensions such that + * columns(a) = rows(b). The returned matrix will have a row count of rows(a) + * and a column count of columns(b). It is the caller's responsibility to call + * matd_destroy() on the returned matrix. + */ +matd_t *matd_multiply(const matd_t *a, const matd_t *b); + +/** + * Creates a matrix which is the transpose of the supplied matrix 'a'. It is the + * caller's responsibility to call matd_destroy() on the returned matrix. + */ +matd_t *matd_transpose(const matd_t *a); + +/** + * Calculates the determinant of the supplied matrix 'a'. + */ +float matd_det(const matd_t *a); + +/** + * Attempts to compute an inverse of the supplied matrix 'a' and return it as + * a new matrix. This is strictly only possible if the determinant of 'a' is + * non-zero (matd_det(a) != 0). + * + * If the determinant is zero, NULL is returned. It is otherwise the + * caller's responsibility to cope with the results caused by poorly + * conditioned matrices. (E.g.., if such a situation is likely to arise, compute + * the pseudo-inverse from the SVD.) + **/ +matd_t *matd_inverse(const matd_t *a); + +static inline void matd_set_data(matd_t *m, const float *data) +{ + memcpy(m->data, data, m->nrows * m->ncols * sizeof(float)); +} + +/** + * Determines whether the supplied matrix 'a' is a scalar (positive return) or + * not (zero return, indicating a matrix of dimensions at least 1x1). + */ +static inline int matd_is_scalar(const matd_t *a) +{ + assert(a != NULL); + return a->ncols == 0 || a->nrows == 0; +} + +/** + * Determines whether the supplied matrix 'a' is a row or column vector + * (positive return) or not (zero return, indicating either 'a' is a scalar or a + * matrix with at least one dimension > 1). + */ +static inline int matd_is_vector(const matd_t *a) +{ + assert(a != NULL); + return a->ncols == 1 || a->nrows == 1; +} + +/** + * Determines whether the supplied matrix 'a' is a row or column vector + * with a dimension of 'len' (positive return) or not (zero return). + */ +static inline int matd_is_vector_len(const matd_t *a, int len) +{ + assert(a != NULL); + return (a->ncols == 1 && a->nrows == len) || (a->ncols == len && a->nrows == 1); +} + +/** + * Calculates the magnitude of the supplied matrix 'a'. + */ +float matd_vec_mag(const matd_t *a); + +/** + * Calculates the magnitude of the distance between the points represented by + * matrices 'a' and 'b'. Both 'a' and 'b' must be vectors and have the same + * dimension (although one may be a row vector and one may be a column vector). + */ +float matd_vec_dist(const matd_t *a, const matd_t *b); + + +/** + * Same as matd_vec_dist, but only uses the first 'n' terms to compute distance + */ +float matd_vec_dist_n(const matd_t *a, const matd_t *b, int n); + +/** + * Calculates the dot product of two vectors. Both 'a' and 'b' must be vectors + * and have the same dimension (although one may be a row vector and one may be + * a column vector). + */ +float matd_vec_dot_product(const matd_t *a, const matd_t *b); + +/** + * Calculates the normalization of the supplied vector 'a' (i.e. a unit vector + * of the same dimension and orientation as 'a' with a magnitude of 1) and returns + * it as a new vector. 'a' must be a vector of any dimension and must have a + * non-zero magnitude. It is the caller's responsibility to call matd_destroy() + * on the returned matrix. + */ +matd_t *matd_vec_normalize(const matd_t *a); + +/** + * Calculates the cross product of supplied matrices 'a' and 'b' (i.e. a x b) + * and returns it as a new matrix. Both 'a' and 'b' must be vectors of dimension + * 3, but can be either row or column vectors. It is the caller's responsibility + * to call matd_destroy() on the returned matrix. + */ +matd_t *matd_crossproduct(const matd_t *a, const matd_t *b); + +float matd_err_inf(const matd_t *a, const matd_t *b); + +/** + * Creates a new matrix by applying a series of matrix operations, as expressed + * in 'expr', to the supplied list of matrices. Each matrix to be operated upon + * must be represented in the expression by a separate matrix placeholder, 'M', + * and there must be one matrix supplied as an argument for each matrix + * placeholder in the expression. All rules and caveats of the corresponding + * matrix operations apply to the operated-on matrices. It is the caller's + * responsibility to call matd_destroy() on the returned matrix. + * + * Available operators (in order of increasing precedence): + * M+M add two matrices together + * M-M subtract one matrix from another + * M*M multiply to matrices together (matrix product) + * MM multiply to matrices together (matrix product) + * -M negate a matrix + * M^-1 take the inverse of a matrix + * M' take the transpose of a matrix + * + * Expressions can be combined together and grouped by enclosing them in + * parenthesis, i.e.: + * -M(M+M+M)-(M*M)^-1 + * + * Scalar values can be generated on-the-fly, i.e.: + * M*2.2 scales M by 2.2 + * -2+M adds -2 to all elements of M + * + * All whitespace in the expression is ignored. + */ +matd_t *matd_op(const char *expr, ...); + +/** + * Frees the memory associated with matrix 'm', being the result of an earlier + * call to a matd_*() function, after which 'm' will no longer be usable. + */ +void matd_destroy(matd_t *m); + +typedef struct +{ + matd_t *U; + matd_t *S; + matd_t *V; +} matd_svd_t; + +/** Compute a complete SVD of a matrix. The SVD exists for all + * matrices. For a matrix MxN, we will have: + * + * A = U*S*V' + * + * where A is MxN, U is MxM (and is an orthonormal basis), S is MxN + * (and is diagonal up to machine precision), and V is NxN (and is an + * orthonormal basis). + * + * The caller is responsible for destroying U, S, and V. + **/ +matd_svd_t matd_svd(matd_t *A); + +#define MATD_SVD_NO_WARNINGS 1 + matd_svd_t matd_svd_flags(matd_t *A, int flags); + +//////////////////////////////// +// PLU Decomposition + +// All square matrices (even singular ones) have a partially-pivoted +// LU decomposition such that A = PLU, where P is a permutation +// matrix, L is a lower triangular matrix, and U is an upper +// triangular matrix. +// +typedef struct +{ + // was the input matrix singular? When a zero pivot is found, this + // flag is set to indicate that this has happened. + int singular; + + unsigned int *piv; // permutation indices + int pivsign; // either +1 or -1 + + // The matd_plu_t object returned "owns" the enclosed LU matrix. It + // is not expected that the returned object is itself useful to + // users: it contains the L and U information all smushed + // together. + matd_t *lu; // combined L and U matrices, permuted so they can be triangular. +} matd_plu_t; + +matd_plu_t *matd_plu(const matd_t *a); +void matd_plu_destroy(matd_plu_t *mlu); +float matd_plu_det(const matd_plu_t *lu); +matd_t *matd_plu_p(const matd_plu_t *lu); +matd_t *matd_plu_l(const matd_plu_t *lu); +matd_t *matd_plu_u(const matd_plu_t *lu); +matd_t *matd_plu_solve(const matd_plu_t *mlu, const matd_t *b); + +// uses LU decomposition internally. +matd_t *matd_solve(matd_t *A, matd_t *b); + +//////////////////////////////// +// Cholesky Factorization + +/** + * Creates a float matrix with the Cholesky lower triangular matrix + * of A. A must be symmetric, positive definite. It is the caller's + * responsibility to call matd_destroy() on the returned matrix. + */ +//matd_t *matd_cholesky(const matd_t *A); + +typedef struct +{ + int is_spd; + matd_t *u; +} matd_chol_t; + +matd_chol_t *matd_chol(matd_t *A); +matd_t *matd_chol_solve(const matd_chol_t *chol, const matd_t *b); +void matd_chol_destroy(matd_chol_t *chol); +// only sensible on PSD matrices +matd_t *matd_chol_inverse(matd_t *a); + +void matd_ltransposetriangle_solve(matd_t *u, const float *b, float *x); +void matd_ltriangle_solve(matd_t *u, const float *b, float *x); +void matd_utriangle_solve(matd_t *u, const float *b, float *x); + + +float matd_max(matd_t *m); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "matd.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// a matd_t with rows=0 cols=0 is a SCALAR. + +// to ease creating mati, matf, etc. in the future. +#define TYPE float + +matd_t *matd_create(int rows, int cols) +{ + assert(rows >= 0); + assert(cols >= 0); + + if (rows == 0 || cols == 0) + return matd_create_scalar(0); + + matd_t *m = calloc(1, sizeof(matd_t) + (rows*cols*sizeof(float))); + m->nrows = rows; + m->ncols = cols; + + return m; +} + +matd_t *matd_create_scalar(TYPE v) +{ + matd_t *m = calloc(1, sizeof(matd_t) + sizeof(float)); + m->nrows = 0; + m->ncols = 0; + m->data[0] = v; + + return m; +} + +matd_t *matd_create_data(int rows, int cols, const TYPE *data) +{ + if (rows == 0 || cols == 0) + return matd_create_scalar(data[0]); + + matd_t *m = matd_create(rows, cols); + for (int i = 0; i < rows * cols; i++) + m->data[i] = data[i]; + + return m; +} + +matd_t *matd_create_dataf(int rows, int cols, const float *data) +{ + if (rows == 0 || cols == 0) + return matd_create_scalar(data[0]); + + matd_t *m = matd_create(rows, cols); + for (int i = 0; i < rows * cols; i++) + m->data[i] = (float)data[i]; + + return m; +} + +matd_t *matd_identity(int dim) +{ + if (dim == 0) + return matd_create_scalar(1); + + matd_t *m = matd_create(dim, dim); + for (int i = 0; i < dim; i++) + MATD_EL(m, i, i) = 1; + + return m; +} + +// row and col are zero-based +TYPE matd_get(const matd_t *m, int row, int col) +{ + assert(m != NULL); + assert(!matd_is_scalar(m)); + assert(row >= 0); + assert(row < m->nrows); + assert(col >= 0); + assert(col < m->ncols); + + return MATD_EL(m, row, col); +} + +// row and col are zero-based +void matd_put(matd_t *m, int row, int col, TYPE value) +{ + assert(m != NULL); + + if (matd_is_scalar(m)) { + matd_put_scalar(m, value); + return; + } + + assert(row >= 0); + assert(row < m->nrows); + assert(col >= 0); + assert(col < m->ncols); + + MATD_EL(m, row, col) = value; +} + +TYPE matd_get_scalar(const matd_t *m) +{ + assert(m != NULL); + assert(matd_is_scalar(m)); + + return (m->data[0]); +} + +void matd_put_scalar(matd_t *m, TYPE value) +{ + assert(m != NULL); + assert(matd_is_scalar(m)); + + m->data[0] = value; +} + +matd_t *matd_copy(const matd_t *m) +{ + assert(m != NULL); + + matd_t *x = matd_create(m->nrows, m->ncols); + if (matd_is_scalar(m)) + x->data[0] = m->data[0]; + else + memcpy(x->data, m->data, sizeof(TYPE)*m->ncols*m->nrows); + + return x; +} + +matd_t *matd_select(const matd_t * a, int r0, int r1, int c0, int c1) +{ + assert(a != NULL); + + assert(r0 >= 0 && r0 < a->nrows); + assert(c0 >= 0 && c0 < a->ncols); + + int nrows = r1 - r0 + 1; + int ncols = c1 - c0 + 1; + + matd_t * r = matd_create(nrows, ncols); + + for (int row = r0; row <= r1; row++) + for (int col = c0; col <= c1; col++) + MATD_EL(r,row-r0,col-c0) = MATD_EL(a,row,col); + + return r; +} + +void matd_print(const matd_t *m, const char *fmt) +{ + assert(m != NULL); + assert(fmt != NULL); + + if (matd_is_scalar(m)) { + printf(fmt, (double) MATD_EL(m, 0, 0)); + printf("\n"); + } else { + for (int i = 0; i < m->nrows; i++) { + for (int j = 0; j < m->ncols; j++) { + printf(fmt, (double) MATD_EL(m, i, j)); + } + printf("\n"); + } + } +} + +void matd_print_transpose(const matd_t *m, const char *fmt) +{ + assert(m != NULL); + assert(fmt != NULL); + + if (matd_is_scalar(m)) { + printf(fmt, (double) MATD_EL(m, 0, 0)); + printf("\n"); + } else { + for (int j = 0; j < m->ncols; j++) { + for (int i = 0; i < m->nrows; i++) { + printf(fmt, (double) MATD_EL(m, i, j)); + } + printf("\n"); + } + } +} + +void matd_destroy(matd_t *m) +{ + if (!m) + return; + + assert(m != NULL); + free(m); +} + +matd_t *matd_multiply(const matd_t *a, const matd_t *b) +{ + assert(a != NULL); + assert(b != NULL); + + if (matd_is_scalar(a)) + return matd_scale(b, a->data[0]); + if (matd_is_scalar(b)) + return matd_scale(a, b->data[0]); + + assert(a->ncols == b->nrows); + matd_t *m = matd_create(a->nrows, b->ncols); + + for (int i = 0; i < m->nrows; i++) { + for (int j = 0; j < m->ncols; j++) { + TYPE acc = 0; + for (int k = 0; k < a->ncols; k++) { + acc += MATD_EL(a, i, k) * MATD_EL(b, k, j); + } + MATD_EL(m, i, j) = acc; + } + } + + return m; +} + +matd_t *matd_scale(const matd_t *a, float s) +{ + assert(a != NULL); + + if (matd_is_scalar(a)) + return matd_create_scalar(a->data[0] * s); + + matd_t *m = matd_create(a->nrows, a->ncols); + + for (int i = 0; i < m->nrows; i++) { + for (int j = 0; j < m->ncols; j++) { + MATD_EL(m, i, j) = s * MATD_EL(a, i, j); + } + } + + return m; +} + +void matd_scale_inplace(matd_t *a, float s) +{ + assert(a != NULL); + + if (matd_is_scalar(a)) { + a->data[0] *= s; + return; + } + + for (int i = 0; i < a->nrows; i++) { + for (int j = 0; j < a->ncols; j++) { + MATD_EL(a, i, j) *= s; + } + } +} + +matd_t *matd_add(const matd_t *a, const matd_t *b) +{ + assert(a != NULL); + assert(b != NULL); + assert(a->nrows == b->nrows); + assert(a->ncols == b->ncols); + + if (matd_is_scalar(a)) + return matd_create_scalar(a->data[0] + b->data[0]); + + matd_t *m = matd_create(a->nrows, a->ncols); + + for (int i = 0; i < m->nrows; i++) { + for (int j = 0; j < m->ncols; j++) { + MATD_EL(m, i, j) = MATD_EL(a, i, j) + MATD_EL(b, i, j); + } + } + + return m; +} + +void matd_add_inplace(matd_t *a, const matd_t *b) +{ + assert(a != NULL); + assert(b != NULL); + assert(a->nrows == b->nrows); + assert(a->ncols == b->ncols); + + if (matd_is_scalar(a)) { + a->data[0] += b->data[0]; + return; + } + + for (int i = 0; i < a->nrows; i++) { + for (int j = 0; j < a->ncols; j++) { + MATD_EL(a, i, j) += MATD_EL(b, i, j); + } + } +} + + +matd_t *matd_subtract(const matd_t *a, const matd_t *b) +{ + assert(a != NULL); + assert(b != NULL); + assert(a->nrows == b->nrows); + assert(a->ncols == b->ncols); + + if (matd_is_scalar(a)) + return matd_create_scalar(a->data[0] - b->data[0]); + + matd_t *m = matd_create(a->nrows, a->ncols); + + for (int i = 0; i < m->nrows; i++) { + for (int j = 0; j < m->ncols; j++) { + MATD_EL(m, i, j) = MATD_EL(a, i, j) - MATD_EL(b, i, j); + } + } + + return m; +} + +void matd_subtract_inplace(matd_t *a, const matd_t *b) +{ + assert(a != NULL); + assert(b != NULL); + assert(a->nrows == b->nrows); + assert(a->ncols == b->ncols); + + if (matd_is_scalar(a)) { + a->data[0] -= b->data[0]; + return; + } + + for (int i = 0; i < a->nrows; i++) { + for (int j = 0; j < a->ncols; j++) { + MATD_EL(a, i, j) -= MATD_EL(b, i, j); + } + } +} + + +matd_t *matd_transpose(const matd_t *a) +{ + assert(a != NULL); + + if (matd_is_scalar(a)) + return matd_create_scalar(a->data[0]); + + matd_t *m = matd_create(a->ncols, a->nrows); + + for (int i = 0; i < a->nrows; i++) { + for (int j = 0; j < a->ncols; j++) { + MATD_EL(m, j, i) = MATD_EL(a, i, j); + } + } + return m; +} + +static +float matd_det_general(const matd_t *a) +{ + // Use LU decompositon to calculate the determinant + matd_plu_t *mlu = matd_plu(a); + matd_t *L = matd_plu_l(mlu); + matd_t *U = matd_plu_u(mlu); + + // The determinants of the L and U matrices are the products of + // their respective diagonal elements + float detL = 1; float detU = 1; + for (int i = 0; i < a->nrows; i++) { + detL *= matd_get(L, i, i); + detU *= matd_get(U, i, i); + } + + // The determinant of a can be calculated as + // epsilon*det(L)*det(U), + // where epsilon is just the sign of the corresponding permutation + // (which is +1 for an even number of permutations and is −1 + // for an uneven number of permutations). + float det = mlu->pivsign * detL * detU; + + // Cleanup + matd_plu_destroy(mlu); + matd_destroy(L); + matd_destroy(U); + + return det; +} + +float matd_det(const matd_t *a) +{ + assert(a != NULL); + assert(a->nrows == a->ncols); + + switch(a->nrows) { + case 0: + // scalar: invalid + assert(a->nrows > 0); + break; + + case 1: + // 1x1 matrix + return a->data[0]; + + case 2: + // 2x2 matrix + return a->data[0] * a->data[3] - a->data[1] * a->data[2]; + + case 3: + // 3x3 matrix + return a->data[0]*a->data[4]*a->data[8] + - a->data[0]*a->data[5]*a->data[7] + + a->data[1]*a->data[5]*a->data[6] + - a->data[1]*a->data[3]*a->data[8] + + a->data[2]*a->data[3]*a->data[7] + - a->data[2]*a->data[4]*a->data[6]; + + case 4: { + // 4x4 matrix + float m00 = MATD_EL(a,0,0), m01 = MATD_EL(a,0,1), m02 = MATD_EL(a,0,2), m03 = MATD_EL(a,0,3); + float m10 = MATD_EL(a,1,0), m11 = MATD_EL(a,1,1), m12 = MATD_EL(a,1,2), m13 = MATD_EL(a,1,3); + float m20 = MATD_EL(a,2,0), m21 = MATD_EL(a,2,1), m22 = MATD_EL(a,2,2), m23 = MATD_EL(a,2,3); + float m30 = MATD_EL(a,3,0), m31 = MATD_EL(a,3,1), m32 = MATD_EL(a,3,2), m33 = MATD_EL(a,3,3); + + return m00 * m11 * m22 * m33 - m00 * m11 * m23 * m32 - + m00 * m21 * m12 * m33 + m00 * m21 * m13 * m32 + m00 * m31 * m12 * m23 - + m00 * m31 * m13 * m22 - m10 * m01 * m22 * m33 + + m10 * m01 * m23 * m32 + m10 * m21 * m02 * m33 - + m10 * m21 * m03 * m32 - m10 * m31 * m02 * m23 + + m10 * m31 * m03 * m22 + m20 * m01 * m12 * m33 - + m20 * m01 * m13 * m32 - m20 * m11 * m02 * m33 + + m20 * m11 * m03 * m32 + m20 * m31 * m02 * m13 - + m20 * m31 * m03 * m12 - m30 * m01 * m12 * m23 + + m30 * m01 * m13 * m22 + m30 * m11 * m02 * m23 - + m30 * m11 * m03 * m22 - m30 * m21 * m02 * m13 + + m30 * m21 * m03 * m12; + } + + default: + return matd_det_general(a); + } + + assert(0); + return 0; +} + +// returns NULL if the matrix is (exactly) singular. Caller is +// otherwise responsible for knowing how to cope with badly +// conditioned matrices. +matd_t *matd_inverse(const matd_t *x) +{ + matd_t *m = NULL; + + assert(x != NULL); + assert(x->nrows == x->ncols); + + if (matd_is_scalar(x)) { + if (x->data[0] == 0) + return NULL; + + return matd_create_scalar(1.0 / x->data[0]); + } + + switch(x->nrows) { + case 1: { + float det = x->data[0]; + if (det == 0) + return NULL; + + float invdet = 1.0 / det; + + m = matd_create(x->nrows, x->nrows); + MATD_EL(m, 0, 0) = 1.0 * invdet; + return m; + } + + case 2: { + float det = x->data[0] * x->data[3] - x->data[1] * x->data[2]; + if (det == 0) + return NULL; + + float invdet = 1.0 / det; + + m = matd_create(x->nrows, x->nrows); + MATD_EL(m, 0, 0) = MATD_EL(x, 1, 1) * invdet; + MATD_EL(m, 0, 1) = - MATD_EL(x, 0, 1) * invdet; + MATD_EL(m, 1, 0) = - MATD_EL(x, 1, 0) * invdet; + MATD_EL(m, 1, 1) = MATD_EL(x, 0, 0) * invdet; + return m; + } + + default: { + matd_plu_t *plu = matd_plu(x); + + matd_t *inv = NULL; + if (!plu->singular) { + matd_t *ident = matd_identity(x->nrows); + inv = matd_plu_solve(plu, ident); + matd_destroy(ident); + } + + matd_plu_destroy(plu); + + return inv; + } + } + + return NULL; // unreachable +} + + + +// TODO Optimization: Some operations we could perform in-place, +// saving some memory allocation work. E.g., ADD, SUBTRACT. Just need +// to make sure that we don't do an in-place modification on a matrix +// that was an input argument! + +// handle right-associative operators, greedily consuming them. These +// include transpose and inverse. This is called by the main recursion +// method. +static inline matd_t *matd_op_gobble_right(const char *expr, int *pos, matd_t *acc, matd_t **garb, int *garbpos) +{ + while (expr[*pos] != 0) { + + switch (expr[*pos]) { + + case '\'': { + assert(acc != NULL); // either a syntax error or a math op failed, producing null + matd_t *res = matd_transpose(acc); + garb[*garbpos] = res; + (*garbpos)++; + acc = res; + + (*pos)++; + break; + } + + // handle inverse ^-1. No other exponents are allowed. + case '^': { + assert(acc != NULL); + assert(expr[*pos+1] == '-'); + assert(expr[*pos+2] == '1'); + + matd_t *res = matd_inverse(acc); + garb[*garbpos] = res; + (*garbpos)++; + acc = res; + + (*pos)+=3; + break; + } + + default: + return acc; + } + } + + return acc; +} + +// @garb, garbpos A list of every matrix allocated during evaluation... used to assist cleanup. +// @oneterm: we should return at the end of this term (i.e., stop at a PLUS, MINUS, LPAREN). +static matd_t *matd_op_recurse(const char *expr, int *pos, matd_t *acc, matd_t **args, int *argpos, + matd_t **garb, int *garbpos, int oneterm) +{ + while (expr[*pos] != 0) { + + switch (expr[*pos]) { + + case '(': { + if (oneterm && acc != NULL) + return acc; + (*pos)++; + matd_t *rhs = matd_op_recurse(expr, pos, NULL, args, argpos, garb, garbpos, 0); + rhs = matd_op_gobble_right(expr, pos, rhs, garb, garbpos); + + if (acc == NULL) { + acc = rhs; + } else { + matd_t *res = matd_multiply(acc, rhs); + garb[*garbpos] = res; + (*garbpos)++; + acc = res; + } + + break; + } + + case ')': { + if (oneterm) + return acc; + + (*pos)++; + return acc; + } + + case '*': { + (*pos)++; + + matd_t *rhs = matd_op_recurse(expr, pos, NULL, args, argpos, garb, garbpos, 1); + rhs = matd_op_gobble_right(expr, pos, rhs, garb, garbpos); + + if (acc == NULL) { + acc = rhs; + } else { + matd_t *res = matd_multiply(acc, rhs); + garb[*garbpos] = res; + (*garbpos)++; + acc = res; + } + + break; + } + + case 'F': { + matd_t *rhs = args[*argpos]; + garb[*garbpos] = rhs; + (*garbpos)++; + + (*pos)++; + (*argpos)++; + + rhs = matd_op_gobble_right(expr, pos, rhs, garb, garbpos); + + if (acc == NULL) { + acc = rhs; + } else { + matd_t *res = matd_multiply(acc, rhs); + garb[*garbpos] = res; + (*garbpos)++; + acc = res; + } + + break; + } + + case 'M': { + matd_t *rhs = args[*argpos]; + + (*pos)++; + (*argpos)++; + + rhs = matd_op_gobble_right(expr, pos, rhs, garb, garbpos); + + if (acc == NULL) { + acc = rhs; + } else { + matd_t *res = matd_multiply(acc, rhs); + garb[*garbpos] = res; + (*garbpos)++; + acc = res; + } + + break; + } + +/* + case 'D': { + int rows = expr[*pos+1]-'0'; + int cols = expr[*pos+2]-'0'; + + matd_t *rhs = matd_create(rows, cols); + + break; + } +*/ + // a constant (SCALAR) defined inline. Treat just like M, creating a matd_t on the fly. +// case '0': +// case '1': +// case '2': +// case '3': +// case '4': +// case '5': +// case '6': +// case '7': +// case '8': +// case '9': +// case '.': { +// const char *start = &expr[*pos]; +// char *end; +// float s = strtod(start, &end); +// (*pos) += (end - start); +// matd_t *rhs = matd_create_scalar(s); +// garb[*garbpos] = rhs; +// (*garbpos)++; + +// rhs = matd_op_gobble_right(expr, pos, rhs, garb, garbpos); + +// if (acc == NULL) { +// acc = rhs; +// } else { +// matd_t *res = matd_multiply(acc, rhs); +// garb[*garbpos] = res; +// (*garbpos)++; +// acc = res; +// } + +// break; +// } + + case '+': { + if (oneterm && acc != NULL) + return acc; + + // don't support unary plus + assert(acc != NULL); + (*pos)++; + matd_t *rhs = matd_op_recurse(expr, pos, NULL, args, argpos, garb, garbpos, 1); + rhs = matd_op_gobble_right(expr, pos, rhs, garb, garbpos); + + matd_t *res = matd_add(acc, rhs); + + garb[*garbpos] = res; + (*garbpos)++; + acc = res; + break; + } + + case '-': { + if (oneterm && acc != NULL) + return acc; + + if (acc == NULL) { + // unary minus + (*pos)++; + matd_t *rhs = matd_op_recurse(expr, pos, NULL, args, argpos, garb, garbpos, 1); + rhs = matd_op_gobble_right(expr, pos, rhs, garb, garbpos); + + matd_t *res = matd_scale(rhs, -1); + garb[*garbpos] = res; + (*garbpos)++; + acc = res; + } else { + // subtract + (*pos)++; + matd_t *rhs = matd_op_recurse(expr, pos, NULL, args, argpos, garb, garbpos, 1); + rhs = matd_op_gobble_right(expr, pos, rhs, garb, garbpos); + + matd_t *res = matd_subtract(acc, rhs); + garb[*garbpos] = res; + (*garbpos)++; + acc = res; + } + break; + } + + case ' ': { + // nothing to do. spaces are meaningless. + (*pos)++; + break; + } + + default: { + fprintf(stderr, "matd_op(): Unknown character: '%c'\n", expr[*pos]); + assert(expr[*pos] != expr[*pos]); + } + } + } + return acc; +} + +// always returns a new matrix. +matd_t *matd_op(const char *expr, ...) +{ + int nargs = 0; + int exprlen = 0; + + assert(expr != NULL); + + for (const char *p = expr; *p != 0; p++) { + if (*p == 'M' || *p == 'F') + nargs++; + exprlen++; + } + + assert(nargs > 0); + + if (!exprlen) // expr = "" + return NULL; + + va_list ap; + va_start(ap, expr); + + matd_t *args[nargs]; + for (int i = 0; i < nargs; i++) { + args[i] = va_arg(ap, matd_t*); + // XXX: sanity check argument; emit warning/error if args[i] + // doesn't look like a matd_t*. + } + + va_end(ap); + + int pos = 0; + int argpos = 0; + int garbpos = 0; + + matd_t *garb[2*exprlen]; // can't create more than 2 new result per character + // one result, and possibly one argument to free + + matd_t *res = matd_op_recurse(expr, &pos, NULL, args, &argpos, garb, &garbpos, 0); + + // 'res' may need to be freed as part of garbage collection (i.e. expr = "F") + matd_t *res_copy = (res ? matd_copy(res) : NULL); + + for (int i = 0; i < garbpos; i++) { + matd_destroy(garb[i]); + } + + return res_copy; +} + +float matd_vec_mag(const matd_t *a) +{ + assert(a != NULL); + assert(matd_is_vector(a)); + + float mag = 0.0; + int len = a->nrows*a->ncols; + for (int i = 0; i < len; i++) + mag += sq(a->data[i]); + return sqrt(mag); +} + +float matd_vec_dist(const matd_t *a, const matd_t *b) +{ + assert(a != NULL); + assert(b != NULL); + assert(matd_is_vector(a) && matd_is_vector(b)); + assert(a->nrows*a->ncols == b->nrows*b->ncols); + + int lena = a->nrows*a->ncols; + return matd_vec_dist_n(a, b, lena); +} + +float matd_vec_dist_n(const matd_t *a, const matd_t *b, int n) +{ + assert(a != NULL); + assert(b != NULL); + assert(matd_is_vector(a) && matd_is_vector(b)); + + int lena = a->nrows*a->ncols; + int lenb = b->nrows*b->ncols; + + assert(n <= lena && n <= lenb); + + float mag = 0.0; + for (int i = 0; i < n; i++) + mag += sq(a->data[i] - b->data[i]); + return sqrt(mag); +} + +// find the index of the off-diagonal element with the largest mag +static inline int max_idx(const matd_t *A, int row, int maxcol) +{ + int maxi = 0; + float maxv = -1; + + for (int i = 0; i < maxcol; i++) { + if (i == row) + continue; + float v = fabs(MATD_EL(A, row, i)); + if (v > maxv) { + maxi = i; + maxv = v; + } + } + + return maxi; +} + +float matd_vec_dot_product(const matd_t *a, const matd_t *b) +{ + assert(a != NULL); + assert(b != NULL); + assert(matd_is_vector(a) && matd_is_vector(b)); + int adim = a->ncols*a->nrows; + int bdim = b->ncols*b->nrows; + assert(adim == bdim); + + float acc = 0; + for (int i = 0; i < adim; i++) { + acc += a->data[i] * b->data[i]; + } + return acc; +} + + +matd_t *matd_vec_normalize(const matd_t *a) +{ + assert(a != NULL); + assert(matd_is_vector(a)); + + float mag = matd_vec_mag(a); + assert(mag > 0); + + matd_t *b = matd_create(a->nrows, a->ncols); + + int len = a->nrows*a->ncols; + for(int i = 0; i < len; i++) + b->data[i] = a->data[i] / mag; + + return b; +} + +matd_t *matd_crossproduct(const matd_t *a, const matd_t *b) +{ // only defined for vecs (col or row) of length 3 + assert(a != NULL); + assert(b != NULL); + assert(matd_is_vector_len(a, 3) && matd_is_vector_len(b, 3)); + + matd_t * r = matd_create(a->nrows, a->ncols); + + r->data[0] = a->data[1] * b->data[2] - a->data[2] * b->data[1]; + r->data[1] = a->data[2] * b->data[0] - a->data[0] * b->data[2]; + r->data[2] = a->data[0] * b->data[1] - a->data[1] * b->data[0]; + + return r; +} + +TYPE matd_err_inf(const matd_t *a, const matd_t *b) +{ + assert(a->nrows == b->nrows); + assert(a->ncols == b->ncols); + + TYPE maxf = 0; + + for (int i = 0; i < a->nrows; i++) { + for (int j = 0; j < a->ncols; j++) { + TYPE av = MATD_EL(a, i, j); + TYPE bv = MATD_EL(b, i, j); + + TYPE err = fabs(av - bv); + maxf = fmax(maxf, err); + } + } + + return maxf; +} + +// Computes an SVD for square or tall matrices. This code doesn't work +// for wide matrices, because the bidiagonalization results in one +// non-zero element too far to the right for us to rotate away. +// +// Caller is responsible for destroying U, S, and V. +static matd_svd_t matd_svd_tall(matd_t *A, int flags) +{ + matd_t *B = matd_copy(A); + + // Apply householder reflections on each side to reduce A to + // bidiagonal form. Specifically: + // + // A = LS*B*RS' + // + // Where B is bidiagonal, and LS/RS are unitary. + // + // Why are we doing this? Some sort of transformation is necessary + // to reduce the matrix's nz elements to a square region. QR could + // work too. We need nzs confined to a square region so that the + // subsequent iterative process, which is based on rotations, can + // work. (To zero out a term at (i,j), our rotations will also + // affect (j,i). + // + // We prefer bidiagonalization over QR because it gets us "closer" + // to the SVD, which should mean fewer iterations. + + // LS: cumulative left-handed transformations + matd_t *LS = matd_identity(A->nrows); + + // RS: cumulative right-handed transformations. + matd_t *RS = matd_identity(A->ncols); + + for (int hhidx = 0; hhidx < A->nrows; hhidx++) { + + if (hhidx < A->ncols) { + // We construct the normal of the reflection plane: let u + // be the vector to reflect, x =[ M 0 0 0 ] the target + // location for u (u') after reflection (with M = ||u||). + // + // The normal vector is then n = (u - x), but since we + // could equally have the target location be x = [-M 0 0 0 + // ], we could use n = (u + x). + // + // We then normalize n. To ensure a reasonable magnitude, + // we select the sign of M so as to maximize the magnitude + // of the first element of (x +/- M). (Otherwise, we could + // end up with a divide-by-zero if u[0] and M cancel.) + // + // The householder reflection matrix is then H=(I - nn'), and + // u' = Hu. + // + // + int vlen = A->nrows - hhidx; + + float v[vlen]; + + float mag2 = 0; + for (int i = 0; i < vlen; i++) { + v[i] = MATD_EL(B, hhidx+i, hhidx); + mag2 += v[i]*v[i]; + } + + float oldv0 = v[0]; + if (oldv0 < 0) + v[0] -= sqrt(mag2); + else + v[0] += sqrt(mag2); + + mag2 += -oldv0*oldv0 + v[0]*v[0]; + + // normalize v + float mag = sqrt(mag2); + + // this case arises with matrices of all zeros, for example. + if (mag == 0) + continue; + + for (int i = 0; i < vlen; i++) + v[i] /= mag; + + // Q = I - 2vv' + //matd_t *Q = matd_identity(A->nrows); + //for (int i = 0; i < vlen; i++) + // for (int j = 0; j < vlen; j++) + // MATD_EL(Q, i+hhidx, j+hhidx) -= 2*v[i]*v[j]; + + + // LS = matd_op("F*M", LS, Q); + // Implementation: take each row of LS, compute dot product with n, + // subtract n (scaled by dot product) from it. + for (int i = 0; i < LS->nrows; i++) { + float dot = 0; + for (int j = 0; j < vlen; j++) + dot += MATD_EL(LS, i, hhidx+j) * v[j]; + for (int j = 0; j < vlen; j++) + MATD_EL(LS, i, hhidx+j) -= 2*dot*v[j]; + } + + // B = matd_op("M*F", Q, B); // should be Q', but Q is symmetric. + for (int i = 0; i < B->ncols; i++) { + float dot = 0; + for (int j = 0; j < vlen; j++) + dot += MATD_EL(B, hhidx+j, i) * v[j]; + for (int j = 0; j < vlen; j++) + MATD_EL(B, hhidx+j, i) -= 2*dot*v[j]; + } + } + + if (hhidx+2 < A->ncols) { + int vlen = A->ncols - hhidx - 1; + + float v[vlen]; + + float mag2 = 0; + for (int i = 0; i < vlen; i++) { + v[i] = MATD_EL(B, hhidx, hhidx+i+1); + mag2 += v[i]*v[i]; + } + + float oldv0 = v[0]; + if (oldv0 < 0) + v[0] -= sqrt(mag2); + else + v[0] += sqrt(mag2); + + mag2 += -oldv0*oldv0 + v[0]*v[0]; + + // compute magnitude of ([1 0 0..]+v) + float mag = sqrt(mag2); + + // this case can occur when the vectors are already perpendicular + if (mag == 0) + continue; + + for (int i = 0; i < vlen; i++) + v[i] /= mag; + + // TODO: optimize these multiplications + // matd_t *Q = matd_identity(A->ncols); + // for (int i = 0; i < vlen; i++) + // for (int j = 0; j < vlen; j++) + // MATD_EL(Q, i+1+hhidx, j+1+hhidx) -= 2*v[i]*v[j]; + + // RS = matd_op("F*M", RS, Q); + for (int i = 0; i < RS->nrows; i++) { + float dot = 0; + for (int j = 0; j < vlen; j++) + dot += MATD_EL(RS, i, hhidx+1+j) * v[j]; + for (int j = 0; j < vlen; j++) + MATD_EL(RS, i, hhidx+1+j) -= 2*dot*v[j]; + } + + // B = matd_op("F*M", B, Q); // should be Q', but Q is symmetric. + for (int i = 0; i < B->nrows; i++) { + float dot = 0; + for (int j = 0; j < vlen; j++) + dot += MATD_EL(B, i, hhidx+1+j) * v[j]; + for (int j = 0; j < vlen; j++) + MATD_EL(B, i, hhidx+1+j) -= 2*dot*v[j]; + } + } + } + + // maxiters used to be smaller to prevent us from looping forever, + // but this doesn't seem to happen any more with our more stable + // svd22 implementation. + int maxiters = 1UL << 5; // 1UL << 30; + assert(maxiters > 0); // reassure clang + int iter; + + float maxv; // maximum non-zero value being reduced this iteration + + float tol = 1E-5; // 1E-10; + + // which method will we use to find the largest off-diagonal + // element of B? + const int find_max_method = 1; //(B->ncols < 6) ? 2 : 1; + + // for each of the first B->ncols rows, which index has the + // maximum absolute value? (used by method 1) + int maxrowidx[B->ncols]; + int lastmaxi, lastmaxj; + + if (find_max_method == 1) { + for (int i = 2; i < B->ncols; i++) + maxrowidx[i] = max_idx(B, i, B->ncols); + + // note that we started the array at 2. That's because by setting + // these values below, we'll recompute first two entries on the + // first iteration! + lastmaxi = 0, lastmaxj = 1; + } + + for (iter = 0; iter < maxiters; iter++) { + + // No diagonalization required for 0x0 and 1x1 matrices. + if (B->ncols < 2) + break; + + // find the largest off-diagonal element of B, and put its + // coordinates in maxi, maxj. + int maxi, maxj; + + if (find_max_method == 1) { + // method 1 is the "smarter" method which does at least + // 4*ncols work. More work might be needed (up to + // ncols*ncols), depending on data. Thus, this might be a + // bit slower than the default method for very small + // matrices. + maxi = -1; + maxv = -1; + + // every iteration, we must deal with the fact that rows + // and columns lastmaxi and lastmaxj have been + // modified. Update maxrowidx accordingly. + + // now, EVERY row also had columns lastmaxi and lastmaxj modified. + for (int rowi = 0; rowi < B->ncols; rowi++) { + + // the magnitude of the largest off-diagonal element + // in this row. + float thismaxv; + + // row 'lastmaxi' and 'lastmaxj' have been completely + // changed. compute from scratch. + if (rowi == lastmaxi || rowi == lastmaxj) { + maxrowidx[rowi] = max_idx(B, rowi, B->ncols); + thismaxv = fabs(MATD_EL(B, rowi, maxrowidx[rowi])); + goto endrowi; + } + + // our maximum entry was just modified. We don't know + // if it went up or down, and so we don't know if it + // is still the maximum. We have to update from + // scratch. + if (maxrowidx[rowi] == lastmaxi || maxrowidx[rowi] == lastmaxj) { + maxrowidx[rowi] = max_idx(B, rowi, B->ncols); + thismaxv = fabs(MATD_EL(B, rowi, maxrowidx[rowi])); + goto endrowi; + } + + // This row is unchanged, except for columns + // 'lastmaxi' and 'lastmaxj', and those columns were + // not previously the largest entry... just check to + // see if they are now the maximum entry in their + // row. (Remembering to consider off-diagonal entries + // only!) + thismaxv = fabs(MATD_EL(B, rowi, maxrowidx[rowi])); + + // check column lastmaxi. Is it now the maximum? + if (lastmaxi != rowi) { + float v = fabs(MATD_EL(B, rowi, lastmaxi)); + if (v > thismaxv) { + thismaxv = v; + maxrowidx[rowi] = lastmaxi; + } + } + + // check column lastmaxj + if (lastmaxj != rowi) { + float v = fabs(MATD_EL(B, rowi, lastmaxj)); + if (v > thismaxv) { + thismaxv = v; + maxrowidx[rowi] = lastmaxj; + } + } + + // does this row have the largest value we've seen so far? + endrowi: + if (thismaxv > maxv) { + maxv = thismaxv; + maxi = rowi; + } + } + + assert(maxi >= 0); + maxj = maxrowidx[maxi]; + + // save these for the next iteration. + lastmaxi = maxi; + lastmaxj = maxj; + + if (maxv < tol) + break; + + } else if (find_max_method == 2) { + // brute-force (reference) version. + maxv = -1; + + // only search top "square" portion + for (int i = 0; i < B->ncols; i++) { + for (int j = 0; j < B->ncols; j++) { + if (i == j) + continue; + + float v = fabs(MATD_EL(B, i, j)); + + if (v > maxv) { + maxi = i; + maxj = j; + maxv = v; + } + } + } + + // termination condition. + if (maxv < tol) + break; + } else { + assert(0); + } + +// printf(">>> %5d %3d, %3d %15g\n", maxi, maxj, iter, maxv); + + // Now, solve the 2x2 SVD problem for the matrix + // [ A0 A1 ] + // [ A2 A3 ] + float A0 = MATD_EL(B, maxi, maxi); + float A1 = MATD_EL(B, maxi, maxj); + float A2 = MATD_EL(B, maxj, maxi); + float A3 = MATD_EL(B, maxj, maxj); + + if (1) { + float AQ[4]; + AQ[0] = A0; + AQ[1] = A1; + AQ[2] = A2; + AQ[3] = A3; + + float U[4], S[2], V[4]; + svd22(AQ, U, S, V); + +/* Reference (slow) implementation... + + // LS = LS * ROT(theta) = LS * QL + matd_t *QL = matd_identity(A->nrows); + MATD_EL(QL, maxi, maxi) = U[0]; + MATD_EL(QL, maxi, maxj) = U[1]; + MATD_EL(QL, maxj, maxi) = U[2]; + MATD_EL(QL, maxj, maxj) = U[3]; + + matd_t *QR = matd_identity(A->ncols); + MATD_EL(QR, maxi, maxi) = V[0]; + MATD_EL(QR, maxi, maxj) = V[1]; + MATD_EL(QR, maxj, maxi) = V[2]; + MATD_EL(QR, maxj, maxj) = V[3]; + + LS = matd_op("F*M", LS, QL); + RS = matd_op("F*M", RS, QR); // remember we'll transpose RS. + B = matd_op("M'*F*M", QL, B, QR); + + matd_destroy(QL); + matd_destroy(QR); +*/ + + // LS = matd_op("F*M", LS, QL); + for (int i = 0; i < LS->nrows; i++) { + float vi = MATD_EL(LS, i, maxi); + float vj = MATD_EL(LS, i, maxj); + + MATD_EL(LS, i, maxi) = U[0]*vi + U[2]*vj; + MATD_EL(LS, i, maxj) = U[1]*vi + U[3]*vj; + } + + // RS = matd_op("F*M", RS, QR); // remember we'll transpose RS. + for (int i = 0; i < RS->nrows; i++) { + float vi = MATD_EL(RS, i, maxi); + float vj = MATD_EL(RS, i, maxj); + + MATD_EL(RS, i, maxi) = V[0]*vi + V[2]*vj; + MATD_EL(RS, i, maxj) = V[1]*vi + V[3]*vj; + } + + // B = matd_op("M'*F*M", QL, B, QR); + // The QL matrix mixes rows of B. + for (int i = 0; i < B->ncols; i++) { + float vi = MATD_EL(B, maxi, i); + float vj = MATD_EL(B, maxj, i); + + MATD_EL(B, maxi, i) = U[0]*vi + U[2]*vj; + MATD_EL(B, maxj, i) = U[1]*vi + U[3]*vj; + } + + // The QR matrix mixes columns of B. + for (int i = 0; i < B->nrows; i++) { + float vi = MATD_EL(B, i, maxi); + float vj = MATD_EL(B, i, maxj); + + MATD_EL(B, i, maxi) = V[0]*vi + V[2]*vj; + MATD_EL(B, i, maxj) = V[1]*vi + V[3]*vj; + } + } + } + + if (!(flags & MATD_SVD_NO_WARNINGS) && iter == maxiters) { + printf("WARNING: maximum iters (maximum = %d, matrix %d x %d, max=%.15f)\n", + iter, A->nrows, A->ncols, (double) maxv); + +// matd_print(A, "%15f"); + } + + // them all positive by flipping the corresponding columns of + // U/LS. + int idxs[A->ncols]; + float vals[A->ncols]; + for (int i = 0; i < A->ncols; i++) { + idxs[i] = i; + vals[i] = MATD_EL(B, i, i); + } + + // A bubble sort. Seriously. + int changed; + do { + changed = 0; + + for (int i = 0; i + 1 < A->ncols; i++) { + if (fabs(vals[i+1]) > fabs(vals[i])) { + int tmpi = idxs[i]; + idxs[i] = idxs[i+1]; + idxs[i+1] = tmpi; + + float tmpv = vals[i]; + vals[i] = vals[i+1]; + vals[i+1] = tmpv; + + changed = 1; + } + } + } while (changed); + + matd_t *LP = matd_identity(A->nrows); + matd_t *RP = matd_identity(A->ncols); + + for (int i = 0; i < A->ncols; i++) { + MATD_EL(LP, idxs[i], idxs[i]) = 0; // undo the identity above + MATD_EL(RP, idxs[i], idxs[i]) = 0; + + MATD_EL(LP, idxs[i], i) = vals[i] < 0 ? -1 : 1; + MATD_EL(RP, idxs[i], i) = 1; //vals[i] < 0 ? -1 : 1; + } + + // we've factored: + // LP*(something)*RP' + + // solve for (something) + B = matd_op("M'*F*M", LP, B, RP); + + // update LS and RS, remembering that RS will be transposed. + LS = matd_op("F*M", LS, LP); + RS = matd_op("F*M", RS, RP); + + matd_destroy(LP); + matd_destroy(RP); + + matd_svd_t res; + memset(&res, 0, sizeof(res)); + + // make B exactly diagonal + + for (int i = 0; i < B->nrows; i++) { + for (int j = 0; j < B->ncols; j++) { + if (i != j) + MATD_EL(B, i, j) = 0; + } + } + + res.U = LS; + res.S = B; + res.V = RS; + + return res; +} + +matd_svd_t matd_svd(matd_t *A) +{ + return matd_svd_flags(A, 0); +} + +matd_svd_t matd_svd_flags(matd_t *A, int flags) +{ + matd_svd_t res; + + if (A->ncols <= A->nrows) { + res = matd_svd_tall(A, flags); + } else { + matd_t *At = matd_transpose(A); + + // A =U S V' + // A'=V S' U' + + matd_svd_t tmp = matd_svd_tall(At, flags); + + memset(&res, 0, sizeof(res)); + res.U = tmp.V; //matd_transpose(tmp.V); + res.S = matd_transpose(tmp.S); + res.V = tmp.U; //matd_transpose(tmp.U); + + matd_destroy(tmp.S); + matd_destroy(At); + } + +/* + matd_t *check = matd_op("M*M*M'-M", res.U, res.S, res.V, A); + float maxerr = 0; + + for (int i = 0; i < check->nrows; i++) + for (int j = 0; j < check->ncols; j++) + maxerr = fmax(maxerr, fabs(MATD_EL(check, i, j))); + + matd_destroy(check); + + if (maxerr > 1e-7) { + printf("bad maxerr: %15f\n", maxerr); + } + + if (maxerr > 1e-5) { + printf("bad maxerr: %15f\n", maxerr); + matd_print(A, "%15f"); + assert(0); + } + +*/ + return res; +} + + +matd_plu_t *matd_plu(const matd_t *a) +{ + unsigned int *piv = calloc(a->nrows, sizeof(unsigned int)); + int pivsign = 1; + matd_t *lu = matd_copy(a); + + // only for square matrices. + assert(a->nrows == a->ncols); + + matd_plu_t *mlu = calloc(1, sizeof(matd_plu_t)); + + for (int i = 0; i < a->nrows; i++) + piv[i] = i; + + for (int j = 0; j < a->ncols; j++) { + for (int i = 0; i < a->nrows; i++) { + int kmax = i < j ? i : j; // min(i,j) + + // compute dot product of row i with column j (up through element kmax) + float acc = 0; + for (int k = 0; k < kmax; k++) + acc += MATD_EL(lu, i, k) * MATD_EL(lu, k, j); + + MATD_EL(lu, i, j) -= acc; + } + + // find pivot and exchange if necessary. + int p = j; + if (1) { + for (int i = j+1; i < lu->nrows; i++) { + if (fabs(MATD_EL(lu,i,j)) > fabs(MATD_EL(lu, p, j))) { + p = i; + } + } + } + + // swap rows p and j? + if (p != j) { + TYPE tmp[lu->ncols]; + memcpy(tmp, &MATD_EL(lu, p, 0), sizeof(TYPE) * lu->ncols); + memcpy(&MATD_EL(lu, p, 0), &MATD_EL(lu, j, 0), sizeof(TYPE) * lu->ncols); + memcpy(&MATD_EL(lu, j, 0), tmp, sizeof(TYPE) * lu->ncols); + int k = piv[p]; + piv[p] = piv[j]; + piv[j] = k; + pivsign = -pivsign; + } + + float LUjj = MATD_EL(lu, j, j); + + // If our pivot is very small (which means the matrix is + // singular or nearly singular), replace with a new pivot of the + // right sign. + if (fabs(LUjj) < MATD_EPS) { +/* + if (LUjj < 0) + LUjj = -MATD_EPS; + else + LUjj = MATD_EPS; + + MATD_EL(lu, j, j) = LUjj; +*/ + mlu->singular = 1; + } + + if (j < lu->ncols && j < lu->nrows && LUjj != 0) { + LUjj = 1.0 / LUjj; + for (int i = j+1; i < lu->nrows; i++) + MATD_EL(lu, i, j) *= LUjj; + } + } + + mlu->lu = lu; + mlu->piv = piv; + mlu->pivsign = pivsign; + + return mlu; +} + +void matd_plu_destroy(matd_plu_t *mlu) +{ + matd_destroy(mlu->lu); + free(mlu->piv); + memset(mlu, 0, sizeof(matd_plu_t)); + free(mlu); +} + +float matd_plu_det(const matd_plu_t *mlu) +{ + matd_t *lu = mlu->lu; + float det = mlu->pivsign; + + if (lu->nrows == lu->ncols) { + for (int i = 0; i < lu->ncols; i++) + det *= MATD_EL(lu, i, i); + } + + return det; +} + +matd_t *matd_plu_p(const matd_plu_t *mlu) +{ + matd_t *lu = mlu->lu; + matd_t *P = matd_create(lu->nrows, lu->nrows); + + for (int i = 0; i < lu->nrows; i++) { + MATD_EL(P, mlu->piv[i], i) = 1; + } + + return P; +} + +matd_t *matd_plu_l(const matd_plu_t *mlu) +{ + matd_t *lu = mlu->lu; + + matd_t *L = matd_create(lu->nrows, lu->ncols); + for (int i = 0; i < lu->nrows; i++) { + MATD_EL(L, i, i) = 1; + + for (int j = 0; j < i; j++) { + MATD_EL(L, i, j) = MATD_EL(lu, i, j); + } + } + + return L; +} + +matd_t *matd_plu_u(const matd_plu_t *mlu) +{ + matd_t *lu = mlu->lu; + + matd_t *U = matd_create(lu->ncols, lu->ncols); + for (int i = 0; i < lu->ncols; i++) { + for (int j = 0; j < lu->ncols; j++) { + if (i <= j) + MATD_EL(U, i, j) = MATD_EL(lu, i, j); + } + } + + return U; +} + +// PLU = A +// Ax = B +// PLUx = B +// LUx = P'B +matd_t *matd_plu_solve(const matd_plu_t *mlu, const matd_t *b) +{ + matd_t *x = matd_copy(b); + + // permute right hand side + for (int i = 0; i < mlu->lu->nrows; i++) + memcpy(&MATD_EL(x, i, 0), &MATD_EL(b, mlu->piv[i], 0), sizeof(TYPE) * b->ncols); + + // solve Ly = b + for (int k = 0; k < mlu->lu->nrows; k++) { + for (int i = k+1; i < mlu->lu->nrows; i++) { + float LUik = -MATD_EL(mlu->lu, i, k); + for (int t = 0; t < b->ncols; t++) + MATD_EL(x, i, t) += MATD_EL(x, k, t) * LUik; + } + } + + // solve Ux = y + for (int k = mlu->lu->ncols-1; k >= 0; k--) { + float LUkk = 1.0 / MATD_EL(mlu->lu, k, k); + for (int t = 0; t < b->ncols; t++) + MATD_EL(x, k, t) *= LUkk; + + for (int i = 0; i < k; i++) { + float LUik = -MATD_EL(mlu->lu, i, k); + for (int t = 0; t < b->ncols; t++) + MATD_EL(x, i, t) += MATD_EL(x, k, t) *LUik; + } + } + + return x; +} + +matd_t *matd_solve(matd_t *A, matd_t *b) +{ + matd_plu_t *mlu = matd_plu(A); + matd_t *x = matd_plu_solve(mlu, b); + + matd_plu_destroy(mlu); + return x; +} + +#if 0 + +static int randi() +{ + int v = random()&31; + v -= 15; + return v; +} + +static float randf() +{ + float v = 1.0 *random() / RAND_MAX; + return 2*v - 1; +} + +int main(int argc, char *argv[]) +{ + if (1) { + int maxdim = 16; + matd_t *A = matd_create(maxdim, maxdim); + + for (int iter = 0; 1; iter++) { + srand(iter); + + if (iter % 1000 == 0) + printf("%d\n", iter); + + int m = 1 + (random()%(maxdim-1)); + int n = 1 + (random()%(maxdim-1)); + + for (int i = 0; i < m*n; i++) + A->data[i] = randi(); + + A->nrows = m; + A->ncols = n; + +// printf("%d %d ", m, n); + matd_svd_t svd = matd_svd(A); + matd_destroy(svd.U); + matd_destroy(svd.S); + matd_destroy(svd.V); + + } + +/* matd_t *A = matd_create_data(2, 5, (float[]) { 1, 5, 2, 6, + 3, 3, 0, 7, + 1, 1, 0, -2, + 4, 0, 9, 9, 2, 6, 1, 3, 2, 5, 5, 4, -1, 2, 5, 9, 8, 2 }); + + matd_svd(A); +*/ + return 0; + } + + + struct svd22 s; + + srand(0); + + matd_t *A = matd_create(2, 2); + MATD_EL(A,0,0) = 4; + MATD_EL(A,0,1) = 7; + MATD_EL(A,1,0) = 2; + MATD_EL(A,1,1) = 6; + + matd_t *U = matd_create(2, 2); + matd_t *V = matd_create(2, 2); + matd_t *S = matd_create(2, 2); + + for (int iter = 0; 1; iter++) { + if (iter % 100000 == 0) + printf("%d\n", iter); + + MATD_EL(A,0,0) = randf(); + MATD_EL(A,0,1) = randf(); + MATD_EL(A,1,0) = randf(); + MATD_EL(A,1,1) = randf(); + + matd_svd22_impl(A->data, &s); + + memcpy(U->data, s.U, 4*sizeof(float)); + memcpy(V->data, s.V, 4*sizeof(float)); + MATD_EL(S,0,0) = s.S[0]; + MATD_EL(S,1,1) = s.S[1]; + + assert(s.S[0] >= s.S[1]); + assert(s.S[0] >= 0); + assert(s.S[1] >= 0); + if (s.S[0] == 0) { +// printf("*"); fflush(NULL); +// printf("%15f %15f %15f %15f\n", MATD_EL(A,0,0), MATD_EL(A,0,1), MATD_EL(A,1,0), MATD_EL(A,1,1)); + } + if (s.S[1] == 0) { +// printf("#"); fflush(NULL); + } + + matd_t *USV = matd_op("M*M*M'", U, S, V); + + float maxerr = 0; + for (int i = 0; i < 4; i++) + maxerr = fmax(maxerr, fabs(USV->data[i] - A->data[i])); + + if (0) { + printf("------------------------------------\n"); + printf("A:\n"); + matd_print(A, "%15f"); + printf("\nUSV':\n"); + matd_print(USV, "%15f"); + printf("maxerr: %.15f\n", maxerr); + printf("\n\n"); + } + + matd_destroy(USV); + + assert(maxerr < 0.00001); + } +} + +#endif + +// XXX NGV Cholesky +/*static float *matd_cholesky_raw(float *A, int n) + { + float *L = (float*)calloc(n * n, sizeof(float)); + + for (int i = 0; i < n; i++) { + for (int j = 0; j < (i+1); j++) { + float s = 0; + for (int k = 0; k < j; k++) + s += L[i * n + k] * L[j * n + k]; + L[i * n + j] = (i == j) ? + sqrt(A[i * n + i] - s) : + (1.0 / L[j * n + j] * (A[i * n + j] - s)); + } + } + + return L; + } + + matd_t *matd_cholesky(const matd_t *A) + { + assert(A->nrows == A->ncols); + float *L_data = matd_cholesky_raw(A->data, A->nrows); + matd_t *L = matd_create_data(A->nrows, A->ncols, L_data); + free(L_data); + return L; + }*/ + +// NOTE: The below implementation of Cholesky is different from the one +// used in NGV. +matd_chol_t *matd_chol(matd_t *A) +{ + assert(A->nrows == A->ncols); + int N = A->nrows; + + // make upper right + matd_t *U = matd_copy(A); + + // don't actually need to clear lower-left... we won't touch it. +/* for (int i = 0; i < U->nrows; i++) { + for (int j = 0; j < i; j++) { +// assert(MATD_EL(U, i, j) == MATD_EL(U, j, i)); +MATD_EL(U, i, j) = 0; +} +} +*/ + int is_spd = 1; // (A->nrows == A->ncols); + + for (int i = 0; i < N; i++) { + float d = MATD_EL(U, i, i); + is_spd &= (d > 0); + + if (d < MATD_EPS) + d = MATD_EPS; + d = 1.0 / sqrt(d); + + for (int j = i; j < N; j++) + MATD_EL(U, i, j) *= d; + + for (int j = i+1; j < N; j++) { + float s = MATD_EL(U, i, j); + + if (s == 0) + continue; + + for (int k = j; k < N; k++) { + MATD_EL(U, j, k) -= MATD_EL(U, i, k)*s; + } + } + } + + matd_chol_t *chol = calloc(1, sizeof(matd_chol_t)); + chol->is_spd = is_spd; + chol->u = U; + return chol; +} + +void matd_chol_destroy(matd_chol_t *chol) +{ + matd_destroy(chol->u); + free(chol); +} + +// Solve: (U')x = b, U is upper triangular +void matd_ltransposetriangle_solve(matd_t *u, const TYPE *b, TYPE *x) +{ + int n = u->ncols; + memcpy(x, b, n*sizeof(TYPE)); + for (int i = 0; i < n; i++) { + x[i] /= MATD_EL(u, i, i); + + for (int j = i+1; j < u->ncols; j++) { + x[j] -= x[i] * MATD_EL(u, i, j); + } + } +} + +// Solve: Lx = b, L is lower triangular +void matd_ltriangle_solve(matd_t *L, const TYPE *b, TYPE *x) +{ + int n = L->ncols; + + for (int i = 0; i < n; i++) { + float acc = b[i]; + + for (int j = 0; j < i; j++) { + acc -= MATD_EL(L, i, j)*x[j]; + } + + x[i] = acc / MATD_EL(L, i, i); + } +} + +// solve Ux = b, U is upper triangular +void matd_utriangle_solve(matd_t *u, const TYPE *b, TYPE *x) +{ + for (int i = u->ncols-1; i >= 0; i--) { + float bi = b[i]; + + float diag = MATD_EL(u, i, i); + + for (int j = i+1; j < u->ncols; j++) + bi -= MATD_EL(u, i, j)*x[j]; + + x[i] = bi / diag; + } +} + +matd_t *matd_chol_solve(const matd_chol_t *chol, const matd_t *b) +{ + matd_t *u = chol->u; + + matd_t *x = matd_copy(b); + + // LUx = b + + // solve Ly = b ==> (U')y = b + + for (int i = 0; i < u->nrows; i++) { + for (int j = 0; j < i; j++) { + // b[i] -= L[i,j]*x[j]... replicated across columns of b + // ==> i.e., ==> + // b[i,k] -= L[i,j]*x[j,k] + for (int k = 0; k < b->ncols; k++) { + MATD_EL(x, i, k) -= MATD_EL(u, j, i)*MATD_EL(x, j, k); + } + } + // x[i] = b[i] / L[i,i] + for (int k = 0; k < b->ncols; k++) { + MATD_EL(x, i, k) /= MATD_EL(u, i, i); + } + } + + // solve Ux = y + for (int k = u->ncols-1; k >= 0; k--) { + float LUkk = 1.0 / MATD_EL(u, k, k); + for (int t = 0; t < b->ncols; t++) + MATD_EL(x, k, t) *= LUkk; + + for (int i = 0; i < k; i++) { + float LUik = -MATD_EL(u, i, k); + for (int t = 0; t < b->ncols; t++) + MATD_EL(x, i, t) += MATD_EL(x, k, t) *LUik; + } + } + + return x; +} + +/*void matd_chol_solve(matd_chol_t *chol, const TYPE *b, TYPE *x) + { + matd_t *u = chol->u; + + TYPE y[u->ncols]; + matd_ltransposetriangle_solve(u, b, y); + matd_utriangle_solve(u, y, x); + } +*/ +// only sensible on PSD matrices. had expected it to be faster than +// inverse via LU... for now, doesn't seem to be. +matd_t *matd_chol_inverse(matd_t *a) +{ + assert(a->nrows == a->ncols); + + matd_chol_t *chol = matd_chol(a); + + matd_t *eye = matd_identity(a->nrows); + matd_t *inv = matd_chol_solve(chol, eye); + matd_destroy(eye); + matd_chol_destroy(chol); + + return inv; +} + +float matd_max(matd_t *m) +{ + float d = -FLT_MAX; + for(int x=0; xnrows; x++) { + for(int y=0; yncols; y++) { + if(MATD_EL(m, x, y) > d) + d = MATD_EL(m, x, y); + } + } + + return d; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "homography.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + + /** Given a 3x3 homography matrix and the focal lengths of the + * camera, compute the pose of the tag. The focal lengths should + * be given in pixels. For example, if the camera's focal length + * is twice the width of the sensor, and the sensor is 600 pixels + * across, the focal length in pixels is 2*600. Note that the + * focal lengths in the fx and fy direction will be approximately + * equal for most lenses, and is not a function of aspect ratio. + * + * Theory: The homography matrix is the product of the camera + * projection matrix and the tag's pose matrix (the matrix that + * projects points from the tag's local coordinate system to the + * camera's coordinate frame). + * + * [ h00 h01 h02 h03] = [ fx 0 cx 0 ] [ R00 R01 R02 TX ] + * [ h10 h11 h12 h13] = [ 0 fy cy 0 ] [ R10 R11 R12 TY ] + * [ h20 h21 h22 h23] = [ 0 0 s 0 ] [ R20 R21 R22 TZ ] + * [ 0 0 0 1 ] + * + * fx is the focal length in the x direction of the camera + * (typically measured in pixels), fy is the focal length. cx and + * cy give the focal center (usually the middle of the image), and + * s is either +1 or -1, depending on the conventions you use. (We + * use 1.) + + * When observing a tag, the points we project in world space all + * have z=0, so we can form a 3x3 matrix by eliminating the 3rd + * column of the pose matrix. + * + * [ h00 h01 h02 ] = [ fx 0 cx 0 ] [ R00 R01 TX ] + * [ h10 h11 h12 ] = [ 0 fy cy 0 ] [ R10 R11 TY ] + * [ h20 h21 h22 ] = [ 0 0 s 0 ] [ R20 R21 TZ ] + * [ 0 0 1 ] + * + * (note that these h's are different from the ones above.) + * + * We can multiply the right-hand side to yield a set of equations + * relating the values of h to the values of the pose matrix. + * + * There are two wrinkles. The first is that the homography matrix + * is known only up to scale. We recover the unknown scale by + * constraining the magnitude of the first two columns of the pose + * matrix to be 1. We use the geometric average scale. The sign of + * the scale factor is recovered by constraining the observed tag + * to be in front of the camera. Once scaled, we recover the first + * two colmuns of the rotation matrix. The third column is the + * cross product of these. + * + * The second wrinkle is that the computed rotation matrix might + * not be exactly orthogonal, so we perform a polar decomposition + * to find a good pure rotation approximation. + * + * Tagsize is the size of the tag in your desired units. I.e., if + * your tag measures 0.25m along the side, your tag size is + * 0.25. (The homography is computed in terms of *half* the tag + * size, i.e., that a tag is 2 units wide as it spans from -1 to + * +1, but this code makes the appropriate adjustment.) + * + * A note on signs: + * + * The code below incorporates no additional negative signs, but + * respects the sign of any parameters that you pass in. Flipping + * the signs allows you to modify the projection to suit a wide + * variety of conditions. + * + * In the "pure geometry" projection matrix, the image appears + * upside down; i.e., the x and y coordinates on the left hand + * side are the opposite of those on the right of the camera + * projection matrix. This would happen for all parameters + * positive: recall that points in front of the camera have + * negative Z values, which will cause the sign of all points to + * flip. + * + * However, most cameras flip things so that the image appears + * "right side up" as though you were looking through the lens + * directly. This means that the projected points should have the + * same sign as the points on the right of the camera projection + * matrix. To achieve this, flip fx and fy. + * + * One further complication: cameras typically put y=0 at the top + * of the image, instead of the bottom. Thus you generally want to + * flip y yet again (so it's now positive again). + * + * General advice: you probably want fx negative, fy positive, cx + * and cy positive, and s=1. + **/ + +// correspondences is a list of float[4]s, consisting of the points x +// and y concatenated. We will compute a homography such that y = Hx +// Specifically, float [] { a, b, c, d } where x = [a b], y = [c d]. + + +#define HOMOGRAPHY_COMPUTE_FLAG_INVERSE 1 +#define HOMOGRAPHY_COMPUTE_FLAG_SVD 0 + +matd_t *homography_compute(zarray_t *correspondences, int flags); + +//void homography_project(const matd_t *H, float x, float y, float *ox, float *oy); +static inline void homography_project(const matd_t *H, float x, float y, float *ox, float *oy) +{ + float xx = MATD_EL(H, 0, 0)*x + MATD_EL(H, 0, 1)*y + MATD_EL(H, 0, 2); + float yy = MATD_EL(H, 1, 0)*x + MATD_EL(H, 1, 1)*y + MATD_EL(H, 1, 2); + float zz = MATD_EL(H, 2, 0)*x + MATD_EL(H, 2, 1)*y + MATD_EL(H, 2, 2); + + *ox = xx / zz; + *oy = yy / zz; +} + +// assuming that the projection matrix is: +// [ fx 0 cx 0 ] +// [ 0 fy cy 0 ] +// [ 0 0 1 0 ] +// +// And that the homography is equal to the projection matrix times the model matrix, +// recover the model matrix (which is returned). Note that the third column of the model +// matrix is missing in the expresison below, reflecting the fact that the homography assumes +// all points are at z=0 (i.e., planar) and that the element of z is thus omitted. +// (3x1 instead of 4x1). +// +// [ fx 0 cx 0 ] [ R00 R01 TX ] [ H00 H01 H02 ] +// [ 0 fy cy 0 ] [ R10 R11 TY ] = [ H10 H11 H12 ] +// [ 0 0 1 0 ] [ R20 R21 TZ ] = [ H20 H21 H22 ] +// [ 0 0 1 ] +// +// fx*R00 + cx*R20 = H00 (note, H only known up to scale; some additional adjustments required; see code.) +// fx*R01 + cx*R21 = H01 +// fx*TX + cx*TZ = H02 +// fy*R10 + cy*R20 = H10 +// fy*R11 + cy*R21 = H11 +// fy*TY + cy*TZ = H12 +// R20 = H20 +// R21 = H21 +// TZ = H22 +matd_t *homography_to_pose(const matd_t *H, float fx, float fy, float cx, float cy); + +// Similar to above +// Recover the model view matrix assuming that the projection matrix is: +// +// [ F 0 A 0 ] (see glFrustrum) +// [ 0 G B 0 ] +// [ 0 0 C D ] +// [ 0 0 -1 0 ] + +matd_t *homography_to_model_view(const matd_t *H, float F, float G, float A, float B, float C, float D); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "homography.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// correspondences is a list of float[4]s, consisting of the points x +// and y concatenated. We will compute a homography such that y = Hx +matd_t *homography_compute(zarray_t *correspondences, int flags) +{ + // compute centroids of both sets of points (yields a better + // conditioned information matrix) + float x_cx = 0, x_cy = 0; + float y_cx = 0, y_cy = 0; + + for (int i = 0; i < zarray_size(correspondences); i++) { + float *c; + zarray_get_volatile(correspondences, i, &c); + + x_cx += c[0]; + x_cy += c[1]; + y_cx += c[2]; + y_cy += c[3]; + } + + int sz = zarray_size(correspondences); + x_cx /= sz; + x_cy /= sz; + y_cx /= sz; + y_cy /= sz; + + // NB We don't normalize scale; it seems implausible that it could + // possibly make any difference given the dynamic range of IEEE + // doubles. + + matd_t *A = matd_create(9,9); + for (int i = 0; i < zarray_size(correspondences); i++) { + float *c; + zarray_get_volatile(correspondences, i, &c); + + // (below world is "x", and image is "y") + float worldx = c[0] - x_cx; + float worldy = c[1] - x_cy; + float imagex = c[2] - y_cx; + float imagey = c[3] - y_cy; + + float a03 = -worldx; + float a04 = -worldy; + float a05 = -1; + float a06 = worldx*imagey; + float a07 = worldy*imagey; + float a08 = imagey; + + MATD_EL(A, 3, 3) += a03*a03; + MATD_EL(A, 3, 4) += a03*a04; + MATD_EL(A, 3, 5) += a03*a05; + MATD_EL(A, 3, 6) += a03*a06; + MATD_EL(A, 3, 7) += a03*a07; + MATD_EL(A, 3, 8) += a03*a08; + MATD_EL(A, 4, 4) += a04*a04; + MATD_EL(A, 4, 5) += a04*a05; + MATD_EL(A, 4, 6) += a04*a06; + MATD_EL(A, 4, 7) += a04*a07; + MATD_EL(A, 4, 8) += a04*a08; + MATD_EL(A, 5, 5) += a05*a05; + MATD_EL(A, 5, 6) += a05*a06; + MATD_EL(A, 5, 7) += a05*a07; + MATD_EL(A, 5, 8) += a05*a08; + MATD_EL(A, 6, 6) += a06*a06; + MATD_EL(A, 6, 7) += a06*a07; + MATD_EL(A, 6, 8) += a06*a08; + MATD_EL(A, 7, 7) += a07*a07; + MATD_EL(A, 7, 8) += a07*a08; + MATD_EL(A, 8, 8) += a08*a08; + + float a10 = worldx; + float a11 = worldy; + float a12 = 1; + float a16 = -worldx*imagex; + float a17 = -worldy*imagex; + float a18 = -imagex; + + MATD_EL(A, 0, 0) += a10*a10; + MATD_EL(A, 0, 1) += a10*a11; + MATD_EL(A, 0, 2) += a10*a12; + MATD_EL(A, 0, 6) += a10*a16; + MATD_EL(A, 0, 7) += a10*a17; + MATD_EL(A, 0, 8) += a10*a18; + MATD_EL(A, 1, 1) += a11*a11; + MATD_EL(A, 1, 2) += a11*a12; + MATD_EL(A, 1, 6) += a11*a16; + MATD_EL(A, 1, 7) += a11*a17; + MATD_EL(A, 1, 8) += a11*a18; + MATD_EL(A, 2, 2) += a12*a12; + MATD_EL(A, 2, 6) += a12*a16; + MATD_EL(A, 2, 7) += a12*a17; + MATD_EL(A, 2, 8) += a12*a18; + MATD_EL(A, 6, 6) += a16*a16; + MATD_EL(A, 6, 7) += a16*a17; + MATD_EL(A, 6, 8) += a16*a18; + MATD_EL(A, 7, 7) += a17*a17; + MATD_EL(A, 7, 8) += a17*a18; + MATD_EL(A, 8, 8) += a18*a18; + + float a20 = -worldx*imagey; + float a21 = -worldy*imagey; + float a22 = -imagey; + float a23 = worldx*imagex; + float a24 = worldy*imagex; + float a25 = imagex; + + MATD_EL(A, 0, 0) += a20*a20; + MATD_EL(A, 0, 1) += a20*a21; + MATD_EL(A, 0, 2) += a20*a22; + MATD_EL(A, 0, 3) += a20*a23; + MATD_EL(A, 0, 4) += a20*a24; + MATD_EL(A, 0, 5) += a20*a25; + MATD_EL(A, 1, 1) += a21*a21; + MATD_EL(A, 1, 2) += a21*a22; + MATD_EL(A, 1, 3) += a21*a23; + MATD_EL(A, 1, 4) += a21*a24; + MATD_EL(A, 1, 5) += a21*a25; + MATD_EL(A, 2, 2) += a22*a22; + MATD_EL(A, 2, 3) += a22*a23; + MATD_EL(A, 2, 4) += a22*a24; + MATD_EL(A, 2, 5) += a22*a25; + MATD_EL(A, 3, 3) += a23*a23; + MATD_EL(A, 3, 4) += a23*a24; + MATD_EL(A, 3, 5) += a23*a25; + MATD_EL(A, 4, 4) += a24*a24; + MATD_EL(A, 4, 5) += a24*a25; + MATD_EL(A, 5, 5) += a25*a25; + } + + // make symmetric + for (int i = 0; i < 9; i++) + for (int j = i+1; j < 9; j++) + MATD_EL(A, j, i) = MATD_EL(A, i, j); + + matd_t *H = matd_create(3,3); + + if (flags & HOMOGRAPHY_COMPUTE_FLAG_INVERSE) { + // compute singular vector by (carefully) inverting the rank-deficient matrix. + + if (1) { + matd_t *Ainv = matd_inverse(A); + float scale = 0; + + for (int i = 0; i < 9; i++) + scale += sq(MATD_EL(Ainv, i, 0)); + scale = sqrt(scale); + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + MATD_EL(H, i, j) = MATD_EL(Ainv, 3*i+j, 0) / scale; + + matd_destroy(Ainv); + } else { + + matd_t *b = matd_create_data(9, 1, (float[]) { 1, 0, 0, 0, 0, 0, 0, 0, 0 }); + matd_t *Ainv = NULL; + + if (0) { + matd_plu_t *lu = matd_plu(A); + Ainv = matd_plu_solve(lu, b); + matd_plu_destroy(lu); + } else { + matd_chol_t *chol = matd_chol(A); + Ainv = matd_chol_solve(chol, b); + matd_chol_destroy(chol); + } + + float scale = 0; + + for (int i = 0; i < 9; i++) + scale += sq(MATD_EL(Ainv, i, 0)); + scale = sqrt(scale); + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + MATD_EL(H, i, j) = MATD_EL(Ainv, 3*i+j, 0) / scale; + + matd_destroy(b); + matd_destroy(Ainv); + } + + } else { + // compute singular vector using SVD. A bit slower, but more accurate. + matd_svd_t svd = matd_svd_flags(A, MATD_SVD_NO_WARNINGS); + + for (int i = 0; i < 3; i++) + for (int j = 0; j < 3; j++) + MATD_EL(H, i, j) = MATD_EL(svd.U, 3*i+j, 8); + + matd_destroy(svd.U); + matd_destroy(svd.S); + matd_destroy(svd.V); + + } + + matd_t *Tx = matd_identity(3); + MATD_EL(Tx,0,2) = -x_cx; + MATD_EL(Tx,1,2) = -x_cy; + + matd_t *Ty = matd_identity(3); + MATD_EL(Ty,0,2) = y_cx; + MATD_EL(Ty,1,2) = y_cy; + + matd_t *H2 = matd_op("M*M*M", Ty, H, Tx); + + matd_destroy(A); + matd_destroy(Tx); + matd_destroy(Ty); + matd_destroy(H); + + return H2; +} + + +// assuming that the projection matrix is: +// [ fx 0 cx 0 ] +// [ 0 fy cy 0 ] +// [ 0 0 1 0 ] +// +// And that the homography is equal to the projection matrix times the +// model matrix, recover the model matrix (which is returned). Note +// that the third column of the model matrix is missing in the +// expresison below, reflecting the fact that the homography assumes +// all points are at z=0 (i.e., planar) and that the element of z is +// thus omitted. (3x1 instead of 4x1). +// +// [ fx 0 cx 0 ] [ R00 R01 TX ] [ H00 H01 H02 ] +// [ 0 fy cy 0 ] [ R10 R11 TY ] = [ H10 H11 H12 ] +// [ 0 0 1 0 ] [ R20 R21 TZ ] = [ H20 H21 H22 ] +// [ 0 0 1 ] +// +// fx*R00 + cx*R20 = H00 (note, H only known up to scale; some additional adjustments required; see code.) +// fx*R01 + cx*R21 = H01 +// fx*TX + cx*TZ = H02 +// fy*R10 + cy*R20 = H10 +// fy*R11 + cy*R21 = H11 +// fy*TY + cy*TZ = H12 +// R20 = H20 +// R21 = H21 +// TZ = H22 + +matd_t *homography_to_pose(const matd_t *H, float fx, float fy, float cx, float cy) +{ + // Note that every variable that we compute is proportional to the scale factor of H. + float R20 = MATD_EL(H, 2, 0); + float R21 = MATD_EL(H, 2, 1); + float TZ = MATD_EL(H, 2, 2); + float R00 = (MATD_EL(H, 0, 0) - cx*R20) / fx; + float R01 = (MATD_EL(H, 0, 1) - cx*R21) / fx; + float TX = (MATD_EL(H, 0, 2) - cx*TZ) / fx; + float R10 = (MATD_EL(H, 1, 0) - cy*R20) / fy; + float R11 = (MATD_EL(H, 1, 1) - cy*R21) / fy; + float TY = (MATD_EL(H, 1, 2) - cy*TZ) / fy; + + // compute the scale by requiring that the rotation columns are unit length + // (Use geometric average of the two length vectors we have) + float length1 = sqrtf(R00*R00 + R10*R10 + R20*R20); + float length2 = sqrtf(R01*R01 + R11*R11 + R21*R21); + float s = 1.0 / sqrtf(length1 * length2); + + // get sign of S by requiring the tag to be in front the camera; + // we assume camera looks in the -Z direction. + if (TZ > 0) + s *= -1; + + R20 *= s; + R21 *= s; + TZ *= s; + R00 *= s; + R01 *= s; + TX *= s; + R10 *= s; + R11 *= s; + TY *= s; + + // now recover [R02 R12 R22] by noting that it is the cross product of the other two columns. + float R02 = R10*R21 - R20*R11; + float R12 = R20*R01 - R00*R21; + float R22 = R00*R11 - R10*R01; + + // Improve rotation matrix by applying polar decomposition. + if (1) { + // do polar decomposition. This makes the rotation matrix + // "proper", but probably increases the reprojection error. An + // iterative alignment step would be superior. + + matd_t *R = matd_create_data(3, 3, (float[]) { R00, R01, R02, + R10, R11, R12, + R20, R21, R22 }); + + matd_svd_t svd = matd_svd(R); + matd_destroy(R); + + R = matd_op("M*M'", svd.U, svd.V); + + matd_destroy(svd.U); + matd_destroy(svd.S); + matd_destroy(svd.V); + + R00 = MATD_EL(R, 0, 0); + R01 = MATD_EL(R, 0, 1); + R02 = MATD_EL(R, 0, 2); + R10 = MATD_EL(R, 1, 0); + R11 = MATD_EL(R, 1, 1); + R12 = MATD_EL(R, 1, 2); + R20 = MATD_EL(R, 2, 0); + R21 = MATD_EL(R, 2, 1); + R22 = MATD_EL(R, 2, 2); + + matd_destroy(R); + } + + return matd_create_data(4, 4, (float[]) { R00, R01, R02, TX, + R10, R11, R12, TY, + R20, R21, R22, TZ, + 0, 0, 0, 1 }); +} + +// Similar to above +// Recover the model view matrix assuming that the projection matrix is: +// +// [ F 0 A 0 ] (see glFrustrum) +// [ 0 G B 0 ] +// [ 0 0 C D ] +// [ 0 0 -1 0 ] + +matd_t *homography_to_model_view(const matd_t *H, float F, float G, float A, float B, float C, float D) +{ + // Note that every variable that we compute is proportional to the scale factor of H. + float R20 = -MATD_EL(H, 2, 0); + float R21 = -MATD_EL(H, 2, 1); + float TZ = -MATD_EL(H, 2, 2); + float R00 = (MATD_EL(H, 0, 0) - A*R20) / F; + float R01 = (MATD_EL(H, 0, 1) - A*R21) / F; + float TX = (MATD_EL(H, 0, 2) - A*TZ) / F; + float R10 = (MATD_EL(H, 1, 0) - B*R20) / G; + float R11 = (MATD_EL(H, 1, 1) - B*R21) / G; + float TY = (MATD_EL(H, 1, 2) - B*TZ) / G; + + // compute the scale by requiring that the rotation columns are unit length + // (Use geometric average of the two length vectors we have) + float length1 = sqrtf(R00*R00 + R10*R10 + R20*R20); + float length2 = sqrtf(R01*R01 + R11*R11 + R21*R21); + float s = 1.0 / sqrtf(length1 * length2); + + // get sign of S by requiring the tag to be in front of the camera + // (which is Z < 0) for our conventions. + if (TZ > 0) + s *= -1; + + R20 *= s; + R21 *= s; + TZ *= s; + R00 *= s; + R01 *= s; + TX *= s; + R10 *= s; + R11 *= s; + TY *= s; + + // now recover [R02 R12 R22] by noting that it is the cross product of the other two columns. + float R02 = R10*R21 - R20*R11; + float R12 = R20*R01 - R00*R21; + float R22 = R00*R11 - R10*R01; + + // TODO XXX: Improve rotation matrix by applying polar decomposition. + + return matd_create_data(4, 4, (float[]) { R00, R01, R02, TX, + R10, R11, R12, TY, + R20, R21, R22, TZ, + 0, 0, 0, 1 }); +} + +// Only uses the upper 3x3 matrix. +/* +static void matrix_to_quat(const matd_t *R, float q[4]) +{ + // see: "from quaternion to matrix and back" + + // trace: get the same result if R is 4x4 or 3x3: + float T = MATD_EL(R, 0, 0) + MATD_EL(R, 1, 1) + MATD_EL(R, 2, 2) + 1; + float S = 0; + + float m0 = MATD_EL(R, 0, 0); + float m1 = MATD_EL(R, 1, 0); + float m2 = MATD_EL(R, 2, 0); + float m4 = MATD_EL(R, 0, 1); + float m5 = MATD_EL(R, 1, 1); + float m6 = MATD_EL(R, 2, 1); + float m8 = MATD_EL(R, 0, 2); + float m9 = MATD_EL(R, 1, 2); + float m10 = MATD_EL(R, 2, 2); + + if (T > 0.0000001) { + S = sqrtf(T) * 2; + q[1] = -( m9 - m6 ) / S; + q[2] = -( m2 - m8 ) / S; + q[3] = -( m4 - m1 ) / S; + q[0] = 0.25 * S; + } else if ( m0 > m5 && m0 > m10 ) { // Column 0: + S = sqrtf( 1.0 + m0 - m5 - m10 ) * 2; + q[1] = -0.25 * S; + q[2] = -(m4 + m1 ) / S; + q[3] = -(m2 + m8 ) / S; + q[0] = (m9 - m6 ) / S; + } else if ( m5 > m10 ) { // Column 1: + S = sqrtf( 1.0 + m5 - m0 - m10 ) * 2; + q[1] = -(m4 + m1 ) / S; + q[2] = -0.25 * S; + q[3] = -(m9 + m6 ) / S; + q[0] = (m2 - m8 ) / S; + } else { + // Column 2: + S = sqrtf( 1.0 + m10 - m0 - m5 ) * 2; + q[1] = -(m2 + m8 ) / S; + q[2] = -(m9 + m6 ) / S; + q[3] = -0.25 * S; + q[0] = (m4 - m1 ) / S; + } + + float mag2 = 0; + for (int i = 0; i < 4; i++) + mag2 += q[i]*q[i]; + float norm = 1.0 / sqrtf(mag2); + for (int i = 0; i < 4; i++) + q[i] *= norm; +} +*/ + +// overwrites upper 3x3 area of matrix M. Doesn't touch any other elements of M. +void quat_to_matrix(const float q[4], matd_t *M) +{ + float w = q[0], x = q[1], y = q[2], z = q[3]; + + MATD_EL(M, 0, 0) = w*w + x*x - y*y - z*z; + MATD_EL(M, 0, 1) = 2*x*y - 2*w*z; + MATD_EL(M, 0, 2) = 2*x*z + 2*w*y; + + MATD_EL(M, 1, 0) = 2*x*y + 2*w*z; + MATD_EL(M, 1, 1) = w*w - x*x + y*y - z*z; + MATD_EL(M, 1, 2) = 2*y*z - 2*w*x; + + MATD_EL(M, 2, 0) = 2*x*z - 2*w*y; + MATD_EL(M, 2, 1) = 2*y*z + 2*w*x; + MATD_EL(M, 2, 2) = w*w - x*x - y*y + z*z; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "g2d.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// This library tries to avoid needless proliferation of types. +// +// A point is a float[2]. (Note that when passing a float[2] as an +// argument, it is passed by pointer, not by value.) +// +// A polygon is a zarray_t of float[2]. (Note that in this case, the +// zarray contains the actual vertex data, and not merely a pointer to +// some other data. IMPORTANT: A polygon must be specified in CCW +// order. It is implicitly closed (do not list the same point at the +// beginning at the end. +// +// Where sensible, it is assumed that objects should be allocated +// sparingly; consequently "init" style methods, rather than "create" +// methods are used. + +//////////////////////////////////////////////////////////////////// +// Lines + +typedef struct +{ + // Internal representation: a point that the line goes through (p) and + // the direction of the line (u). + float p[2]; + float u[2]; // always a unit vector +} g2d_line_t; + +// initialize a line object. +void g2d_line_init_from_points(g2d_line_t *line, const float p0[2], const float p1[2]); + +// The line defines a one-dimensional coordinate system whose origin +// is p. Where is q? (If q is not on the line, the point nearest q is +// returned. +float g2d_line_get_coordinate(const g2d_line_t *line, const float q[2]); + +// Intersect two lines. The intersection, if it exists, is written to +// p (if not NULL), and 1 is returned. Else, zero is returned. +int g2d_line_intersect_line(const g2d_line_t *linea, const g2d_line_t *lineb, float *p); + +//////////////////////////////////////////////////////////////////// +// Line Segments. line.p is always one endpoint; p1 is the other +// endpoint. +typedef struct +{ + g2d_line_t line; + float p1[2]; +} g2d_line_segment_t; + +void g2d_line_segment_init_from_points(g2d_line_segment_t *seg, const float p0[2], const float p1[2]); + +// Intersect two segments. The intersection, if it exists, is written +// to p (if not NULL), and 1 is returned. Else, zero is returned. +int g2d_line_segment_intersect_segment(const g2d_line_segment_t *sega, const g2d_line_segment_t *segb, float *p); + +void g2d_line_segment_closest_point(const g2d_line_segment_t *seg, const float *q, float *p); +float g2d_line_segment_closest_point_distance(const g2d_line_segment_t *seg, const float *q); + +//////////////////////////////////////////////////////////////////// +// Polygons + +zarray_t *g2d_polygon_create_data(float v[][2], int sz); + +zarray_t *g2d_polygon_create_zeros(int sz); + +zarray_t *g2d_polygon_create_empty(); + +void g2d_polygon_add(zarray_t *poly, float v[2]); + +// Takes a polygon in either CW or CCW and modifies it (if necessary) +// to be CCW. +void g2d_polygon_make_ccw(zarray_t *poly); + +// Return 1 if point q lies within poly. +int g2d_polygon_contains_point(const zarray_t *poly, float q[2]); + +// Do the edges of the polygons cross? (Does not test for containment). +int g2d_polygon_intersects_polygon(const zarray_t *polya, const zarray_t *polyb); + +// Does polya completely contain polyb? +int g2d_polygon_contains_polygon(const zarray_t *polya, const zarray_t *polyb); + +// Is there some point which is in both polya and polyb? +int g2d_polygon_overlaps_polygon(const zarray_t *polya, const zarray_t *polyb); + +// returns the number of points written to x. see comments. +int g2d_polygon_rasterize(const zarray_t *poly, float y, float *x); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "g2d.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +float g2d_distance(const float a[2], const float b[2]) +{ + return sqrtf(sq(a[0]-b[0]) + sq(a[1]-b[1])); +} + +zarray_t *g2d_polygon_create_empty() +{ + return zarray_create(sizeof(float[2])); +} + +void g2d_polygon_add(zarray_t *poly, float v[2]) +{ + zarray_add(poly, v); +} + +zarray_t *g2d_polygon_create_data(float v[][2], int sz) +{ + zarray_t *points = g2d_polygon_create_empty(); + + for (int i = 0; i < sz; i++) + g2d_polygon_add(points, v[i]); + + return points; +} + +zarray_t *g2d_polygon_create_zeros(int sz) +{ + zarray_t *points = zarray_create(sizeof(float[2])); + + float z[2] = { 0, 0 }; + + for (int i = 0; i < sz; i++) + zarray_add(points, z); + + return points; +} + +void g2d_polygon_make_ccw(zarray_t *poly) +{ + // Step one: we want the points in counter-clockwise order. + // If the points are in clockwise order, we'll reverse them. + float total_theta = 0; + float last_theta = 0; + + // Count the angle accumulated going around the polygon. If + // the sum is +2pi, it's CCW. Otherwise, we'll get -2pi. + int sz = zarray_size(poly); + + for (int i = 0; i <= sz; i++) { + float p0[2], p1[2]; + zarray_get(poly, i % sz, &p0); + zarray_get(poly, (i+1) % sz, &p1); + + float this_theta = atan2(p1[1]-p0[1], p1[0]-p0[0]); + + if (i > 0) { + float dtheta = mod2pi(this_theta-last_theta); + total_theta += dtheta; + } + + last_theta = this_theta; + } + + int ccw = (total_theta > 0); + + // reverse order if necessary. + if (!ccw) { + for (int i = 0; i < sz / 2; i++) { + float a[2], b[2]; + + zarray_get(poly, i, a); + zarray_get(poly, sz-1-i, b); + zarray_set(poly, i, b, NULL); + zarray_set(poly, sz-1-i, a, NULL); + } + } +} + +int g2d_polygon_contains_point_ref(const zarray_t *poly, float q[2]) +{ + // use winding. If the point is inside the polygon, we'll wrap + // around it (accumulating 6.28 radians). If we're outside the + // polygon, we'll accumulate zero. + int psz = zarray_size(poly); + + float acc_theta = 0; + + float last_theta; + + for (int i = 0; i <= psz; i++) { + float p[2]; + + zarray_get(poly, i % psz, &p); + + float this_theta = atan2(q[1]-p[1], q[0]-p[0]); + + if (i != 0) + acc_theta += mod2pi(this_theta - last_theta); + + last_theta = this_theta; + } + + return acc_theta > M_PI; +} + +/* +// sort by x coordinate, ascending +static int g2d_convex_hull_sort(const void *_a, const void *_b) +{ + float *a = (float*) _a; + float *b = (float*) _b; + + if (a[0] < b[0]) + return -1; + if (a[0] == b[0]) + return 0; + return 1; +} +*/ + +/* +zarray_t *g2d_convex_hull2(const zarray_t *points) +{ + zarray_t *hull = zarray_copy(points); + + zarray_sort(hull, g2d_convex_hull_sort); + + int hsz = zarray_size(hull); + int hout = 0; + + for (int hin = 1; hin < hsz; hin++) { + float *p; + zarray_get_volatile(hull, i, &p); + + // Everything to the right of hin is already convex. We now + // add one point, p, which begins "connected" by two + // (coincident) edges from the last right-most point to p. + float *last; + zarray_get_volatile(hull, hout, &last); + + // We now remove points from the convex hull by moving + } + + return hull; +} +*/ + +// creates and returns a zarray(float[2]). The resulting polygon is +// CCW and implicitly closed. Unnecessary colinear points are omitted. +zarray_t *g2d_convex_hull(const zarray_t *points) +{ + zarray_t *hull = zarray_create(sizeof(float[2])); + + // gift-wrap algorithm. + + // step 1: find left most point. + int insz = zarray_size(points); + + // must have at least 2 points. (XXX need 3?) + assert(insz >= 2); + + float *pleft = NULL; + for (int i = 0; i < insz; i++) { + float *p; + zarray_get_volatile(points, i, &p); + + if (pleft == NULL || p[0] < pleft[0]) + pleft = p; + } + + // cannot be NULL since there must be at least one point. + assert(pleft != NULL); + + zarray_add(hull, pleft); + + // step 2. gift wrap. Keep searching for points that make the + // smallest-angle left-hand turn. This implementation is carefully + // written to use only addition/subtraction/multiply. No division + // or sqrts. This guarantees exact results for integer-coordinate + // polygons (no rounding/precision problems). + float *p = pleft; + + while (1) { + assert(p != NULL); + + float *q = NULL; + float n0 = 0, n1 = 0; // the normal to the line (p, q) (not + // necessarily unit length). + + // Search for the point q for which the line (p,q) is most "to + // the right of" the other points. (i.e., every time we find a + // point that is to the right of our current line, we change + // lines.) + for (int i = 0; i < insz; i++) { + float *thisq; + zarray_get_volatile(points, i, &thisq); + + if (thisq == p) + continue; + + // the first time we find another point, we initialize our + // value of q, forming the line (p,q) + if (q == NULL) { + q = thisq; + n0 = q[1] - p[1]; + n1 = -q[0] + p[0]; + } else { + // we already have a line (p,q). is point thisq RIGHT OF line (p, q)? + float e0 = thisq[0] - p[0], e1 = thisq[1] - p[1]; + float dot = e0*n0 + e1*n1; + + if (dot > 0) { + // it is. change our line. + q = thisq; + n0 = q[1] - p[1]; + n1 = -q[0] + p[0]; + } + } + } + + // we must have elected *some* line, so long as there are at + // least 2 points in the polygon. + assert(q != NULL); + + // loop completed? + if (q == pleft) + break; + + int colinear = 0; + + // is this new point colinear with the last two? + if (zarray_size(hull) > 1) { + float *o; + zarray_get_volatile(hull, zarray_size(hull) - 2, &o); + + float e0 = o[0] - p[0]; + float e1 = o[1] - p[1]; + + if (n0*e0 + n1*e1 == 0) + colinear = 1; + } + + // if it is colinear, overwrite the last one. + if (colinear) + zarray_set(hull, zarray_size(hull)-1, q, NULL); + else + zarray_add(hull, q); + + p = q; + } + + return hull; +} + +// Find point p on the boundary of poly that is closest to q. +void g2d_polygon_closest_boundary_point(const zarray_t *poly, const float q[2], float *p) +{ + int psz = zarray_size(poly); + float min_dist = HUGE_VALF; + + for (int i = 0; i < psz; i++) { + float *p0, *p1; + + zarray_get_volatile(poly, i, &p0); + zarray_get_volatile(poly, (i+1) % psz, &p1); + + g2d_line_segment_t seg; + g2d_line_segment_init_from_points(&seg, p0, p1); + + float thisp[2]; + g2d_line_segment_closest_point(&seg, q, thisp); + + float dist = g2d_distance(q, thisp); + if (dist < min_dist) { + memcpy(p, thisp, sizeof(float[2])); + min_dist = dist; + } + } +} + +int g2d_polygon_contains_point(const zarray_t *poly, float q[2]) +{ + // use winding. If the point is inside the polygon, we'll wrap + // around it (accumulating 6.28 radians). If we're outside the + // polygon, we'll accumulate zero. + int psz = zarray_size(poly); + assert(psz > 0); + + int last_quadrant; + int quad_acc = 0; + + for (int i = 0; i <= psz; i++) { + float *p; + + zarray_get_volatile(poly, i % psz, &p); + + // p[0] < q[0] p[1] < q[1] quadrant + // 0 0 0 + // 0 1 3 + // 1 0 1 + // 1 1 2 + + // p[1] < q[1] p[0] < q[0] quadrant + // 0 0 0 + // 0 1 1 + // 1 0 3 + // 1 1 2 + + int quadrant; + if (p[0] < q[0]) + quadrant = (p[1] < q[1]) ? 2 : 1; + else + quadrant = (p[1] < q[1]) ? 3 : 0; + + if (i > 0) { + int dquadrant = quadrant - last_quadrant; + + // encourage a jump table by mapping to small positive integers. + switch (dquadrant) { + case -3: + case 1: + quad_acc ++; + break; + case -1: + case 3: + quad_acc --; + break; + case 0: + break; + case -2: + case 2: + { + // get the previous point. + float *p0; + zarray_get_volatile(poly, i-1, &p0); + + // Consider the points p0 and p (the points around the + //polygon that we are tracing) and the query point q. + // + // If we've moved diagonally across quadrants, we want + // to measure whether we have rotated +PI radians or + // -PI radians. We can test this by computing the dot + // product of vector (p0-q) with the vector + // perpendicular to vector (p-q) + float nx = p[1] - q[1]; + float ny = -p[0] + q[0]; + + float dot = nx*(p0[0]-q[0]) + ny*(p0[1]-q[1]); + if (dot < 0) + quad_acc -= 2; + else + quad_acc += 2; + + break; + } + } + } + + last_quadrant = quadrant; + } + + int v = (quad_acc >= 2) || (quad_acc <= -2); + + if (0 && v != g2d_polygon_contains_point_ref(poly, q)) { + printf("FAILURE %d %d\n", v, quad_acc); + exit(-1); + } + + return v; +} + +void g2d_line_init_from_points(g2d_line_t *line, const float p0[2], const float p1[2]) +{ + line->p[0] = p0[0]; + line->p[1] = p0[1]; + line->u[0] = p1[0]-p0[0]; + line->u[1] = p1[1]-p0[1]; + float mag = sqrtf(sq(line->u[0]) + sq(line->u[1])); + + line->u[0] /= mag; + line->u[1] /= mag; +} + +float g2d_line_get_coordinate(const g2d_line_t *line, const float q[2]) +{ + return (q[0]-line->p[0])*line->u[0] + (q[1]-line->p[1])*line->u[1]; +} + +// Compute intersection of two line segments. If they intersect, +// result is stored in p and 1 is returned. Otherwise, zero is +// returned. p may be NULL. +int g2d_line_intersect_line(const g2d_line_t *linea, const g2d_line_t *lineb, float *p) +{ + // this implementation is many times faster than the original, + // mostly due to avoiding a general-purpose LU decomposition in + // Matrix.inverse(). + float m00, m01, m10, m11; + float i00, i01; + float b00, b10; + + m00 = linea->u[0]; + m01= -lineb->u[0]; + m10 = linea->u[1]; + m11= -lineb->u[1]; + + // determinant of m + float det = m00*m11-m01*m10; + + // parallel lines? + if (fabs(det) < 0.00000001) + return 0; + + // inverse of m + i00 = m11/det; + i01 = -m01/det; + + b00 = lineb->p[0] - linea->p[0]; + b10 = lineb->p[1] - linea->p[1]; + + float x00; //, x10; + x00 = i00*b00+i01*b10; + + if (p != NULL) { + p[0] = linea->u[0]*x00 + linea->p[0]; + p[1] = linea->u[1]*x00 + linea->p[1]; + } + + return 1; +} + + +void g2d_line_segment_init_from_points(g2d_line_segment_t *seg, const float p0[2], const float p1[2]) +{ + g2d_line_init_from_points(&seg->line, p0, p1); + seg->p1[0] = p1[0]; + seg->p1[1] = p1[1]; +} + +// Find the point p on segment seg that is closest to point q. +void g2d_line_segment_closest_point(const g2d_line_segment_t *seg, const float *q, float *p) +{ + float a = g2d_line_get_coordinate(&seg->line, seg->line.p); + float b = g2d_line_get_coordinate(&seg->line, seg->p1); + float c = g2d_line_get_coordinate(&seg->line, q); + + if (a < b) + c = dclamp(c, a, b); + else + c = dclamp(c, b, a); + + p[0] = seg->line.p[0] + c * seg->line.u[0]; + p[1] = seg->line.p[1] + c * seg->line.u[1]; +} + +// Compute intersection of two line segments. If they intersect, +// result is stored in p and 1 is returned. Otherwise, zero is +// returned. p may be NULL. +int g2d_line_segment_intersect_segment(const g2d_line_segment_t *sega, const g2d_line_segment_t *segb, float *p) +{ + float tmp[2]; + + if (!g2d_line_intersect_line(&sega->line, &segb->line, tmp)) + return 0; + + float a = g2d_line_get_coordinate(&sega->line, sega->line.p); + float b = g2d_line_get_coordinate(&sega->line, sega->p1); + float c = g2d_line_get_coordinate(&sega->line, tmp); + + // does intersection lie on the first line? + if ((ca && c>b)) + return 0; + + a = g2d_line_get_coordinate(&segb->line, segb->line.p); + b = g2d_line_get_coordinate(&segb->line, segb->p1); + c = g2d_line_get_coordinate(&segb->line, tmp); + + // does intersection lie on second line? + if ((ca && c>b)) + return 0; + + if (p != NULL) { + p[0] = tmp[0]; + p[1] = tmp[1]; + } + + return 1; +} + +// Compute intersection of a line segment and a line. If they +// intersect, result is stored in p and 1 is returned. Otherwise, zero +// is returned. p may be NULL. +int g2d_line_segment_intersect_line(const g2d_line_segment_t *seg, const g2d_line_t *line, float *p) +{ + float tmp[2]; + + if (!g2d_line_intersect_line(&seg->line, line, tmp)) + return 0; + + float a = g2d_line_get_coordinate(&seg->line, seg->line.p); + float b = g2d_line_get_coordinate(&seg->line, seg->p1); + float c = g2d_line_get_coordinate(&seg->line, tmp); + + // does intersection lie on the first line? + if ((ca && c>b)) + return 0; + + if (p != NULL) { + p[0] = tmp[0]; + p[1] = tmp[1]; + } + + return 1; +} + +// do the edges of polya and polyb collide? (Does NOT test for containment). +int g2d_polygon_intersects_polygon(const zarray_t *polya, const zarray_t *polyb) +{ + // do any of the line segments collide? If so, the answer is no. + + // dumb N^2 method. + for (int ia = 0; ia < zarray_size(polya); ia++) { + float pa0[2], pa1[2]; + zarray_get(polya, ia, pa0); + zarray_get(polya, (ia+1)%zarray_size(polya), pa1); + + g2d_line_segment_t sega; + g2d_line_segment_init_from_points(&sega, pa0, pa1); + + for (int ib = 0; ib < zarray_size(polyb); ib++) { + float pb0[2], pb1[2]; + zarray_get(polyb, ib, pb0); + zarray_get(polyb, (ib+1)%zarray_size(polyb), pb1); + + g2d_line_segment_t segb; + g2d_line_segment_init_from_points(&segb, pb0, pb1); + + if (g2d_line_segment_intersect_segment(&sega, &segb, NULL)) + return 1; + } + } + + return 0; +} + +// does polya completely contain polyb? +int g2d_polygon_contains_polygon(const zarray_t *polya, const zarray_t *polyb) +{ + // do any of the line segments collide? If so, the answer is no. + if (g2d_polygon_intersects_polygon(polya, polyb)) + return 0; + + // if none of the edges cross, then the polygon is either fully + // contained or fully outside. + float p[2]; + zarray_get(polyb, 0, p); + + return g2d_polygon_contains_point(polya, p); +} + +// compute a point that is inside the polygon. (It may not be *far* inside though) +void g2d_polygon_get_interior_point(const zarray_t *poly, float *p) +{ + // take the first three points, which form a triangle. Find the middle point + float a[2], b[2], c[2]; + + zarray_get(poly, 0, a); + zarray_get(poly, 1, b); + zarray_get(poly, 2, c); + + p[0] = (a[0]+b[0]+c[0])/3; + p[1] = (a[1]+b[1]+c[1])/3; +} + +int g2d_polygon_overlaps_polygon(const zarray_t *polya, const zarray_t *polyb) +{ + // do any of the line segments collide? If so, the answer is yes. + if (g2d_polygon_intersects_polygon(polya, polyb)) + return 1; + + // if none of the edges cross, then the polygon is either fully + // contained or fully outside. + float p[2]; + g2d_polygon_get_interior_point(polyb, p); + + if (g2d_polygon_contains_point(polya, p)) + return 1; + + g2d_polygon_get_interior_point(polya, p); + + if (g2d_polygon_contains_point(polyb, p)) + return 1; + + return 0; +} + +static int double_sort_up(const void *_a, const void *_b) +{ + float a = *((float*) _a); + float b = *((float*) _b); + + if (a < b) + return -1; + + if (a == b) + return 0; + + return 1; +} + +// Compute the crossings of the polygon along line y, storing them in +// the array x. X must be allocated to be at least as long as +// zarray_size(poly). X will be sorted, ready for +// rasterization. Returns the number of intersections (and elements +// written to x). +/* + To rasterize, do something like this: + + float res = 0.099; + for (float y = y0; y < y1; y += res) { + float xs[zarray_size(poly)]; + + int xsz = g2d_polygon_rasterize(poly, y, xs); + int xpos = 0; + int inout = 0; // start off "out" + + for (float x = x0; x < x1; x += res) { + while (x > xs[xpos] && xpos < xsz) { + xpos++; + inout ^= 1; + } + + if (inout) + printf("y"); + else + printf(" "); + } + printf("\n"); +*/ + +// returns the number of x intercepts +int g2d_polygon_rasterize(const zarray_t *poly, float y, float *x) +{ + int sz = zarray_size(poly); + + g2d_line_t line; + if (1) { + float p0[2] = { 0, y }; + float p1[2] = { 1, y }; + + g2d_line_init_from_points(&line, p0, p1); + } + + int xpos = 0; + + for (int i = 0; i < sz; i++) { + g2d_line_segment_t seg; + float *p0, *p1; + zarray_get_volatile(poly, i, &p0); + zarray_get_volatile(poly, (i+1)%sz, &p1); + + g2d_line_segment_init_from_points(&seg, p0, p1); + + float q[2]; + if (g2d_line_segment_intersect_line(&seg, &line, q)) + x[xpos++] = q[0]; + } + + qsort(x, xpos, sizeof(float), double_sort_up); + + return xpos; +} + +/* + /---(1,5) + (-2,4)-/ | + \ | + \ (1,2)--(2,2)\ + \ \ + \ \ + (0,0)------------------(4,0) +*/ +#if 0 + +#include "timeprofile.h" + +int main(int argc, char *argv[]) +{ + timeprofile_t *tp = timeprofile_create(); + + zarray_t *polya = g2d_polygon_create_data((float[][2]) { + { 0, 0}, + { 4, 0}, + { 2, 2}, + { 1, 2}, + { 1, 5}, + { -2,4} }, 6); + + zarray_t *polyb = g2d_polygon_create_data((float[][2]) { + { .1, .1}, + { .5, .1}, + { .1, .5 } }, 3); + + zarray_t *polyc = g2d_polygon_create_data((float[][2]) { + { 3, 0}, + { 5, 0}, + { 5, 1} }, 3); + + zarray_t *polyd = g2d_polygon_create_data((float[][2]) { + { 5, 5}, + { 6, 6}, + { 5, 6} }, 3); + +/* + 5 L---K + 4 |I--J + 3 |H-G + 2 |E-F + 1 |D--C + 0 A---B + 01234 +*/ + zarray_t *polyE = g2d_polygon_create_data((float[][2]) { + {0,0}, {4,0}, {4, 1}, {1,1}, + {1,2}, {3,2}, {3,3}, {1,3}, + {1,4}, {4,4}, {4,5}, {0,5}}, 12); + + srand(0); + + timeprofile_stamp(tp, "begin"); + + if (1) { + int niters = 100000; + + for (int i = 0; i < niters; i++) { + float q[2]; + q[0] = 10.0f * random() / RAND_MAX - 2; + q[1] = 10.0f * random() / RAND_MAX - 2; + + g2d_polygon_contains_point(polyE, q); + } + + timeprofile_stamp(tp, "fast"); + + for (int i = 0; i < niters; i++) { + float q[2]; + q[0] = 10.0f * random() / RAND_MAX - 2; + q[1] = 10.0f * random() / RAND_MAX - 2; + + g2d_polygon_contains_point_ref(polyE, q); + } + + timeprofile_stamp(tp, "slow"); + + for (int i = 0; i < niters; i++) { + float q[2]; + q[0] = 10.0f * random() / RAND_MAX - 2; + q[1] = 10.0f * random() / RAND_MAX - 2; + + int v0 = g2d_polygon_contains_point(polyE, q); + int v1 = g2d_polygon_contains_point_ref(polyE, q); + assert(v0 == v1); + } + + timeprofile_stamp(tp, "both"); + timeprofile_display(tp); + } + + if (1) { + zarray_t *poly = polyE; + + float res = 0.399; + for (float y = 5.2; y >= -.5; y -= res) { + float xs[zarray_size(poly)]; + + int xsz = g2d_polygon_rasterize(poly, y, xs); + int xpos = 0; + int inout = 0; // start off "out" + for (float x = -3; x < 6; x += res) { + while (x > xs[xpos] && xpos < xsz) { + xpos++; + inout ^= 1; + } + + if (inout) + printf("y"); + else + printf(" "); + } + printf("\n"); + + for (float x = -3; x < 6; x += res) { + float q[2] = {x, y}; + if (g2d_polygon_contains_point(poly, q)) + printf("X"); + else + printf(" "); + } + printf("\n"); + } + } + + + +/* +// CW order +float p[][2] = { { 0, 0}, +{ -2, 4}, +{1, 5}, +{1, 2}, +{2, 2}, +{4, 0} }; +*/ + + float q[2] = { 10, 10 }; + printf("0==%d\n", g2d_polygon_contains_point(polya, q)); + + q[0] = 1; q[1] = 1; + printf("1==%d\n", g2d_polygon_contains_point(polya, q)); + + q[0] = 3; q[1] = .5; + printf("1==%d\n", g2d_polygon_contains_point(polya, q)); + + q[0] = 1.2; q[1] = 2.1; + printf("0==%d\n", g2d_polygon_contains_point(polya, q)); + + printf("0==%d\n", g2d_polygon_contains_polygon(polya, polyb)); + + printf("0==%d\n", g2d_polygon_contains_polygon(polya, polyc)); + + printf("0==%d\n", g2d_polygon_contains_polygon(polya, polyd)); + + //////////////////////////////////////////////////////// + // Test convex hull + if (1) { + zarray_t *hull = g2d_convex_hull(polyE); + + for (int k = 0; k < zarray_size(hull); k++) { + float *h; + zarray_get_volatile(hull, k, &h); + + printf("%15f, %15f\n", h[0], h[1]); + } + } + + for (int i = 0; i < 100000; i++) { + zarray_t *points = zarray_create(sizeof(float[2])); + + for (int j = 0; j < 100; j++) { + float q[2]; + q[0] = 10.0f * random() / RAND_MAX - 2; + q[1] = 10.0f * random() / RAND_MAX - 2; + + zarray_add(points, q); + } + + zarray_t *hull = g2d_convex_hull(points); + for (int j = 0; j < zarray_size(points); j++) { + float *q; + zarray_get_volatile(points, j, &q); + + int on_edge; + + float p[2]; + g2d_polygon_closest_boundary_point(hull, q, p); + if (g2d_distance(q, p) < .00001) + on_edge = 1; + + assert(on_edge || g2d_polygon_contains_point(hull, q)); + } + + zarray_destroy(hull); + zarray_destroy(points); + } +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "image_types.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// to support conversions between different types, we define all image +// types at once. Type-specific implementations can then #include this +// file, assured that the basic types of each image are known. + +typedef struct image_u8 image_u8_t; +struct image_u8 +{ + int32_t width; + int32_t height; + int32_t stride; + + uint8_t *buf; +}; + +typedef struct image_u8x3 image_u8x3_t; +struct image_u8x3 +{ + const int32_t width; + const int32_t height; + const int32_t stride; // bytes per line + + uint8_t *buf; +}; + +typedef struct image_u8x4 image_u8x4_t; +struct image_u8x4 +{ + const int32_t width; + const int32_t height; + const int32_t stride; // bytes per line + + uint8_t *buf; +}; + +typedef struct image_f32 image_f32_t; +struct image_f32 +{ + const int32_t width; + const int32_t height; + const int32_t stride; // floats per line + + float *buf; // indexed as buf[y*stride + x] +}; + +typedef struct image_u32 image_u32_t; +struct image_u32 +{ + const int32_t width; + const int32_t height; + const int32_t stride; // int32_ts per line + + uint32_t *buf; // indexed as buf[y*stride + x] +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "apriltag_math.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// Computes the cholesky factorization of A, putting the lower +// triangular matrix into R. +static inline void mat33_chol(const float *A, + float *R) +{ + // A[0] = R[0]*R[0] + R[0] = sqrt(A[0]); + + // A[1] = R[0]*R[3]; + R[3] = A[1] / R[0]; + + // A[2] = R[0]*R[6]; + R[6] = A[2] / R[0]; + + // A[4] = R[3]*R[3] + R[4]*R[4] + R[4] = sqrt(A[4] - R[3]*R[3]); + + // A[5] = R[3]*R[6] + R[4]*R[7] + R[7] = (A[5] - R[3]*R[6]) / R[4]; + + // A[8] = R[6]*R[6] + R[7]*R[7] + R[8]*R[8] + R[8] = sqrt(A[8] - R[6]*R[6] - R[7]*R[7]); + + R[1] = 0; + R[2] = 0; + R[5] = 0; +} + +static inline void mat33_lower_tri_inv(const float *A, + float *R) +{ + // A[0]*R[0] = 1 + R[0] = 1 / A[0]; + + // A[3]*R[0] + A[4]*R[3] = 0 + R[3] = -A[3]*R[0] / A[4]; + + // A[4]*R[4] = 1 + R[4] = 1 / A[4]; + + // A[6]*R[0] + A[7]*R[3] + A[8]*R[6] = 0 + R[6] = (-A[6]*R[0] - A[7]*R[3]) / A[8]; + + // A[7]*R[4] + A[8]*R[7] = 0 + R[7] = -A[7]*R[4] / A[8]; + + // A[8]*R[8] = 1 + R[8] = 1 / A[8]; +} + + +static inline void mat33_sym_solve(const float *A, + const float *B, + float *R) +{ + float L[9]; + mat33_chol(A, L); + + float M[9]; + mat33_lower_tri_inv(L, M); + + float tmp[3]; + tmp[0] = M[0]*B[0]; + tmp[1] = M[3]*B[0] + M[4]*B[1]; + tmp[2] = M[6]*B[0] + M[7]*B[1] + M[8]*B[2]; + + R[0] = M[0]*tmp[0] + M[3]*tmp[1] + M[6]*tmp[2]; + R[1] = M[4]*tmp[1] + M[7]*tmp[2]; + R[2] = M[8]*tmp[2]; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "apriltag.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +struct quad +{ + float p[4][2]; // corners + + // H: tag coordinates ([-1,1] at the black corners) to pixels + // Hinv: pixels to tag + matd_t *H, *Hinv; +}; + +// Represents a tag family. Every tag belongs to a tag family. Tag +// families are generated by the Java tool +// april.tag.TagFamilyGenerator and can be converted to C using +// april.tag.TagToC. +typedef struct apriltag_family apriltag_family_t; +struct apriltag_family +{ + // How many codes are there in this tag family? + uint32_t ncodes; + + // how wide (in bit-sizes) is the black border? (usually 1) + uint32_t black_border; + + // how many bits tall and wide is it? (e.g. 36bit tag ==> 6) + uint32_t d; + + // minimum hamming distance between any two codes. (e.g. 36h11 => 11) + uint32_t h; + + // The codes in the family. + uint64_t codes[]; +}; + +struct apriltag_quad_thresh_params +{ + // reject quads containing too few pixels + int min_cluster_pixels; + + // how many corner candidates to consider when segmenting a group + // of pixels into a quad. + int max_nmaxima; + + // Reject quads where pairs of edges have angles that are close to + // straight or close to 180 degrees. Zero means that no quads are + // rejected. (In radians). + float critical_rad; + + // When fitting lines to the contours, what is the maximum mean + // squared error allowed? This is useful in rejecting contours + // that are far from being quad shaped; rejecting these quads "early" + // saves expensive decoding processing. + float max_line_fit_mse; + + // When we build our model of black & white pixels, we add an + // extra check that the white model must be (overall) brighter + // than the black model. How much brighter? (in pixel values, + // [0,255]). . + int min_white_black_diff; + + // should the thresholded image be deglitched? Only useful for + // very noisy images + int deglitch; +}; + +// Represents a detector object. Upon creating a detector, all fields +// are set to reasonable values, but can be overridden by accessing +// these fields. +typedef struct apriltag_detector apriltag_detector_t; +struct apriltag_detector +{ + /////////////////////////////////////////////////////////////// + // User-configurable parameters. + + // When non-zero, the edges of the each quad are adjusted to "snap + // to" strong gradients nearby. This is useful when decimation is + // employed, as it can increase the quality of the initial quad + // estimate substantially. Generally recommended to be on (1). + // + // Very computationally inexpensive. Option is ignored if + // quad_decimate = 1. + int refine_edges; + + // when non-zero, detections are refined in a way intended to + // increase the number of detected tags. Especially effective for + // very small tags near the resolution threshold (e.g. 10px on a + // side). + int refine_decode; + + // when non-zero, detections are refined in a way intended to + // increase the accuracy of the extracted pose. This is done by + // maximizing the contrast around the black and white border of + // the tag. This generally increases the number of successfully + // detected tags, though not as effectively (or quickly) as + // refine_decode. + // + // This option must be enabled in order for "goodness" to be + // computed. + int refine_pose; + + struct apriltag_quad_thresh_params qtp; + + /////////////////////////////////////////////////////////////// + // Statistics relating to last processed frame + + uint32_t nedges; + uint32_t nsegments; + uint32_t nquads; + + /////////////////////////////////////////////////////////////// + // Internal variables below + + // Not freed on apriltag_destroy; a tag family can be shared + // between multiple users. The user should ultimately destroy the + // tag family passed into the constructor. + zarray_t *tag_families; +}; + +// Represents the detection of a tag. These are returned to the user +// and must be individually destroyed by the user. +typedef struct apriltag_detection apriltag_detection_t; +struct apriltag_detection +{ + // a pointer for convenience. not freed by apriltag_detection_destroy. + apriltag_family_t *family; + + // The decoded ID of the tag + int id; + + // How many error bits were corrected? Note: accepting large numbers of + // corrected errors leads to greatly increased false positive rates. + // NOTE: As of this implementation, the detector cannot detect tags with + // a hamming distance greater than 2. + int hamming; + + // A measure of the quality of tag localization: measures the + // average contrast of the pixels around the border of the + // tag. refine_pose must be enabled, or this field will be zero. + float goodness; + + // A measure of the quality of the binary decoding process: the + // average difference between the intensity of a data bit versus + // the decision threshold. Higher numbers roughly indicate better + // decodes. This is a reasonable measure of detection accuracy + // only for very small tags-- not effective for larger tags (where + // we could have sampled anywhere within a bit cell and still + // gotten a good detection.) + float decision_margin; + + // The 3x3 homography matrix describing the projection from an + // "ideal" tag (with corners at (-1,-1), (1,-1), (1,1), and (-1, + // 1)) to pixels in the image. This matrix will be freed by + // apriltag_detection_destroy. + matd_t *H; + + // The center of the detection in image pixel coordinates. + float c[2]; + + // The corners of the tag in image pixel coordinates. These always + // wrap counter-clock wise around the tag. + float p[4][2]; +}; + +// don't forget to add a family! +apriltag_detector_t *apriltag_detector_create(); + +// add a family to the apriltag detector. caller still "owns" the family. +// a single instance should only be provided to one apriltag detector instance. +void apriltag_detector_add_family_bits(apriltag_detector_t *td, apriltag_family_t *fam, int bits_corrected); + +// Tunable, but really, 2 is a good choice. Values of >=3 +// consume prohibitively large amounts of memory, and otherwise +// you want the largest value possible. +static inline void apriltag_detector_add_family(apriltag_detector_t *td, apriltag_family_t *fam) +{ + apriltag_detector_add_family_bits(td, fam, 2); +} + +// does not deallocate the family. +void apriltag_detector_remove_family(apriltag_detector_t *td, apriltag_family_t *fam); + +// unregister all families, but does not deallocate the underlying tag family objects. +void apriltag_detector_clear_families(apriltag_detector_t *td); + +// Destroy the april tag detector (but not the underlying +// apriltag_family_t used to initialize it.) +void apriltag_detector_destroy(apriltag_detector_t *td); + +// Detect tags from an image and return an array of +// apriltag_detection_t*. You can use apriltag_detections_destroy to +// free the array and the detections it contains, or call +// _detection_destroy and zarray_destroy yourself. +zarray_t *apriltag_detector_detect(apriltag_detector_t *td, image_u8_t *im_orig); + +// Call this method on each of the tags returned by apriltag_detector_detect +void apriltag_detection_destroy(apriltag_detection_t *det); + +// destroys the array AND the detections within it. +void apriltag_detections_destroy(zarray_t *detections); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "tag16h5" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const apriltag_family_t tag16h5 = { + .ncodes = 30, + .black_border = 1, + .d = 4, + .h = 5, + .codes = { + 0x000000000000231bUL, + 0x0000000000002ea5UL, + 0x000000000000346aUL, + 0x00000000000045b9UL, + 0x00000000000079a6UL, + 0x0000000000007f6bUL, + 0x000000000000b358UL, + 0x000000000000e745UL, + 0x000000000000fe59UL, + 0x000000000000156dUL, + 0x000000000000380bUL, + 0x000000000000f0abUL, + 0x0000000000000d84UL, + 0x0000000000004736UL, + 0x0000000000008c72UL, + 0x000000000000af10UL, + 0x000000000000093cUL, + 0x00000000000093b4UL, + 0x000000000000a503UL, + 0x000000000000468fUL, + 0x000000000000e137UL, + 0x0000000000005795UL, + 0x000000000000df42UL, + 0x0000000000001c1dUL, + 0x000000000000e9dcUL, + 0x00000000000073adUL, + 0x000000000000ad5fUL, + 0x000000000000d530UL, + 0x00000000000007caUL, + 0x000000000000af2eUL + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "tag25h7" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const apriltag_family_t tag25h7 = { + .ncodes = 242, + .black_border = 1, + .d = 5, + .h = 7, + .codes = { + 0x00000000004b770dUL, + 0x00000000011693e6UL, + 0x0000000001a599abUL, + 0x0000000000c3a535UL, + 0x000000000152aafaUL, + 0x0000000000accd98UL, + 0x0000000001cad922UL, + 0x00000000002c2fadUL, + 0x0000000000bb3572UL, + 0x00000000014a3b37UL, + 0x000000000186524bUL, + 0x0000000000c99d4cUL, + 0x000000000023bfeaUL, + 0x000000000141cb74UL, + 0x0000000001d0d139UL, + 0x0000000001670aebUL, + 0x0000000000851675UL, + 0x000000000150334eUL, + 0x00000000006e3ed8UL, + 0x0000000000fd449dUL, + 0x0000000000aa55ecUL, + 0x0000000001c86176UL, + 0x00000000015e9b28UL, + 0x00000000007ca6b2UL, + 0x000000000147c38bUL, + 0x0000000001d6c950UL, + 0x00000000008b0e8cUL, + 0x00000000011a1451UL, + 0x0000000001562b65UL, + 0x00000000013f53c8UL, + 0x0000000000d58d7aUL, + 0x0000000000829ec9UL, + 0x0000000000faccf1UL, + 0x000000000136e405UL, + 0x00000000007a2f06UL, + 0x00000000010934cbUL, + 0x00000000016a8b56UL, + 0x0000000001a6a26aUL, + 0x0000000000f85545UL, + 0x000000000195c2e4UL, + 0x000000000024c8a9UL, + 0x00000000012bfc96UL, + 0x00000000016813aaUL, + 0x0000000001a42abeUL, + 0x0000000001573424UL, + 0x0000000001044573UL, + 0x0000000000b156c2UL, + 0x00000000005e6811UL, + 0x0000000001659bfeUL, + 0x0000000001d55a63UL, + 0x00000000005bf065UL, + 0x0000000000e28667UL, + 0x0000000001e9ba54UL, + 0x00000000017d7c5aUL, + 0x0000000001f5aa82UL, + 0x0000000001a2bbd1UL, + 0x00000000001ae9f9UL, + 0x0000000001259e51UL, + 0x000000000134062bUL, + 0x0000000000e1177aUL, + 0x0000000000ed07a8UL, + 0x000000000162be24UL, + 0x000000000059128bUL, + 0x0000000001663e8fUL, + 0x00000000001a83cbUL, + 0x000000000045bb59UL, + 0x000000000189065aUL, + 0x00000000004bb370UL, + 0x00000000016fb711UL, + 0x000000000122c077UL, + 0x0000000000eca17aUL, + 0x0000000000dbc1f4UL, + 0x000000000088d343UL, + 0x000000000058ac5dUL, + 0x0000000000ba02e8UL, + 0x00000000001a1d9dUL, + 0x0000000001c72eecUL, + 0x0000000000924bc5UL, + 0x0000000000dccab3UL, + 0x0000000000886d15UL, + 0x000000000178c965UL, + 0x00000000005bc69aUL, + 0x0000000001716261UL, + 0x000000000174e2ccUL, + 0x0000000001ed10f4UL, + 0x0000000000156aa8UL, + 0x00000000003e2a8aUL, + 0x00000000002752edUL, + 0x000000000153c651UL, + 0x0000000001741670UL, + 0x0000000000765b05UL, + 0x000000000119c0bbUL, + 0x000000000172a783UL, + 0x00000000004faca1UL, + 0x0000000000f31257UL, + 0x00000000012441fcUL, + 0x00000000000d3748UL, + 0x0000000000c21f15UL, + 0x0000000000ac5037UL, + 0x000000000180e592UL, + 0x00000000007d3210UL, + 0x0000000000a27187UL, + 0x00000000002beeafUL, + 0x000000000026ff57UL, + 0x0000000000690e82UL, + 0x000000000077765cUL, + 0x0000000001a9e1d7UL, + 0x000000000140be1aUL, + 0x0000000001aa1e3aUL, + 0x0000000001944f5cUL, + 0x00000000019b5032UL, + 0x0000000000169897UL, + 0x0000000001068eb9UL, + 0x0000000000f30dbcUL, + 0x000000000106a151UL, + 0x0000000001d53e95UL, + 0x0000000001348ceeUL, + 0x0000000000cf4fcaUL, + 0x0000000001728bb5UL, + 0x0000000000dc1eecUL, + 0x000000000069e8dbUL, + 0x00000000016e1523UL, + 0x000000000105fa25UL, + 0x00000000018abb0cUL, + 0x0000000000c4275dUL, + 0x00000000006d8e76UL, + 0x0000000000e8d6dbUL, + 0x0000000000e16fd7UL, + 0x0000000001ac2682UL, + 0x000000000077435bUL, + 0x0000000000a359ddUL, + 0x00000000003a9c4eUL, + 0x000000000123919aUL, + 0x0000000001e25817UL, + 0x000000000002a836UL, + 0x00000000001545a4UL, + 0x0000000001209c8dUL, + 0x0000000000bb5f69UL, + 0x0000000001dc1f02UL, + 0x00000000005d5f7eUL, + 0x00000000012d0581UL, + 0x00000000013786c2UL, + 0x0000000000e15409UL, + 0x0000000001aa3599UL, + 0x000000000139aad8UL, + 0x0000000000b09d2aUL, + 0x000000000054488fUL, + 0x00000000013c351cUL, + 0x0000000000976079UL, + 0x0000000000b25b12UL, + 0x0000000001addb34UL, + 0x0000000001cb23aeUL, + 0x0000000001175738UL, + 0x0000000001303bb8UL, + 0x0000000000d47716UL, + 0x000000000188ceeaUL, + 0x0000000000baf967UL, + 0x0000000001226d39UL, + 0x000000000135e99bUL, + 0x000000000034adc5UL, + 0x00000000002e384dUL, + 0x000000000090d3faUL, + 0x0000000000232713UL, + 0x00000000017d49b1UL, + 0x0000000000aa84d6UL, + 0x0000000000c2ddf8UL, + 0x0000000001665646UL, + 0x00000000004f345fUL, + 0x00000000002276b1UL, + 0x0000000001255dd7UL, + 0x00000000016f4cccUL, + 0x00000000004aaffcUL, + 0x0000000000c46da6UL, + 0x000000000085c7b3UL, + 0x0000000001311fcbUL, + 0x00000000009c6c4fUL, + 0x000000000187d947UL, + 0x00000000008578e4UL, + 0x0000000000e2bf0bUL, + 0x0000000000a01b4cUL, + 0x0000000000a1493bUL, + 0x00000000007ad766UL, + 0x0000000000ccfe82UL, + 0x0000000001981b5bUL, + 0x0000000001cacc85UL, + 0x0000000000562cdbUL, + 0x00000000015b0e78UL, + 0x00000000008f66c5UL, + 0x00000000003332bfUL, + 0x00000000012ce754UL, + 0x0000000000096a76UL, + 0x0000000001d5e3baUL, + 0x000000000027ea41UL, + 0x00000000014412dfUL, + 0x000000000067b9b4UL, + 0x0000000000daa51aUL, + 0x00000000001dcb17UL, + 0x00000000004d4afdUL, + 0x00000000006335d5UL, + 0x0000000000ee2334UL, + 0x00000000017d4e55UL, + 0x0000000001b8b0f0UL, + 0x00000000014999e3UL, + 0x0000000001513dfaUL, + 0x0000000000765cf2UL, + 0x000000000056af90UL, + 0x00000000012e16acUL, + 0x0000000001d3d86cUL, + 0x0000000000ff279bUL, + 0x00000000018822ddUL, + 0x000000000099d478UL, + 0x00000000008dc0d2UL, + 0x000000000034b666UL, + 0x0000000000cf9526UL, + 0x000000000186443dUL, + 0x00000000007a8e29UL, + 0x00000000019c6aa5UL, + 0x0000000001f2a27dUL, + 0x00000000012b2136UL, + 0x0000000000d0cd0dUL, + 0x00000000012cb320UL, + 0x00000000017ddb0bUL, + 0x000000000005353bUL, + 0x00000000015b2cafUL, + 0x0000000001e5a507UL, + 0x000000000120f1e5UL, + 0x000000000114605aUL, + 0x00000000014efe4cUL, + 0x0000000000568134UL, + 0x00000000011b9f92UL, + 0x000000000174d2a7UL, + 0x0000000000692b1dUL, + 0x000000000039e4feUL, + 0x0000000000aaff3dUL, + 0x000000000096224cUL, + 0x00000000013c9f77UL, + 0x000000000110ee8fUL, + 0x0000000000f17beaUL, + 0x000000000099fb5dUL, + 0x0000000000337141UL, + 0x000000000002b54dUL, + 0x0000000001233a70UL + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "tag25h9" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const apriltag_family_t tag25h9 = { + .ncodes = 35, + .black_border = 1, + .d = 5, + .h = 9, + .codes = { + 0x000000000155cbf1UL, + 0x0000000001e4d1b6UL, + 0x00000000017b0b68UL, + 0x0000000001eac9cdUL, + 0x00000000012e14ceUL, + 0x00000000003548bbUL, + 0x00000000007757e6UL, + 0x0000000001065dabUL, + 0x0000000001baa2e7UL, + 0x0000000000dea688UL, + 0x000000000081d927UL, + 0x000000000051b241UL, + 0x0000000000dbc8aeUL, + 0x0000000001e50e19UL, + 0x00000000015819d2UL, + 0x00000000016d8282UL, + 0x000000000163e035UL, + 0x00000000009d9b81UL, + 0x000000000173eec4UL, + 0x0000000000ae3a09UL, + 0x00000000005f7c51UL, + 0x0000000001a137fcUL, + 0x0000000000dc9562UL, + 0x0000000001802e45UL, + 0x0000000001c3542cUL, + 0x0000000000870fa4UL, + 0x0000000000914709UL, + 0x00000000016684f0UL, + 0x0000000000c8f2a5UL, + 0x0000000000833ebbUL, + 0x000000000059717fUL, + 0x00000000013cd050UL, + 0x0000000000fa0ad1UL, + 0x0000000001b763b0UL, + 0x0000000000b991ceUL + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "tag36h10" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const apriltag_family_t tag36h10 = { + .ncodes = 2320, + .black_border = 1, + .d = 6, + .h = 10, + .codes = { + 0x00000001ca92a687UL, + 0x000000020521ac4cUL, + 0x000000027a3fb7d6UL, + 0x00000002b4cebd9bUL, + 0x00000003647bceeaUL, + 0x000000039f0ad4afUL, + 0x00000003d999da74UL, + 0x000000044eb7e5feUL, + 0x0000000538f3fd12UL, + 0x00000005738302d7UL, + 0x000000065dbf19ebUL, + 0x000000070d6c2b3aUL, + 0x00000007f7a8424eUL, + 0x0000000832374813UL, + 0x000000086cc64dd8UL, + 0x00000008a755539dUL, + 0x00000009570264ecUL, + 0x0000000991916ab1UL, + 0x0000000a06af763bUL, + 0x0000000ab65c878aUL, + 0x0000000b2b7a9314UL, + 0x0000000b660998d9UL, + 0x0000000bdb27a463UL, + 0x0000000cc563bb77UL, + 0x0000000e24bdde15UL, + 0x0000000ed46aef64UL, + 0x0000000f4988faeeUL, + 0x000000006e5417c7UL, + 0x0000000158902edbUL, + 0x00000001cdae3a65UL, + 0x0000000242cc45efUL, + 0x000000027d5b4bb4UL, + 0x00000002b7ea5179UL, + 0x000000032d085d03UL, + 0x00000003679762c8UL, + 0x00000003a226688dUL, + 0x00000003dcb56e52UL, + 0x000000048c627fa1UL, + 0x00000005769e96b5UL, + 0x00000006264ba804UL, + 0x0000000660daadc9UL, + 0x00000006d5f8b953UL, + 0x000000074b16c4ddUL, + 0x00000007fac3d62cUL, + 0x000000091f8ef305UL, + 0x000000095a1df8caUL, + 0x0000000994acfe8fUL, + 0x0000000a09cb0a19UL, + 0x0000000a445a0fdeUL, + 0x0000000a7ee915a3UL, + 0x0000000ab9781b68UL, + 0x0000000af407212dUL, + 0x0000000b69252cb7UL, + 0x0000000c8df04990UL, + 0x0000000d3d9d5adfUL, + 0x0000000d782c60a4UL, + 0x0000000f12158907UL, + 0x00000001d0c9ce43UL, + 0x000000020b58d408UL, + 0x00000002f594eb1cUL, + 0x00000003a541fc6bUL, + 0x0000000454ef0dbaUL, + 0x000000053f2b24ceUL, + 0x0000000629673be2UL, + 0x000000074e3258bbUL, + 0x00000008ad8c7b59UL, + 0x00000009d2579832UL, + 0x0000000a8204a981UL, + 0x0000000af722b50bUL, + 0x0000000b6c40c095UL, + 0x0000000ba6cfc65aUL, + 0x0000000f15311ce5UL, + 0x00000000748b3f83UL, + 0x00000000af1a4548UL, + 0x00000000e9a94b0dUL, + 0x00000002be217935UL, + 0x00000003e2ec960eUL, + 0x00000004cd28ad22UL, + 0x0000000507b7b2e7UL, + 0x000000054246b8acUL, + 0x000000057cd5be71UL, + 0x00000006a1a0db4aUL, + 0x00000006dc2fe10fUL, + 0x0000000876190972UL, + 0x000000099ae4264bUL, + 0x0000000abfaf4324UL, + 0x0000000c9427714cUL, + 0x0000000d09457cd6UL, + 0x0000000d43d4829bUL, + 0x0000000ea32ea539UL, + 0x0000000f52dbb688UL, + 0x0000000161e2ea75UL, + 0x0000000286ae074eUL, + 0x000000066a2d6963UL, + 0x00000008b3c3a315UL, + 0x00000008ee52a8daUL, + 0x0000000a131dc5b3UL, + 0x0000000e6bbb3352UL, + 0x0000000f55f74a66UL, + 0x0000000005a45bb5UL, + 0x000000007ac2673fUL, + 0x00000001da1c89ddUL, + 0x0000000289c99b2cUL, + 0x00000003ae94b805UL, + 0x000000050deedaa3UL, + 0x00000005830ce62dUL, + 0x00000005bd9bebf2UL, + 0x0000000632b9f77cUL, + 0x00000006e26708cbUL, + 0x0000000841c12b69UL, + 0x000000092bfd427dUL, + 0x00000009668c4842UL, + 0x00000009dbaa53ccUL, + 0x0000000b007570a5UL, + 0x0000000b3b04766aUL, + 0x0000000c25408d7eUL, + 0x0000000ea965ccf5UL, + 0x0000000f93a1e409UL, + 0x00000000434ef558UL, + 0x00000001681a1231UL, + 0x00000001dd381dbbUL, + 0x0000000302033a94UL, + 0x000000075aa0a833UL, + 0x000000092f18d65bUL, + 0x00000009a436e1e5UL, + 0x0000000a1954ed6fUL, + 0x0000000b78af100dUL, + 0x0000000bb33e15d2UL, + 0x0000000c62eb2721UL, + 0x00000000466a8936UL, + 0x00000000f6179a85UL, + 0x000000016b35a60fUL, + 0x0000000589440de9UL, + 0x00000006738024fdUL, + 0x0000000847f85325UL, + 0x00000009e1e17b88UL, + 0x0000000acc1d929cUL, + 0x0000000b06ac9861UL, + 0x0000000d5042d213UL, + 0x0000000fd468118aUL, + 0x00000000f9332e63UL, + 0x0000000342c96815UL, + 0x000000037d586ddaUL, + 0x0000000551d09c02UL, + 0x00000005c6eea78cUL, + 0x00000006017dad51UL, + 0x00000009354ffe17UL, + 0x00000009aa6e09a1UL, + 0x0000000a94aa20b5UL, + 0x0000000acf39267aUL, + 0x0000000bb9753d8eUL, + 0x0000000bf4044353UL, + 0x000000008730b6b7UL, + 0x00000001716ccdcbUL, + 0x000000022119df1aUL, + 0x00000003f5920d42UL, + 0x000000058f7b35a5UL, + 0x00000006b446527eUL, + 0x0000000972fa97baUL, + 0x00000009e818a344UL, + 0x0000000a5d36aeceUL, + 0x0000000d1beaf40aUL, + 0x0000000dcb980559UL, + 0x0000000f65812dbcUL, + 0x0000000139f95be4UL, + 0x00000006b761e65cUL, + 0x00000006f1f0ec21UL, + 0x0000000a605242acUL, + 0x0000000e43d1a4c1UL, + 0x000000029c6f1260UL, + 0x0000000386ab2974UL, + 0x00000004e6054c12UL, + 0x00000008c984ae27UL, + 0x000000097931bf76UL, + 0x00000009ee4fcb00UL, + 0x0000000d22221bc6UL, + 0x0000000e46ed389fUL, + 0x0000000ebc0b4429UL, + 0x00000006f82813ddUL, + 0x0000000732b719a2UL, + 0x00000009072f47caUL, + 0x0000000941be4d8fUL, + 0x000000097c4d5354UL, + 0x00000009f16b5edeUL, + 0x0000000b8b548741UL, + 0x0000000d253dafa4UL, + 0x0000000d5fccb569UL, + 0x0000000d9a5bbb2eUL, + 0x0000000e4a08cc7dUL, + 0x00000003c77156f5UL, + 0x00000007aaf0b90aUL, + 0x0000000b53e1155aUL, + 0x0000000b8e701b1fUL, + 0x000000005c2b9448UL, + 0x000000014667ab5cUL, + 0x000000047a39fc22UL, + 0x00000004ef5807acUL, + 0x000000064eb22a4aUL, + 0x0000000982847b10UL, + 0x0000000aa74f97e9UL, + 0x0000000ae1de9daeUL, + 0x0000000b56fca938UL, + 0x0000000f750b1112UL, + 0x00000001bea14ac4UL, + 0x00000001f9305089UL, + 0x0000000233bf564eUL, + 0x000000031dfb6d62UL, + 0x00000003931978ecUL, + 0x000000052d02a14fUL, + 0x00000005a220acd9UL, + 0x0000000b1f893751UL, + 0x0000000fb2b5aab5UL, + 0x000000040b531854UL, + 0x00000005a53c40b7UL, + 0x00000008d90e917dUL, + 0x00000009139d9742UL, + 0x000000094e2c9d07UL, + 0x0000000d6c3b04e1UL, + 0x0000000e910621baUL, + 0x0000000f40b33309UL, + 0x00000001152b6131UL, + 0x000000032432951eUL, + 0x00000003d3dfa66dUL, + 0x000000065804e5e4UL, + 0x0000000ab0a25383UL, + 0x0000000b604f64d2UL, + 0x0000000de474a449UL, + 0x000000011846f50fUL, + 0x00000006d03e854cUL, + 0x00000007455c90d6UL, + 0x0000000ab3bde761UL, + 0x0000000dad013262UL, + 0x0000000e973d4976UL, + 0x00000002b54bb150UL, + 0x00000009577f58a1UL, + 0x00000009920e5e66UL, + 0x0000000b66868c8eUL, + 0x0000000f4a05eea3UL, + 0x00000003dd326207UL, + 0x00000005b1aa902fUL, + 0x000000099529f244UL, + 0x0000000b2f131aa7UL, + 0x0000000d038b48cfUL, + 0x0000000d3e1a4e94UL, + 0x000000024664cd82UL, + 0x000000036b2fea5bUL, + 0x000000095db6805dUL, + 0x0000000a0d6391acUL, + 0x0000000abd10a2fbUL, + 0x000000015f444a4cUL, + 0x00000002be9e6ceaUL, + 0x000000057d52b226UL, + 0x00000005f270bdb0UL, + 0x0000000b6fd94828UL, + 0x0000000879b19105UL, + 0x0000000d476d0a2eUL, + 0x0000000e6c382707UL, + 0x0000000dbfa6a996UL, + 0x00000001689705e6UL, + 0x00000003b22d3f98UL, + 0x0000000636527f0fUL, + 0x00000007d03ba772UL, + 0x0000000ee78d5a4dUL, + 0x0000000bf165a32aUL, + 0x0000000c2bf4a8efUL, + 0x0000000517be89f2UL, + 0x000000067718ac90UL, + 0x00000006b1a7b255UL, + 0x0000000726c5bddfUL, + 0x0000000bb9f23143UL, + 0x00000001375abbbbUL, + 0x0000000296b4de59UL, + 0x00000008893b745bUL, + 0x0000000a9842a848UL, + 0x0000000b827ebf5cUL, + 0x00000003840c894bUL, + 0x00000006b7deda11UL, + 0x0000000bc02958ffUL, + 0x000000055ba04b51UL, + 0x000000076aa77f3eUL, + 0x00000009b43db8f0UL, + 0x00000009eeccbeb5UL, + 0x0000000a295bc47aUL, + 0x0000000b4e26e153UL, + 0x0000000e476a2c54UL, + 0x00000006be1601cdUL, + 0x00000006f8a50792UL, + 0x000000097cca4709UL, + 0x0000000bc66080bbUL, + 0x00000001093a056eUL, + 0x00000006fbc09b70UL, + 0x0000000b8eed0ed4UL, + 0x0000000cee473172UL, + 0x000000023120b625UL, + 0x00000005da111275UL, + 0x0000000cf162c550UL, + 0x0000000ec8f68756UL, + 0x0000000b5db0c4a9UL, + 0x00000002b2ad1127UL, + 0x0000000536d2509eUL, + 0x00000009c9fec402UL, + 0x0000000c1394fdb4UL, + 0x000000006c326b53UL, + 0x00000005e99af5cbUL, + 0x0000000af1e574b9UL, + 0x0000000e6046cb44UL, + 0x0000000661d49533UL, + 0x00000008e5f9d4aaUL, + 0x0000000db3b54dd3UL, + 0x0000000e63625f22UL, + 0x0000000e9df164e7UL, + 0x0000000455e8f524UL, + 0x00000005b54317c2UL, + 0x0000000be258b389UL, + 0x000000054340a016UL, + 0x00000005b85eaba0UL, + 0x00000001284dcc1aUL, + 0x000000024d18e8f3UL, + 0x00000004d13e286aUL, + 0x00000008b4bd8a7fUL, + 0x0000000215a5770cUL, + 0x000000046572d87aUL, + 0x0000000c2c719ca4UL, + 0x00000004ddac77e2UL, + 0x0000000d19c94796UL, + 0x00000002d1c0d7d3UL, + 0x00000009ae8384e9UL, + 0x00000009e9128aaeUL, + 0x0000000ca7c6cfeaUL, + 0x0000000016282675UL, + 0x0000000ad985c97eUL, + 0x00000004af8bc195UL, + 0x00000009f580da26UL, + 0x0000000a6a9ee5b0UL, + 0x0000000bc9f9084eUL, + 0x0000000d63e230b1UL, + 0x0000000c4232a7b6UL, + 0x0000000d66fdc48fUL, + 0x0000000ec657e72dUL, + 0x0000000a364707a7UL, + 0x0000000f79208c5aUL, + 0x0000000de88a1f91UL, + 0x0000000574f9ddf6UL, + 0x000000065f35f50aUL, + 0x000000069ce08eadUL, + 0x0000000490f4ee9eUL, + 0x0000000c9282b88dUL, + 0x0000000752c4c7b8UL, + 0x0000000b364429cdUL, + 0x00000008b53a7e34UL, + 0x0000000be90ccefaUL, + 0x0000000b0507dfa2UL, + 0x000000000d525e90UL, + 0x00000005c549eecdUL, + 0x0000000e3bf5c446UL, + 0x0000000936c6d936UL, + 0x00000009747172d9UL, + 0x0000000ca843c39fUL, + 0x0000000d57f0d4eeUL, + 0x00000002d5595f66UL, + 0x0000000bfbb2462eUL, + 0x0000000266727b98UL, + 0x00000007ac679429UL, + 0x000000026fc53732UL, + 0x0000000656602d25UL, + 0x00000002eb1a6a78UL, + 0x00000004850392dbUL, + 0x0000000e5b098af2UL, + 0x0000000ab534c280UL, + 0x00000009ce143f4aUL, + 0x0000000f4b7cc9c2UL, + 0x0000000035b8e0d6UL, + 0x0000000871d5b08aUL, + 0x00000005b958930aUL, + 0x00000000b429a7faUL, + 0x000000054d8d431aUL, + 0x00000007d1b28291UL, + 0x0000000a1e645021UL, + 0x0000000b80da069dUL, + 0x0000000eef3b5d28UL, + 0x0000000263d3db6fUL, + 0x000000009592d503UL, + 0x00000004b9d86499UL, + 0x00000006c8df9886UL, + 0x0000000a3740ef11UL, + 0x0000000c4963b6dcUL, + 0x000000006da94672UL, + 0x000000053b64bf9bUL, + 0x0000000b2deb559dUL, + 0x0000000f116ab7b2UL, + 0x00000008ace1aa04UL, + 0x00000008ea8c43a7UL, + 0x00000006a4119dd3UL, + 0x000000099d54e8d4UL, + 0x0000000c969833d5UL, + 0x0000000f554c7911UL, + 0x00000003ade9e6b0UL, + 0x00000006e1bc3776UL, + 0x00000007916948c5UL, + 0x0000000dbe7ee48cUL, + 0x000000079484dca3UL, + 0x0000000f992e3a70UL, + 0x0000000884f81b73UL, + 0x0000000c68777d88UL, + 0x0000000603ee6fdaUL, + 0x0000000728b98cb3UL, + 0x0000000b12701684UL, + 0x0000000d5f21e414UL, + 0x0000000058652f15UL, + 0x00000002dc8a6e8cUL, + 0x00000004767396efUL, + 0x0000000b8dc549caUL, + 0x0000000f36b5a61aUL, + 0x00000000d09ece7dUL, + 0x0000000dda77175aUL, + 0x000000005e9c56d1UL, + 0x000000073e7a97c5UL, + 0x0000000b21f9f9daUL, + 0x0000000de3c9d2f4UL, + 0x000000069504ae32UL, + 0x000000077f40c546UL, + 0x0000000ed1217de6UL, + 0x00000003a1f88aedUL, + 0x0000000e623a9a18UL, + 0x00000000aeec67a8UL, + 0x0000000bea83aa19UL, + 0x000000092eeaf8bbUL, + 0x0000000a5d08d12eUL, + 0x0000000819a9bf38UL, + 0x0000000473d4f6c6UL, + 0x0000000b192431f5UL, + 0x0000000a6c92b484UL, + 0x00000007046885b5UL, + 0x0000000b9ab08cf7UL, + 0x0000000782d94cd9UL, + 0x0000000f158032faUL, + 0x0000000077f5e976UL, + 0x000000012dda2281UL, + 0x0000000e72417123UL, + 0x00000003056de487UL, + 0x0000000e3de9931aUL, + 0x0000000eb3079ea4UL, + 0x0000000e4420bad6UL, + 0x0000000439c2e4b6UL, + 0x000000047da4a615UL, + 0x00000000d7cfdda3UL, + 0x000000056afc5107UL, + 0x0000000e978c5f8bUL, + 0x00000005aede1266UL, + 0x0000000af1b79719UL, + 0x0000000f8b1b3239UL, + 0x000000075e8845dbUL, + 0x0000000bf1b4b93fUL, + 0x0000000fd5341b54UL, + 0x0000000a2373b2d3UL, + 0x00000005967e672bUL, + 0x0000000a2cc66e6dUL, + 0x0000000b17028581UL, + 0x0000000b54ad1f24UL, + 0x0000000e91d22b84UL, + 0x0000000de85c41f1UL, + 0x000000053d588e6fUL, + 0x0000000e9e407afcUL, + 0x0000000fc6272bb3UL, + 0x0000000a8ca0629aUL, + 0x0000000b86665d04UL, + 0x000000005a58fde9UL, + 0x00000001855b427eUL, + 0x0000000aabb42946UL, + 0x0000000e204ca78dUL, + 0x000000032897267bUL, + 0x00000000a78d7ae2UL, + 0x000000096536a598UL, + 0x0000000bf2aea0a9UL, + 0x00000000c9bcd56cUL, + 0x000000081eb921eaUL, + 0x00000002732fe125UL, + 0x00000002eb69808dUL, + 0x000000061f3bd153UL, + 0x00000008ddf0168fUL, + 0x0000000921d1d7eeUL, + 0x00000002bd48ca40UL, + 0x000000083ab154b8UL, + 0x00000005f436aee4UL, + 0x000000093dca0abcUL, + 0x000000026d75ad1eUL, + 0x0000000872a1ba54UL, + 0x0000000373a9f700UL, + 0x000000050d931f63UL, + 0x000000012d2f512cUL, + 0x0000000c6efdbb59UL, + 0x000000088e99ed22UL, + 0x0000000903b7f8acUL, + 0x0000000a9da1210fUL, + 0x0000000a2eba3d41UL, + 0x0000000fe9cd615cUL, + 0x0000000fb8911731UL, + 0x00000001cab3defcUL, + 0x0000000e6289b02dUL, + 0x000000066d6a35b6UL, + 0x0000000b0096a91aUL, + 0x0000000c9afcc532UL, + 0x000000080aebe5acUL, + 0x0000000d2f2e9768UL, + 0x0000000cc67edb56UL, + 0x0000000a51e37f35UL, + 0x00000006ac0eb6c3UL, + 0x00000006af2a4aa1UL, + 0x0000000e76290ecbUL, + 0x000000037e738db9UL, + 0x000000072d9b11c5UL, + 0x000000076e613f46UL, + 0x00000004f073278bUL, + 0x00000000e1eea307UL, + 0x0000000e9e8f9111UL, + 0x00000000793ee6f5UL, + 0x000000017304e15fUL, + 0x00000007a3361104UL, + 0x0000000731339958UL, + 0x00000008daa6a511UL, + 0x0000000a4037ef6bUL, + 0x0000000210896f2fUL, + 0x0000000afc535032UL, + 0x0000000e3025a0f8UL, + 0x000000063e21ba5fUL, + 0x00000003ebb5b8c8UL, + 0x0000000f9c6b06c3UL, + 0x0000000ca95ee37eUL, + 0x000000081f852bb4UL, + 0x0000000d6895d823UL, + 0x00000007040cca75UL, + 0x00000004d66ec391UL, + 0x00000004a216e588UL, + 0x000000051d6c18ceUL, + 0x000000047711c319UL, + 0x00000006ae7f794cUL, + 0x00000004abe694d7UL, + 0x0000000bc96f6f6eUL, + 0x000000057aa76cd2UL, + 0x0000000f948f2648UL, + 0x000000031bcd1bc3UL, + 0x000000094c7b3f1dUL, + 0x000000032eef86acUL, + 0x0000000668f8ff2eUL, + 0x00000006de170ab8UL, + 0x00000009341b93e2UL, + 0x0000000974e1c163UL, + 0x000000073182af6dUL, + 0x00000005d85fb48bUL, + 0x000000078b257bdeUL, + 0x0000000173d0eb29UL, + 0x00000005add785d1UL, + 0x0000000af6e83240UL, + 0x0000000dce79166cUL, + 0x000000022716840bUL, + 0x00000007b408f1d9UL, + 0x0000000de43a217eUL, + 0x000000036e10fb6eUL, + 0x0000000ea9a83ddfUL, + 0x00000004dcf50162UL, + 0x00000009aab07a8bUL, + 0x0000000281364431UL, + 0x00000008392dd46eUL, + 0x00000000945e6aceUL, + 0x00000008d07b3a82UL, + 0x0000000d012f1990UL, + 0x0000000b7098acc7UL, + 0x0000000c2fcfa16cUL, + 0x00000001e7c731a9UL, + 0x0000000f16ea68eeUL, + 0x00000002fa69cb03UL, + 0x00000004cee1f92bUL, + 0x00000001e20cfda2UL, + 0x00000009e9d1ef4dUL, + 0x0000000e83358a6dUL, + 0x000000059a873d48UL, + 0x0000000a6842b671UL, + 0x00000006885bdbefUL, + 0x000000073e4014faUL, + 0x00000007b9954840UL, + 0x0000000548157ffdUL, + 0x00000008853a8c5dUL, + 0x0000000eb8874fe0UL, + 0x0000000b25d4f257UL, + 0x000000036b447da5UL, + 0x000000071d87958fUL, + 0x0000000eedd91553UL, + 0x00000004af23612aUL, + 0x0000000278329eacUL, + 0x0000000191121b76UL, + 0x00000006e691175dUL, + 0x00000000bd140329UL, + 0x00000006ed4532ceUL, + 0x0000000d5e3c8ff4UL, + 0x0000000e26c64033UL, + 0x0000000494a2097bUL, + 0x0000000f5e36d440UL, + 0x0000000b7818d202UL, + 0x0000000a63548c34UL, + 0x0000000682f0bdfdUL, + 0x00000003d3c65c17UL, + 0x00000004c4399ae7UL, + 0x00000006b18e67ffUL, + 0x00000004778211a3UL, + 0x00000004089b2dd5UL, + 0x000000021edee850UL, + 0x0000000739cede72UL, + 0x0000000858dfc744UL, + 0x00000004bc5dba6cUL, + 0x000000021cbd3bdcUL, + 0x0000000ac83de313UL, + 0x00000006135f08daUL, + 0x0000000d3a3a9f0bUL, + 0x0000000eb2716099UL, + 0x000000040b88e413UL, + 0x0000000992442a25UL, + 0x0000000c639de695UL, + 0x0000000683bcc7c7UL, + 0x00000006245fc74fUL, + 0x0000000543766bd5UL, + 0x0000000375356569UL, + 0x0000000fa45b7a88UL, + 0x00000009f29b1207UL, + 0x0000000ba245457cUL, + 0x00000005b98e5ec9UL, + 0x0000000204aca6b6UL, + 0x0000000d52e9605bUL, + 0x0000000504a40d28UL, + 0x0000000ab6e1695eUL, + 0x0000000a26481ebbUL, + 0x00000009455ec341UL, + 0x00000002f05f98e9UL, + 0x0000000ead83365cUL, + 0x0000000b928d8486UL, + 0x00000007b860de0bUL, + 0x0000000964ef7da2UL, + 0x00000002422962b9UL, + 0x000000028f5ddfb2UL, + 0x00000008c5c63713UL, + 0x00000009068c6494UL, + 0x000000050aad5744UL, + 0x000000093e7cca30UL, + 0x00000009825e8b8fUL, + 0x00000003a8b4947dUL, + 0x0000000fa06737b5UL, + 0x0000000cb9c963e8UL, + 0x000000091a2bc332UL, + 0x0000000284666b59UL, + 0x0000000ceb82a1c8UL, + 0x0000000a2fe9f06aUL, + 0x000000006fa50365UL, + 0x000000019aa747faUL, + 0x0000000a6e117dc2UL, + 0x00000002b3810910UL, + 0x0000000ff7e857b2UL, + 0x0000000048b55c3eUL, + 0x0000000975456ac2UL, + 0x0000000ad200ed37UL, + 0x0000000e4d4d86efUL, + 0x0000000b3ec62491UL, + 0x00000008cd465c4eUL, + 0x00000008a2be2d94UL, + 0x00000005a9f7d648UL, + 0x000000059e067a85UL, + 0x0000000b5c35327eUL, + 0x0000000c4ca8714eUL, + 0x00000000e927a04cUL, + 0x0000000f71eac628UL, + 0x0000000109354e62UL, + 0x00000001037b1a5bUL, + 0x000000026c27f893UL, + 0x00000003597fa385UL, + 0x0000000ee24b62efUL, + 0x00000004b31f921cUL, + 0x0000000650244e5dUL, + 0x0000000cfec64526UL, + 0x0000000cd4bb0a21UL, + 0x0000000648c56197UL, + 0x0000000bd95056f8UL, + 0x0000000983c8c183UL, + 0x0000000ddc662f22UL, + 0x00000005631bb980UL, + 0x00000001203f56f3UL, + 0x00000006a0c37549UL, + 0x0000000a59baa8a4UL, + 0x00000006a23a5068UL, + 0x0000000ad609c354UL, + 0x0000000f6f6d5e74UL, + 0x000000036bc95f79UL, + 0x0000000424c92c62UL, + 0x00000003692abf50UL, + 0x00000000a4bc460dUL, + 0x00000001b4434b89UL, + 0x00000004eb31302dUL, + 0x00000009a3a891f9UL, + 0x000000093af8d5e7UL, + 0x0000000ef60bfa02UL, + 0x0000000607a378d6UL, + 0x00000005a5a7d835UL, + 0x0000000536c0f467UL, + 0x000000011f66a7feUL, + 0x000000074c7c43c5UL, + 0x000000066eae7c29UL, + 0x0000000f5a785d2cUL, + 0x0000000d3948a5c0UL, + 0x0000000ec1094aa4UL, + 0x0000000fcad61c19UL, + 0x000000049ca7108aUL, + 0x000000077437f4b6UL, + 0x0000000553083d4aUL, + 0x00000005c26c14cdUL, + 0x00000006eb4caceeUL, + 0x0000000e8aded63cUL, + 0x0000000132aa4b5cUL, + 0x000000057603a19eUL, + 0x0000000ee359dda3UL, + 0x0000000d0f5ea330UL, + 0x000000046b0f0b1fUL, + 0x0000000d47cbfc81UL, + 0x00000003ed1b37b0UL, + 0x00000004c8c752d8UL, + 0x0000000ac202044bUL, + 0x0000000207f16128UL, + 0x000000053f5c3981UL, + 0x000000070e1a33a2UL, + 0x0000000ed8348baaUL, + 0x0000000798f94a3eUL, + 0x00000008896c890eUL, + 0x0000000521425a3fUL, + 0x0000000c8329e9eaUL, + 0x000000041f238ba5UL, + 0x00000001093d3c81UL, + 0x0000000cab628e90UL, + 0x0000000a1b4bf356UL, + 0x000000092eee2fceUL, + 0x000000059d35b9afUL, + 0x00000002176e9f53UL, + 0x00000007c9abfb89UL, + 0x0000000b06d107e9UL, + 0x0000000e94c318d5UL, + 0x00000002f397ae30UL, + 0x0000000d7e5a1a48UL, + 0x000000098c4abc47UL, + 0x0000000574737c29UL, + 0x0000000d7f5401b2UL, + 0x0000000852b87bc6UL, + 0x00000001180fa957UL, + 0x0000000501c63328UL, + 0x0000000fd28c0d13UL, + 0x0000000f764aa079UL, + 0x0000000c8a6f8c5aUL, + 0x0000000975b10240UL, + 0x00000006bd33e4c0UL, + 0x0000000a1113e39cUL, + 0x0000000abcfa8fb8UL, + 0x0000000149ea1facUL, + 0x0000000f6443556fUL, + 0x0000000959da07e7UL, + 0x00000004721a2ac4UL, + 0x000000030bde0f15UL, + 0x0000000c7c4fdef8UL, + 0x0000000b7feb4465UL, + 0x000000056675073cUL, + 0x000000096f3f57b9UL, + 0x0000000876f0386eUL, + 0x0000000393f0a7e8UL, + 0x000000032b40ebd6UL, + 0x000000010a11346aUL, + 0x0000000fb81f48aeUL, + 0x00000000a892877eUL, + 0x000000086f636e08UL, + 0x00000004fbc4d72bUL, + 0x0000000561d5f314UL, + 0x0000000ca18e2835UL, + 0x00000007e6f519f5UL, + 0x00000008b94e7983UL, + 0x0000000619adfaf3UL, + 0x00000004c9ddbbabUL, + 0x0000000cb6a461f2UL, + 0x00000000f6e22456UL, + 0x0000000858c9b401UL, + 0x000000092dc1b3b8UL, + 0x00000003a783615bUL, + 0x0000000c74b66f67UL, + 0x0000000ea90891bcUL, + 0x000000031a829e4bUL, + 0x00000007bd38f505UL, + 0x0000000b4476ea80UL, + 0x00000001af371feaUL, + 0x000000084894ff56UL, + 0x00000000537584dfUL, + 0x0000000d4ebdd1d0UL, + 0x0000000b43cc192bUL, + 0x000000076700d287UL, + 0x0000000aaad9fa58UL, + 0x0000000fa511710fUL, + 0x00000006e54699e5UL, + 0x00000006d73391aeUL, + 0x00000003c2f1fb49UL, + 0x00000004fbd96a75UL, + 0x0000000252e6304bUL, + 0x0000000e72826214UL, + 0x00000008fe6c9336UL, + 0x0000000326397743UL, + 0x00000000e03bc524UL, + 0x0000000dedac9594UL, + 0x000000004ff19096UL, + 0x0000000409b4cdbbUL, + 0x0000000b15921888UL, + 0x0000000a259bcd6dUL, + 0x000000043c67f305UL, + 0x00000001dc65dcecUL, + 0x00000001730b4f85UL, + 0x0000000f8a48f16aUL, + 0x000000057a30e743UL, + 0x000000005afd9839UL, + 0x0000000682d5f3aeUL, + 0x00000000b694337eUL, + 0x0000000758c7dacfUL, + 0x000000018fa1ae7dUL, + 0x00000005ba9b5984UL, + 0x000000032e1d45ddUL, + 0x0000000672f05518UL, + 0x0000000382ffc5b1UL, + 0x00000008f0b08f33UL, + 0x0000000b4a4d9ff0UL, + 0x000000053ba0f980UL, + 0x00000002ac0751fbUL, + 0x0000000ab005de73UL, + 0x000000061ab7be9bUL, + 0x000000063078c9adUL, + 0x000000027659d148UL, + 0x0000000c653c684fUL, + 0x0000000ecee05017UL, + 0x000000024378ce5eUL, + 0x0000000d0f7e5bacUL, + 0x00000008b4bf4199UL, + 0x0000000b7aa495fbUL, + 0x0000000f3d6b78a5UL, + 0x000000098bab1024UL, + 0x00000006b4971fadUL, + 0x00000000723d6c89UL, + 0x0000000a17071a75UL, + 0x0000000d55a301f4UL, + 0x0000000988de925bUL, + 0x00000007ca276f45UL, + 0x0000000321b6e484UL, + 0x0000000316149ed6UL, + 0x0000000b8dba5bb9UL, + 0x0000000aad4df3f4UL, + 0x0000000178e204f5UL, + 0x000000095cd2e357UL, + 0x000000073fb8a733UL, + 0x000000084ca10c86UL, + 0x000000089498492dUL, + 0x00000005b9bdf383UL, + 0x000000066c58bb10UL, + 0x0000000f153ac21eUL, + 0x00000007ca5d3b04UL, + 0x0000000637a521c7UL, + 0x0000000b5ed589c1UL, + 0x0000000d0a3c644eUL, + 0x00000003d5d0754fUL, + 0x0000000c6b901174UL, + 0x00000003e96fd3edUL, + 0x000000079c2fdf8cUL, + 0x00000006594ae371UL, + 0x00000003504fd77aUL, + 0x000000032b81dcc7UL, + 0x000000057b4f3e35UL, + 0x00000004c9808072UL, + 0x0000000a06c10993UL, + 0x0000000b059666afUL, + 0x000000072b69c034UL, + 0x0000000e33ae836eUL, + 0x0000000ad6cadf0dUL, + 0x000000019573ace1UL, + 0x0000000d562fd1e7UL, + 0x0000000847804d9dUL, + 0x00000009e32f6734UL, + 0x00000002355c580fUL, + 0x00000002f177b8d6UL, + 0x0000000d689ac650UL, + 0x0000000071b70abcUL, + 0x0000000254915730UL, + 0x0000000c5934f949UL, + 0x0000000134d59d09UL, + 0x00000002c731fb06UL, + 0x0000000d90c6c5cbUL, + 0x0000000cc3f9bca4UL, + 0x0000000bf078980cUL, + 0x000000080838e95aUL, + 0x00000005ccd6f054UL, + 0x00000000378bae56UL, + 0x00000008d1ddb978UL, + 0x000000093b875cf4UL, + 0x0000000e2ce90bc6UL, + 0x0000000ec291b91bUL, + 0x0000000d0a11bdc1UL, + 0x0000000751be7244UL, + 0x0000000579fcd29eUL, + 0x0000000f76e5ccb1UL, + 0x0000000eb33da184UL, + 0x0000000c0ac75b0fUL, + 0x000000015454fb33UL, + 0x00000008b92a411cUL, + 0x00000007da34b476UL, + 0x00000004f413d45eUL, + 0x000000010ec1dbeaUL, + 0x0000000650739b93UL, + 0x0000000315e08a31UL, + 0x0000000d4fd5f1bdUL, + 0x0000000b5058a126UL, + 0x000000020d5cb63bUL, + 0x0000000c1598dfe7UL, + 0x0000000a0a2a338dUL, + 0x0000000e29af7686UL, + 0x000000083fd0cac9UL, + 0x000000014a8094d8UL, + 0x0000000816e0afa3UL, + 0x0000000499ef5d2cUL, + 0x0000000bddbd0d95UL, + 0x0000000a30839ca9UL, + 0x00000004fd33fb4cUL, + 0x0000000ef63557b7UL, + 0x0000000535f06ab2UL, + 0x00000000d47d352eUL, + 0x0000000a371e6dc4UL, + 0x00000008ac914b17UL, + 0x00000006ba9d69d7UL, + 0x0000000096da89aaUL, + 0x000000065bbd5d14UL, + 0x0000000d41d2c5c4UL, + 0x000000052a283583UL, + 0x00000004c1f56d26UL, + 0x000000086cd9984aUL, + 0x000000026cbcedc6UL, + 0x00000001aa0eaa03UL, + 0x0000000b95d5ad2cUL, + 0x0000000e2eb56b20UL, + 0x0000000ff49d9d5cUL, + 0x0000000cce338378UL, + 0x0000000330e9fac7UL, + 0x0000000e2f53974aUL, + 0x0000000668d1c6d5UL, + 0x0000000eca0ba751UL, + 0x00000008d48ab5e6UL, + 0x0000000d205e18cdUL, + 0x00000001c391633cUL, + 0x0000000ef5d02e5fUL, + 0x0000000d12bb5f20UL, + 0x0000000323215199UL, + 0x000000088f5b3ffcUL, + 0x0000000931445f29UL, + 0x0000000b893cb727UL, + 0x000000032851ecc0UL, + 0x000000080b44d81bUL, + 0x00000005aa48da98UL, + 0x000000046d1e1284UL, + 0x00000004c837ba14UL, + 0x0000000eb22c26deUL, + 0x0000000e51e9d246UL, + 0x00000008d03deee6UL, + 0x00000005af8e0909UL, + 0x0000000bde9773a4UL, + 0x0000000bf611cabfUL, + 0x0000000d24ac96e7UL, + 0x00000009fe919318UL, + 0x000000050d0206a6UL, + 0x0000000b43b9741cUL, + 0x0000000ba48d4fb3UL, + 0x00000006bccd7290UL, + 0x00000008bc6bfb9cUL, + 0x0000000e5a036c9fUL, + 0x0000000a80a2cfeeUL, + 0x0000000c193655a7UL, + 0x00000007c8e5170dUL, + 0x00000006141edbbbUL, + 0x00000004d6b990dcUL, + 0x0000000cc49b5702UL, + 0x00000002343fef58UL, + 0x0000000d50cb593cUL, + 0x00000004248a60cdUL, + 0x0000000901cfbd4cUL, + 0x000000064a4c8736UL, + 0x00000001b2dcbaeaUL, + 0x0000000d691e5f4cUL, + 0x0000000df352a493UL, + 0x00000001991ac7daUL, + 0x00000004c4879f45UL, + 0x00000009b34aadeeUL, + 0x000000052bb3db0dUL, + 0x00000007b9a8c9d3UL, + 0x0000000d7ce6e47eUL, + 0x0000000ec0b922d8UL, + 0x00000008079cab6bUL, + 0x0000000abadc8899UL, + 0x00000000f57b93b7UL, + 0x000000005c4ef219UL, + 0x0000000d7a438d49UL, + 0x0000000f55ecca97UL, + 0x0000000d07899f1dUL, + 0x0000000260947d6cUL, + 0x0000000ffbd21ab6UL, + 0x0000000d04ff923eUL, + 0x0000000964b72033UL, + 0x000000031ac3fd7eUL, + 0x0000000d2c52e2c4UL, + 0x0000000799a640efUL, + 0x000000098dd061edUL, + 0x00000005cb2ab7b8UL, + 0x000000072f3881c8UL, + 0x0000000e65ed1164UL, + 0x000000034fa0bd5bUL, + 0x000000064f9823cdUL, + 0x00000003797e1ac0UL, + 0x00000002fb8a4751UL, + 0x00000006f347342eUL, + 0x000000022dd7ea0aUL, + 0x0000000b19b65e57UL, + 0x000000044fe83e8aUL, + 0x000000007732732eUL, + 0x000000064de20ed7UL, + 0x000000006c9ea834UL, + 0x00000008ce066650UL, + 0x0000000c2a685ff0UL, + 0x000000064f19b01fUL, + 0x0000000491ab8a88UL, + 0x000000041212fe5aUL, + 0x00000006f9916f3bUL, + 0x0000000694f72e71UL, + 0x0000000ad7a5b35eUL, + 0x0000000f62795292UL, + 0x0000000c8cdc3d3aUL, + 0x0000000fbc6b3518UL, + 0x000000067b631901UL, + 0x00000005b5ba79d5UL, + 0x0000000f4fadebddUL, + 0x0000000ac7c802e7UL, + 0x0000000385712d9dUL, + 0x000000064bd375b4UL, + 0x0000000c9a11df70UL, + 0x000000088355bf31UL, + 0x0000000606ffbb0aUL, + 0x0000000bda93c2d5UL, + 0x00000007c5f94f0aUL, + 0x000000076fe26501UL, + 0x00000005d8b9153cUL, + 0x0000000886bbb218UL, + 0x0000000acee2fecaUL, + 0x00000002ad19a925UL, + 0x000000083b97855cUL, + 0x0000000d36608312UL, + 0x00000008ac60dbc7UL, + 0x00000000885c8f58UL, + 0x00000008abbdf891UL, + 0x0000000ea1602271UL, + 0x0000000ad654fee1UL, + 0x00000006c461195eUL, + 0x00000005eeb1a327UL, + 0x000000018d743962UL, + 0x00000001fc7c55a5UL, + 0x0000000aba749670UL, + 0x00000009c9a59c60UL, + 0x00000006e5bafc06UL, + 0x000000096977db12UL, + 0x0000000a97b6ebfaUL, + 0x000000063d2d9da6UL, + 0x0000000fab00cd60UL, + 0x0000000d7bdf4632UL, + 0x0000000f83878d59UL, + 0x0000000b1c2c462eUL, + 0x000000014e5144a7UL, + 0x0000000f4a909b28UL, + 0x0000000e979a185bUL, + 0x0000000908090a64UL, + 0x000000099eccd798UL, + 0x0000000348780a96UL, + 0x0000000fdc7ad169UL, + 0x0000000a600c2e5bUL, + 0x0000000b0968cd98UL, + 0x00000001a45ec098UL, + 0x000000099118c1b4UL, + 0x00000008afa5cd5aUL, + 0x00000001db7e655eUL, + 0x00000009f637e452UL, + 0x00000009568504e3UL, + 0x0000000045b2a662UL, + 0x0000000f2a1455a2UL, + 0x00000006c1ca9e75UL, + 0x000000030a4a4639UL, + 0x0000000c6c2c1a30UL, + 0x000000087500b452UL, + 0x00000005e338bb2eUL, + 0x0000000d9dd11dffUL, + 0x00000008c4b5d012UL, + 0x00000008191194e0UL, + 0x0000000dd11db867UL, + 0x0000000c67c151ceUL, + 0x00000005cb1a00e4UL, + 0x0000000098b7a1c6UL, + 0x0000000369f35cd4UL, + 0x0000000ca2190bdbUL, + 0x00000006e14bb3b9UL, + 0x00000008d5692f8cUL, + 0x0000000ca4b2f4f8UL, + 0x0000000787f06877UL, + 0x00000008acbb8550UL, + 0x0000000535f4b56aUL, + 0x0000000f4caf7ecbUL, + 0x0000000d4615b258UL, + 0x0000000347ca7070UL, + 0x00000003c798c85dUL, + 0x0000000460506465UL, + 0x0000000870d0a5dcUL, + 0x00000006510b2464UL, + 0x0000000d1dba5544UL, + 0x0000000d57789a33UL, + 0x0000000e2417c5baUL, + 0x0000000b5ff8628cUL, + 0x0000000a3bb22787UL, + 0x0000000a16b64f34UL, + 0x0000000421e81d3dUL, + 0x000000035b4596a7UL, + 0x00000008d7a2dd7eUL, + 0x000000050b2d83faUL, + 0x00000009ea87e7c2UL, + 0x0000000d5055e752UL, + 0x0000000f96aa9da5UL, + 0x0000000b096e2a07UL, + 0x000000049970b44bUL, + 0x0000000867fb1518UL, + 0x00000005d0f5dba2UL, + 0x00000001b191d11eUL, + 0x00000008e839bb8fUL, + 0x00000001cd4aca15UL, + 0x0000000971ec5615UL, + 0x00000007d72a7ebdUL, + 0x00000008b1253bfbUL, + 0x0000000e11de1d25UL, + 0x00000000a7566839UL, + 0x0000000f4f3542e0UL, + 0x00000001ea791e32UL, + 0x000000032a84f759UL, + 0x0000000646f1844eUL, + 0x000000042af26809UL, + 0x00000001f4b464ffUL, + 0x0000000da684d2d9UL, + 0x0000000d854f5fb9UL, + 0x00000004d4d3e91aUL, + 0x00000005af3ef4e2UL, + 0x00000008a1ef5ce7UL, + 0x00000002354febf3UL, + 0x0000000b3c5a8944UL, + 0x000000098b62a144UL, + 0x00000009bdba0b4eUL, + 0x000000004aa99b42UL, + 0x00000008099ea151UL, + 0x00000002185463a3UL, + 0x0000000b0a1ae997UL, + 0x0000000e628d5770UL, + 0x0000000b40b5ac89UL, + 0x000000027213b17dUL, + 0x00000004d21db5b5UL, + 0x000000010d0748f7UL, + 0x00000002276c7876UL, + 0x0000000b98bee56dUL, + 0x0000000bd1ca6ae8UL, + 0x0000000824ab48faUL, + 0x0000000c6f35ae62UL, + 0x00000003547a563cUL, + 0x0000000f1fc0d824UL, + 0x000000058f55ed75UL, + 0x0000000aa9d0de01UL, + 0x00000004719dde60UL, + 0x0000000d5386b3ddUL, + 0x00000004d8d9f666UL, + 0x0000000aee36013bUL, + 0x0000000ba4ee322fUL, + 0x0000000898d2db4eUL, + 0x00000009fe364808UL, + 0x0000000bb13e8045UL, + 0x0000000be346d43aUL, + 0x0000000b4c9f886fUL, + 0x0000000c9a6f53b8UL, + 0x00000000ed5a7b6fUL, + 0x00000002a1fac740UL, + 0x0000000b8c134a59UL, + 0x0000000b1f773993UL, + 0x0000000c4d9d0025UL, + 0x0000000ca905bdcaUL, + 0x00000003150a39a7UL, + 0x0000000e8329fad5UL, + 0x0000000bd4f98059UL, + 0x00000003bc5cf6cdUL, + 0x0000000c982fdd03UL, + 0x00000000a372de28UL, + 0x000000073fe2e35aUL, + 0x00000000b9f684ecUL, + 0x0000000c543ff680UL, + 0x00000001bcf5f09aUL, + 0x000000051b2a8099UL, + 0x0000000ee53277c2UL, + 0x00000000b3835a6cUL, + 0x0000000aed6765c1UL, + 0x000000092cfd64c8UL, + 0x0000000d20c60ed2UL, + 0x000000059dbd9f51UL, + 0x0000000b6acb694bUL, + 0x0000000427dcd5fdUL, + 0x0000000646336a75UL, + 0x00000008008dea4dUL, + 0x00000000af2bdc7cUL, + 0x0000000b8a46478aUL, + 0x0000000b02c535b6UL, + 0x0000000c645d8631UL, + 0x0000000044b4af3dUL, + 0x0000000c9edfe6cbUL, + 0x000000032ac8ea2aUL, + 0x000000079266a23fUL, + 0x0000000c2d902e93UL, + 0x00000006ae5cfbdbUL, + 0x00000002c66c633eUL, + 0x0000000eb7a8a4e3UL, + 0x0000000cb17281cfUL, + 0x00000007ca378680UL, + 0x00000007ac81509dUL, + 0x0000000a59a05073UL, + 0x0000000c9cb9f18dUL, + 0x0000000b78100d29UL, + 0x0000000fab49420aUL, + 0x0000000d0a4e69c4UL, + 0x0000000d6c33f722UL, + 0x000000068d21bff8UL, + 0x00000001fdad8ca3UL, + 0x00000002884d6968UL, + 0x0000000b091ff264UL, + 0x0000000eb5fb236fUL, + 0x0000000a3d2a1839UL, + 0x0000000527db0bc8UL, + 0x00000002dc68cd9fUL, + 0x0000000e3f4ea98aUL, + 0x0000000a629fe44fUL, + 0x0000000b73bd7d66UL, + 0x00000002abfd7b6bUL, + 0x00000001b4056054UL, + 0x0000000d6efaac28UL, + 0x00000000d13cc950UL, + 0x0000000ef84ead94UL, + 0x00000005b6ee0d50UL, + 0x00000000f4bec692UL, + 0x0000000de1b98881UL, + 0x000000055ccccd31UL, + 0x0000000086d9b84dUL, + 0x00000005ab736e3dUL, + 0x0000000167d2f005UL, + 0x0000000118ed1522UL, + 0x000000038bbdc903UL, + 0x000000039cd31ac2UL, + 0x000000031091bc51UL, + 0x0000000d66a87d3fUL, + 0x0000000afdade6d3UL, + 0x00000002bd1fe097UL, + 0x00000005cf545dd2UL, + 0x00000005e0af578eUL, + 0x00000006fe6dd4c9UL, + 0x0000000862bc8fcaUL, + 0x0000000cbce0b4c6UL, + 0x000000008b7fa8ddUL, + 0x00000003d108ae9fUL, + 0x0000000fed2d914aUL, + 0x0000000bab304bd8UL, + 0x0000000debe74f8dUL, + 0x00000001e857e3dcUL, + 0x0000000570340581UL, + 0x0000000114bbf4f5UL, + 0x0000000a3cfc0566UL, + 0x00000004026cd686UL, + 0x0000000266fb76cdUL, + 0x0000000b715773bbUL, + 0x00000002fd2785fdUL, + 0x0000000481b34cadUL, + 0x000000011c58d2baUL, + 0x00000003a5186f4dUL, + 0x0000000da55ab71cUL, + 0x0000000ac887db92UL, + 0x00000009bd6d5592UL, + 0x000000045857d12aUL, + 0x00000008c862f0b9UL, + 0x0000000870c88666UL, + 0x00000004a4f4901fUL, + 0x0000000774a993d0UL, + 0x0000000c9f16c81dUL, + 0x0000000eb415e9efUL, + 0x0000000307aa6302UL, + 0x0000000a246f21eeUL, + 0x00000001a4f8a9c2UL, + 0x00000000cf09f9b4UL, + 0x0000000db30dbb49UL, + 0x00000003581be36fUL, + 0x00000006919a4318UL, + 0x00000008ee677afdUL, + 0x00000005944b9d59UL, + 0x00000008d5fe61aaUL, + 0x000000077c174b1dUL, + 0x00000005cff8fa10UL, + 0x0000000c1ce82f48UL, + 0x00000007fbb18e65UL, + 0x00000000b6737103UL, + 0x0000000e2d30a9b6UL, + 0x00000006481ff469UL, + 0x00000005834b4d26UL, + 0x00000003bba517d5UL, + 0x0000000eee6e8080UL, + 0x00000005fe4fea5eUL, + 0x0000000e84e94c8cUL, + 0x0000000ba2ad0a2aUL, + 0x0000000a7f2aead0UL, + 0x000000063cecb46dUL, + 0x00000008943d7229UL, + 0x00000001d3878b2bUL, + 0x0000000f2b4efe94UL, + 0x0000000d9af1949dUL, + 0x0000000bb5824d39UL, + 0x0000000b8d8f5090UL, + 0x0000000ed5e19d08UL, + 0x000000060287437eUL, + 0x00000008fe6ae5c2UL, + 0x00000006c85ac058UL, + 0x0000000b906be1b8UL, + 0x0000000f9d423f65UL, + 0x00000006efed81d6UL, + 0x0000000781b67fa2UL, + 0x0000000e1dd437acUL, + 0x00000007a9201a8cUL, + 0x0000000fb444c819UL, + 0x0000000ce75af959UL, + 0x000000086df6e72bUL, + 0x0000000756695aa7UL, + 0x0000000b7b2bddf2UL, + 0x0000000f19a1b99eUL, + 0x00000009a5790e90UL, + 0x00000001d3b3eac0UL, + 0x0000000a5c5d9d2bUL, + 0x0000000152850218UL, + 0x0000000025c4ba6eUL, + 0x0000000d4a5f4bebUL, + 0x0000000709cec10eUL, + 0x000000094ddbdb6cUL, + 0x00000009d1218277UL, + 0x00000006190ca34aUL, + 0x0000000468ed6a3fUL, + 0x0000000801bda52eUL, + 0x0000000261b3f1a9UL, + 0x00000000b3494d9bUL, + 0x0000000583e2d7e5UL, + 0x00000009407a80f2UL, + 0x000000058e902456UL, + 0x00000009108c2273UL, + 0x000000059778ff8cUL, + 0x0000000d6ce05028UL, + 0x00000000286adc62UL, + 0x00000007ed3060dcUL, + 0x000000057b7e03edUL, + 0x00000003e3dce5c1UL, + 0x00000001bebc2295UL, + 0x0000000014a17c9aUL, + 0x0000000c7d90fbdaUL, + 0x00000008158ae35aUL, + 0x000000069d70a335UL, + 0x0000000d3ef97931UL, + 0x00000005793efb7aUL, + 0x0000000e6989ef43UL, + 0x0000000cd15f0116UL, + 0x0000000f9dbc6e25UL, + 0x0000000da4a91117UL, + 0x0000000054d0917aUL, + 0x000000060f2c3f15UL, + 0x00000007393b0a66UL, + 0x00000006630ed79bUL, + 0x0000000ed8589c60UL, + 0x00000007db37ab26UL, + 0x0000000c4631e80aUL, + 0x00000001badaf501UL, + 0x00000009bdef764dUL, + 0x0000000dd0949b4bUL, + 0x000000086f116771UL, + 0x0000000acd7ea109UL, + 0x00000007cc9d2f6bUL, + 0x00000003f5598822UL, + 0x00000004ba5a8d0cUL, + 0x000000066e7f9c42UL, + 0x000000033127fb36UL, + 0x00000000c85ff976UL, + 0x00000009dbb32ddfUL, + 0x00000003d06c7a56UL, + 0x0000000ac07601ddUL, + 0x00000005fda3d7e9UL, + 0x000000040a47aef0UL, + 0x0000000139928cd0UL, + 0x0000000183ab75ebUL, + 0x00000009dd6d1f4bUL, + 0x0000000954afec44UL, + 0x000000029953fe22UL, + 0x0000000f947e49b1UL, + 0x0000000a74266cb0UL, + 0x00000003bbb7fdabUL, + 0x00000008a72b63d1UL, + 0x00000008763e2fbbUL, + 0x00000008c9b4f9a2UL, + 0x0000000a35f5a861UL, + 0x000000099e54752cUL, + 0x00000002fdb8e16fUL, + 0x00000002d083ed68UL, + 0x0000000a05d36c5eUL, + 0x00000005460842feUL, + 0x0000000173ae0ee6UL, + 0x000000038b3c62e5UL, + 0x0000000476c1ae99UL, + 0x00000009a8cb898aUL, + 0x000000019d4032acUL, + 0x0000000a9c01d80bUL, + 0x0000000ca7d5e4deUL, + 0x0000000295d53115UL, + 0x0000000b26740e51UL, + 0x0000000bf21b0988UL, + 0x0000000167391c15UL, + 0x0000000d10af35c6UL, + 0x0000000d94750799UL, + 0x0000000cb986d117UL, + 0x000000001dddf588UL, + 0x000000071ed85f46UL, + 0x0000000a5437d58fUL, + 0x00000004029d1e25UL, + 0x0000000c580ec972UL, + 0x00000006847df8baUL, + 0x0000000e294d997bUL, + 0x0000000e2e8b10eeUL, + 0x00000001593103ddUL, + 0x0000000222103857UL, + 0x00000001e035591dUL, + 0x0000000b5c9ef2e9UL, + 0x00000009f815ec3eUL, + 0x0000000d1da2a021UL, + 0x000000054f171191UL, + 0x0000000e51f4a05eUL, + 0x0000000c15e7d603UL, + 0x0000000ba7f16b87UL, + 0x000000080b7a83e1UL, + 0x0000000720e2b18dUL, + 0x00000005ec0c069dUL, + 0x0000000a4f9f689cUL, + 0x00000005871cafdaUL, + 0x0000000c913140a2UL, + 0x00000007a8f2efd1UL, + 0x000000077064952cUL, + 0x00000004ea2d857fUL, + 0x0000000484523555UL, + 0x000000054971a9e3UL, + 0x0000000eb0694eb2UL, + 0x0000000b513c8e63UL, + 0x00000005c910db58UL, + 0x0000000ca87a4dd7UL, + 0x0000000b8ca63158UL, + 0x0000000b4b09431dUL, + 0x00000003dc9d50b7UL, + 0x00000007d57f02acUL, + 0x00000005c595b1b2UL, + 0x00000009e0caf698UL, + 0x0000000136b48555UL, + 0x0000000687dbcc2bUL, + 0x000000054bae2294UL, + 0x00000006899bbd7bUL, + 0x00000008108f46deUL, + 0x00000001dbe8cf08UL, + 0x0000000a02e1ae1dUL, + 0x00000000f5f26d59UL, + 0x0000000805cf202bUL, + 0x0000000afede5687UL, + 0x00000001583d5b30UL, + 0x0000000da9ed0620UL, + 0x0000000cf1237338UL, + 0x00000003a5a77bc4UL, + 0x0000000a17ffa0c6UL, + 0x000000029de4c387UL, + 0x000000007825d431UL, + 0x000000002d7b9b38UL, + 0x00000008ed0f26aaUL, + 0x000000056e54e30dUL, + 0x00000009620ab0e7UL, + 0x0000000c7e3ea94cUL, + 0x0000000d288a41e2UL, + 0x0000000f68884f1eUL, + 0x00000005ee02df09UL, + 0x0000000c02dbf645UL, + 0x0000000eac4c2424UL, + 0x0000000cab2d51e1UL, + 0x0000000037439577UL, + 0x00000005618ada43UL, + 0x00000002683b5859UL, + 0x00000008a607c1ceUL, + 0x0000000795fd9198UL, + 0x0000000b3edb11b8UL, + 0x0000000846939c5cUL, + 0x00000008b1f6fa23UL, + 0x0000000b1a2f2bfeUL, + 0x0000000b63a07ad7UL, + 0x00000005f8ea7b00UL, + 0x00000004ee9c6d0cUL, + 0x0000000990f2889bUL, + 0x0000000b7f7251d0UL, + 0x0000000ac3291369UL, + 0x00000009d8f36a7bUL, + 0x0000000d57342897UL, + 0x0000000efca98365UL, + 0x0000000dacc69f0eUL, + 0x00000003a70e4b3cUL, + 0x00000001e95c34c2UL, + 0x00000004caab6c06UL, + 0x00000007231f6ee1UL, + 0x000000037909aa04UL, + 0x0000000048c9a9ccUL, + 0x000000059cd081bcUL, + 0x00000004dd78c2e4UL, + 0x00000004979da10fUL, + 0x000000004749d0c5UL, + 0x0000000a17a4283bUL, + 0x0000000de7e1d52dUL, + 0x00000000e47cedf1UL, + 0x00000004fa48cbffUL, + 0x0000000545a932a0UL, + 0x00000006c2bd9eb8UL, + 0x0000000dd9bd3b8cUL, + 0x000000043332c1baUL, + 0x0000000501fa761dUL, + 0x00000007ec40adbbUL, + 0x00000004049f2b33UL, + 0x0000000cde28f57bUL, + 0x0000000f68c804b9UL, + 0x00000008f50fbd3eUL, + 0x000000054e1bc344UL, + 0x000000036b26e3a2UL, + 0x000000002e5ac9b1UL, + 0x000000010837858dUL, + 0x00000006ccac9e0bUL, + 0x0000000625ba8a52UL, + 0x0000000ac4c8b45cUL, + 0x0000000868678237UL, + 0x00000004187235feUL, + 0x0000000bd62663ceUL, + 0x0000000ea832dfb2UL, + 0x0000000d5a72f0a7UL, + 0x00000000659c855eUL, + 0x0000000bea7f5e48UL, + 0x0000000ff9566715UL, + 0x00000001bd06d99aUL, + 0x00000009666c578cUL, + 0x0000000c6527d3ecUL, + 0x0000000b541f3c61UL, + 0x0000000678a9ad70UL, + 0x000000036eaadfa3UL, + 0x0000000af74b01deUL, + 0x000000054cc3cdc3UL, + 0x0000000d2e587ce6UL, + 0x00000008694b9349UL, + 0x0000000d309898feUL, + 0x00000005c3250e09UL, + 0x000000084dcac28eUL, + 0x0000000f72add2dfUL, + 0x00000001901681a3UL, + 0x000000009e6a8fd4UL, + 0x000000012f614cd1UL, + 0x00000006d7801ac4UL, + 0x000000014cf1ca54UL, + 0x000000012a7eb608UL, + 0x00000005e7a3bf62UL, + 0x00000000ba5056a2UL, + 0x00000005bee44c9bUL, + 0x0000000819d7dc86UL, + 0x0000000062adc8fdUL, + 0x0000000bd3155d41UL, + 0x0000000cd8c6b38aUL, + 0x0000000e320fd50eUL, + 0x0000000e189d6655UL, + 0x00000006863c2831UL, + 0x00000000d2b9058fUL, + 0x000000023bfad8faUL, + 0x0000000199bd1216UL, + 0x000000056138afd7UL, + 0x0000000face83a93UL, + 0x00000009554da725UL, + 0x00000009b614dd91UL, + 0x000000098acbca3fUL, + 0x0000000d5f0d5f21UL, + 0x0000000eb59039e1UL, + 0x000000051d1ec82aUL, + 0x0000000a366ef3baUL, + 0x00000001ad0e01f0UL, + 0x00000007f038ad0bUL, + 0x00000003ee055321UL, + 0x00000003bf2dcbb7UL, + 0x0000000210e9856cUL, + 0x0000000e4fea8231UL, + 0x0000000b89444937UL, + 0x000000058852cc34UL, + 0x00000001ee29eea9UL, + 0x0000000b919c79f2UL, + 0x0000000ddc44d3adUL, + 0x0000000ddcbd4777UL, + 0x00000003c3982ba1UL, + 0x0000000dc8ebc45dUL, + 0x00000008b97712b1UL, + 0x00000009702ea21eUL, + 0x00000001f457e726UL, + 0x000000027c6f6e26UL, + 0x00000000a9797770UL, + 0x0000000d7615f53bUL, + 0x000000074f1cb6e1UL, + 0x0000000a32e4d7dcUL, + 0x00000002e89afd1dUL, + 0x00000000b03704d5UL, + 0x0000000cca58aab0UL, + 0x00000001e5749225UL, + 0x00000006e63a36baUL, + 0x0000000562992099UL, + 0x000000064701b950UL, + 0x0000000f94ed6196UL, + 0x0000000b3441b5f1UL, + 0x0000000c64fac247UL, + 0x0000000d72ebd98bUL, + 0x0000000fa1985b23UL, + 0x00000002df788358UL, + 0x000000088838b488UL, + 0x00000006091032b4UL, + 0x000000025ff2d736UL, + 0x0000000dce63d3d5UL, + 0x0000000bb5970414UL, + 0x000000044d8b5ffeUL, + 0x0000000e1a5666d8UL, + 0x0000000e34129125UL, + 0x00000000e23854b1UL, + 0x000000001b2a6dbeUL, + 0x0000000d11507bcdUL, + 0x0000000844531e6bUL, + 0x0000000d864a8611UL, + 0x0000000e2a5a7700UL, + 0x00000002d178962aUL, + 0x0000000156b07f01UL, + 0x000000048b59fec3UL, + 0x00000003d3d9d79cUL, + 0x00000001846fb339UL, + 0x0000000ddf1d03caUL, + 0x00000000998abaf9UL, + 0x0000000c9d76190bUL, + 0x000000067354a1a8UL, + 0x0000000cc89e2b09UL, + 0x0000000353356834UL, + 0x00000007ad97470eUL, + 0x0000000f4d560524UL, + 0x0000000534b7804eUL, + 0x000000014290c632UL, + 0x0000000b67d39d60UL, + 0x000000035b166febUL, + 0x000000088e6fb681UL, + 0x0000000a0f82ae1aUL, + 0x000000008460ce52UL, + 0x00000008b06a9012UL, + 0x0000000daf1299dcUL, + 0x0000000629ab696cUL, + 0x00000003113b448aUL, + 0x00000000db5ca215UL, + 0x00000003e00b1e2dUL, + 0x000000085a87f5abUL, + 0x0000000b3995ff20UL, + 0x000000085661554dUL, + 0x0000000e709c5384UL, + 0x00000000111ca99bUL, + 0x000000049e614279UL, + 0x0000000f14677ec4UL, + 0x00000008f6439bfbUL, + 0x0000000749faa461UL, + 0x00000001c4f9189aUL, + 0x0000000e8e9015caUL, + 0x0000000f6e68d510UL, + 0x0000000b3819319fUL, + 0x0000000da9f7119fUL, + 0x00000007787f40f8UL, + 0x0000000bc57f5716UL, + 0x000000060ff2897eUL, + 0x0000000b3a28a934UL, + 0x000000010b34c97cUL, + 0x0000000c14f53aedUL, + 0x0000000d3c4eaf5dUL, + 0x0000000b3148d39eUL, + 0x000000007874ea02UL, + 0x0000000f86692b4aUL, + 0x00000005b03a0e8dUL, + 0x0000000ce6db8cc6UL, + 0x00000008233d5908UL, + 0x0000000f163e3c06UL, + 0x0000000dff854cceUL, + 0x000000026706f1bcUL, + 0x000000094c358653UL, + 0x00000007384c9821UL, + 0x0000000e51b8e5d5UL, + 0x0000000eda32963bUL, + 0x0000000a073f392fUL, + 0x0000000c3ccfa213UL, + 0x000000034adf5216UL, + 0x0000000cb8da286bUL, + 0x00000003b5fbbf08UL, + 0x000000012812d1f8UL, + 0x0000000c97c54c39UL, + 0x0000000e1c3e36b9UL, + 0x0000000abb8dc0edUL, + 0x0000000019dcbbf6UL, + 0x000000025b0d7c4dUL, + 0x0000000045e6b5ceUL, + 0x000000017dc086caUL, + 0x0000000c3f425e6bUL, + 0x00000006fdee14f8UL, + 0x000000039155e6b4UL, + 0x00000000a191ec15UL, + 0x0000000398fcd7f4UL, + 0x0000000a6e2b0594UL, + 0x0000000fe5678d82UL, + 0x0000000e317eba1fUL, + 0x00000002c4f10ca1UL, + 0x0000000ae239c19eUL, + 0x000000018e663ed2UL, + 0x00000004a040b7e7UL, + 0x0000000bbca0849cUL, + 0x0000000ce05b3a74UL, + 0x00000007cee982fdUL, + 0x000000078ee54fa7UL, + 0x00000007b47bb0bdUL, + 0x00000007e8f19216UL, + 0x0000000d67d91cedUL, + 0x0000000ef5effe94UL, + 0x0000000ec1d1938dUL, + 0x00000004c05ef70eUL, + 0x00000000324442d9UL, + 0x0000000fb0183bb4UL, + 0x0000000fb7a0bd50UL, + 0x000000089aa17d87UL, + 0x0000000e4e6aed89UL, + 0x0000000dbecf68b4UL, + 0x0000000683770de4UL, + 0x0000000b9f41a136UL, + 0x0000000c7614caceUL, + 0x000000089c298386UL, + 0x0000000959cf09deUL, + 0x0000000ab30b19e3UL, + 0x0000000db2e4b614UL, + 0x000000026d30d39bUL, + 0x00000006ccefe452UL, + 0x0000000587c5035cUL, + 0x0000000ea73bbbe0UL, + 0x0000000dd9d91a11UL, + 0x0000000dd8c5e851UL, + 0x0000000e8b4aa077UL, + 0x00000008ccf8faddUL, + 0x000000047ddd3c0bUL, + 0x0000000635a92f19UL, + 0x0000000f0edfd1a3UL, + 0x00000001f760bf5eUL, + 0x0000000a83feb68aUL, + 0x00000004f74da9ddUL, + 0x000000052f759252UL, + 0x000000098bee689eUL, + 0x0000000c5fc8c3d5UL, + 0x00000008373d1286UL, + 0x0000000f5f1cdabdUL, + 0x0000000ada68d3e5UL, + 0x00000003bbb9eb5eUL, + 0x000000050cde8478UL, + 0x0000000f01f956e0UL, + 0x0000000a922f2842UL, + 0x0000000233a8b25aUL, + 0x000000071118b754UL, + 0x0000000b7f874552UL, + 0x000000044d757121UL, + 0x0000000b873b14ccUL, + 0x00000005bcc1db5cUL, + 0x0000000bf9b895ceUL, + 0x00000005e65bb620UL, + 0x0000000bbd1ed35cUL, + 0x0000000358e79973UL, + 0x000000062aa5a4a5UL, + 0x000000081715fc0fUL, + 0x00000008df03a76eUL, + 0x0000000376b7c6c7UL, + 0x0000000a07a49f2eUL, + 0x000000045e159b63UL, + 0x0000000dae5706b0UL, + 0x0000000b5e52c7ccUL, + 0x0000000206935e8eUL, + 0x000000039f0c5119UL, + 0x00000003cd58c574UL, + 0x0000000571986d35UL, + 0x0000000ad66da60fUL, + 0x000000002b1a6315UL, + 0x0000000d0131b533UL, + 0x0000000741a195c5UL, + 0x00000000b8663437UL, + 0x00000001cde52798UL, + 0x00000006b8e658b1UL, + 0x0000000b43c0d44dUL, + 0x000000045481d697UL, + 0x000000029de93df5UL, + 0x000000010549b874UL, + 0x0000000c056b5828UL, + 0x000000003fa830adUL, + 0x00000009496d14faUL, + 0x0000000f540592a0UL, + 0x0000000f31c8b855UL, + 0x000000064f2ba36bUL, + 0x0000000fe7c6e4f5UL, + 0x00000005e42a78b0UL, + 0x00000009c2b8b096UL, + 0x0000000dcb4a6e71UL, + 0x0000000d63b0e7edUL, + 0x0000000de1bcbcdaUL, + 0x000000068e7161f2UL, + 0x00000003e5ddf88dUL, + 0x0000000419a37501UL, + 0x0000000fad63e7abUL, + 0x0000000c6e81b4baUL, + 0x00000008329315d3UL, + 0x0000000c88d267e6UL, + 0x000000073a0ac25fUL, + 0x0000000e7b75690fUL, + 0x0000000dcbb95be2UL, + 0x00000007a1d2a059UL, + 0x0000000d8fac361eUL, + 0x00000006312ff5c9UL, + 0x0000000d2cf50d54UL, + 0x00000008c65fd00fUL, + 0x0000000aa1636532UL, + 0x0000000870c7285dUL, + 0x00000001894f0b84UL, + 0x00000004260cc5c3UL, + 0x0000000e9997b9ecUL, + 0x000000087a052144UL, + 0x00000008706babf6UL, + 0x0000000bd5f62ad3UL, + 0x00000001a7895439UL, + 0x0000000f7e294bbcUL, + 0x0000000bcc27ca26UL, + 0x00000003186a63d4UL, + 0x00000007f3ede4a4UL, + 0x0000000b64e32468UL, + 0x000000071f250d53UL, + 0x00000007c6513783UL, + 0x0000000b1778714aUL, + 0x000000094bf2c57fUL, + 0x000000064a9f893aUL, + 0x00000001305be654UL, + 0x0000000493e0c9f6UL, + 0x000000005ba6fed8UL, + 0x0000000c4a0c7a06UL, + 0x00000000cc2ec0ddUL, + 0x0000000d9a6769afUL, + 0x0000000724c78a49UL, + 0x0000000c85c981a4UL, + 0x000000012553c4cdUL, + 0x000000083cb892b1UL, + 0x0000000bc324ccc7UL, + 0x0000000ef43f6c1dUL, + 0x00000002d6748bb7UL, + 0x00000005efdce2d7UL, + 0x000000094af64f28UL, + 0x0000000f9d58feb3UL, + 0x0000000cf547ac63UL, + 0x0000000ceb309febUL, + 0x000000030beba8caUL, + 0x00000008ab2e486aUL, + 0x00000004a95d58adUL, + 0x000000025ce07c46UL, + 0x0000000712b93fd7UL, + 0x00000007f46acc81UL, + 0x000000064049d4beUL, + 0x000000065303aa09UL, + 0x0000000f3aad21b3UL, + 0x00000002903a6cd0UL, + 0x00000005a0e0467dUL, + 0x00000003c4fa64e4UL, + 0x00000005c6655126UL, + 0x0000000b40a2a67fUL, + 0x0000000b0c22c6e5UL, + 0x00000001507e039bUL, + 0x0000000b282b16b8UL, + 0x0000000c0e14a3d3UL, + 0x000000093d381427UL, + 0x00000006bb55bb87UL, + 0x0000000b675af72fUL, + 0x0000000fceb4f95eUL, + 0x000000066af6ebbdUL, + 0x000000020a44d1f2UL, + 0x00000006bc873916UL, + 0x0000000b8947bee8UL, + 0x00000004b6bed8a6UL, + 0x00000007012f7867UL, + 0x00000007eda3c150UL, + 0x0000000ab3ef1b8eUL, + 0x00000006d71466eeUL, + 0x0000000408c4e225UL, + 0x0000000e117838b1UL, + 0x00000000aef3a075UL, + 0x00000005a0779d4fUL, + 0x000000070a3b1d69UL, + 0x000000026ccd31fdUL, + 0x0000000ed64dd1b2UL, + 0x0000000981d4f60cUL, + 0x00000006a6e4fb61UL, + 0x000000052f15fc93UL, + 0x0000000032b3a64dUL, + 0x0000000ecb17d667UL, + 0x0000000a983fb935UL, + 0x000000037d23c88dUL, + 0x0000000b8590fbcbUL, + 0x0000000ec2f1a277UL, + 0x000000090d3053e6UL, + 0x0000000a36fa8ccdUL, + 0x000000044bd08eccUL, + 0x000000061dd197d9UL, + 0x0000000a307cfd82UL, + 0x00000001d09c2de4UL, + 0x00000005f6d74368UL, + 0x00000001327d1b2dUL, + 0x0000000594cc36b9UL, + 0x0000000fea1cba7cUL, + 0x000000050c31262dUL, + 0x0000000d99b1a6baUL, + 0x00000001bf789cd2UL, + 0x0000000e2f6f66f9UL, + 0x000000013d5edfc6UL, + 0x0000000bc3a9ab0cUL, + 0x00000001da5b2734UL, + 0x000000025ef4f2deUL, + 0x0000000dcb55a50aUL, + 0x00000009c6dbc6acUL, + 0x000000089a838853UL, + 0x0000000168f099eeUL, + 0x0000000d51601760UL, + 0x000000089f324f1aUL, + 0x00000002cb1ec1eaUL, + 0x00000006306de366UL, + 0x0000000012a2f11eUL, + 0x0000000b5c0bf797UL, + 0x00000005c5f02be4UL, + 0x00000005019f54beUL, + 0x00000006ae4a096aUL, + 0x00000004bce78778UL, + 0x000000094b65b97fUL, + 0x0000000d3f6e7bd2UL, + 0x00000001fbd2a84cUL, + 0x00000006d0127ab1UL, + 0x00000003e82799aaUL, + 0x00000004c1264dfeUL, + 0x0000000cf69c9360UL, + 0x00000004b43e5342UL, + 0x000000035d1f0372UL, + 0x0000000d78c18eb4UL, + 0x0000000262574101UL, + 0x0000000c2c5c7335UL, + 0x0000000bad04051aUL, + 0x00000001c481f94eUL, + 0x00000003285aa0deUL, + 0x00000008973e1f69UL, + 0x00000005d238c694UL, + 0x00000007b71847b9UL, + 0x0000000242f5675cUL, + 0x0000000cc5751c2dUL, + 0x0000000e09bc620bUL, + 0x00000000e4e904ddUL, + 0x000000007ca4f1a7UL, + 0x00000002ac79ae43UL, + 0x0000000e213d4250UL, + 0x0000000d4137c2b5UL, + 0x0000000ddfce11bcUL, + 0x0000000d1d658566UL, + 0x0000000213f5b1bbUL, + 0x0000000cd35be0a8UL, + 0x0000000cc67d7f91UL, + 0x0000000509bde098UL, + 0x000000074d3d8f46UL, + 0x000000051309c970UL, + 0x000000053e2bdf66UL, + 0x0000000a5dd3fed3UL, + 0x0000000a4e69b212UL, + 0x0000000b1d39936dUL, + 0x00000006b6c8926bUL, + 0x000000046540a7b0UL, + 0x00000002eebc599fUL, + 0x00000002e54a283eUL, + 0x0000000f9a328a9cUL, + 0x00000007ea9cfc53UL, + 0x00000005cffa2bdbUL, + 0x0000000464d16f8eUL, + 0x0000000eb09444bcUL, + 0x00000003f341b259UL, + 0x00000004d112b108UL, + 0x000000070cb94242UL, + 0x0000000974ed4ffdUL, + 0x00000001084da291UL, + 0x000000085673ca39UL, + 0x0000000d4d74766fUL, + 0x000000064a68e1deUL, + 0x0000000e35630caeUL, + 0x00000002073229dbUL, + 0x000000063d3a3902UL, + 0x000000031598ee06UL, + 0x0000000808d61126UL, + 0x0000000029957984UL, + 0x0000000d4f5f2649UL, + 0x00000009ec8a706bUL, + 0x0000000349981760UL, + 0x0000000c93ab23a6UL, + 0x00000002c7aa80daUL, + 0x0000000866f102baUL, + 0x0000000b15cff7bcUL, + 0x000000066a13a4caUL, + 0x000000054a755048UL, + 0x0000000d13fdb8d9UL, + 0x000000016ad5edf3UL, + 0x0000000e043bb154UL, + 0x0000000cc8755671UL, + 0x0000000cf9b2bfd5UL, + 0x00000003608890b4UL, + 0x0000000330fef315UL, + 0x0000000e3299ca65UL, + 0x00000000b60765e1UL, + 0x00000000e9bb17dcUL, + 0x000000095f474d8bUL, + 0x0000000e721d3d00UL, + 0x0000000d4679e565UL, + 0x0000000c80da6113UL, + 0x000000098deeff30UL, + 0x0000000c293bb871UL, + 0x0000000e79132f48UL, + 0x0000000b152dafbbUL, + 0x000000055f6a4386UL, + 0x0000000a1b8a4044UL, + 0x00000004f4187b05UL, + 0x00000000b17c2ed3UL, + 0x000000095d75ba04UL, + 0x0000000bbf12e96dUL, + 0x00000006abd1a52fUL, + 0x0000000f300bc991UL, + 0x0000000f0a7385d4UL, + 0x000000052964f82aUL, + 0x0000000a9962925fUL, + 0x0000000613b2eef1UL, + 0x00000005fd2c92a8UL, + 0x000000009ebecd05UL, + 0x000000036002b87aUL, + 0x0000000902c79eefUL, + 0x0000000394e63c7eUL, + 0x0000000133285064UL, + 0x0000000f7cfe2d4bUL, + 0x00000004f068522cUL, + 0x000000096fea1a0fUL, + 0x0000000c5a927b13UL, + 0x0000000e9a2c1994UL, + 0x00000005c53b3803UL, + 0x0000000f636b6188UL, + 0x0000000007c656e3UL, + 0x000000026af1fc5fUL, + 0x0000000ec2f40b78UL, + 0x0000000faa1921e5UL, + 0x00000006137a8b30UL, + 0x0000000028674f7bUL, + 0x00000003de184e35UL, + 0x0000000eeef093e6UL, + 0x0000000d44b3dae0UL, + 0x0000000bb7ab7d93UL, + 0x00000002ae18c956UL, + 0x0000000cde492bd6UL, + 0x00000001cee0216eUL, + 0x0000000f1e5830adUL, + 0x000000076f6c3299UL, + 0x0000000dea24af84UL, + 0x0000000277e75586UL, + 0x0000000a17318024UL, + 0x00000005c4739486UL, + 0x00000005e3de4725UL, + 0x00000006f67c9f6dUL, + 0x000000025f42791dUL, + 0x00000003c54d15b3UL, + 0x0000000ef98d9c32UL, + 0x000000042f64819dUL, + 0x000000016d5fd070UL, + 0x000000063cb98d4fUL, + 0x000000045a3ad27cUL, + 0x00000001b496b0acUL, + 0x0000000aa471c42dUL, + 0x00000000599346a2UL, + 0x00000000dc8d1c2dUL, + 0x00000007498928c1UL, + 0x0000000ea06e90ffUL, + 0x0000000b683baa32UL, + 0x0000000f93014e16UL, + 0x000000020575d56eUL, + 0x0000000794325589UL, + 0x00000001533e9935UL, + 0x000000086b8bcb70UL, + 0x0000000ce11faf5dUL, + 0x000000036c0bd318UL, + 0x0000000e5e8c1167UL, + 0x0000000e1831ba64UL, + 0x0000000e088dbfa4UL, + 0x0000000984479674UL, + 0x0000000afef02b29UL, + 0x000000048518c716UL, + 0x00000004301564ceUL, + 0x000000021cc88710UL, + 0x0000000d5c995278UL, + 0x0000000d8367de1cUL, + 0x00000004a51125e8UL, + 0x0000000113e1c226UL, + 0x0000000ef141e076UL, + 0x000000044097011dUL, + 0x00000004ca9d707cUL, + 0x000000040d8831f1UL, + 0x0000000bd9c3b1d8UL, + 0x0000000978364177UL, + 0x000000010f7606a9UL, + 0x000000046a64270aUL, + 0x000000042df1b22bUL, + 0x0000000e906cf2a0UL, + 0x0000000997da6fa5UL, + 0x0000000a5722c26fUL, + 0x0000000b14f58aaaUL, + 0x0000000afc167ad8UL, + 0x000000037be56e60UL, + 0x0000000de7f80d62UL, + 0x00000000c3fb0a64UL, + 0x0000000ce8ca802cUL, + 0x000000035032ed9dUL, + 0x0000000aa8ba3ee6UL, + 0x000000094b2e707cUL, + 0x00000002debbdae1UL, + 0x0000000f53e25fcfUL, + 0x0000000e935543ebUL, + 0x00000001462f0e90UL, + 0x000000054ce7d18cUL, + 0x00000002ddafdc5fUL, + 0x0000000700565deeUL, + 0x0000000fd408e0afUL, + 0x000000017d089decUL, + 0x0000000833ea2459UL, + 0x00000003c8d3776aUL, + 0x00000002e5eebac8UL, + 0x000000020cbf49b0UL, + 0x0000000c44675eb7UL, + 0x00000003a4b6beb1UL, + 0x0000000ce6f37c1eUL, + 0x000000063fba2e7cUL, + 0x00000005a05b553dUL, + 0x00000001286445b0UL, + 0x00000005e07a9b61UL, + 0x00000007d8397ea4UL, + 0x00000008084b7bbbUL, + 0x0000000b05b38097UL, + 0x000000029c3019eeUL, + 0x0000000ed1d2708bUL, + 0x00000009df8a4d47UL, + 0x0000000e4891e436UL, + 0x00000002a762ab72UL, + 0x000000092f70600fUL, + 0x000000092329a2cdUL, + 0x00000003e200c6edUL, + 0x00000008c0a7233eUL, + 0x000000060866806aUL, + 0x0000000f4fddd24aUL, + 0x0000000f78464c71UL, + 0x00000009c3d22242UL, + 0x00000003877ea6d1UL, + 0x0000000e2a6d54acUL, + 0x0000000497d2a5e7UL, + 0x0000000ca82f781eUL, + 0x0000000481524f4cUL, + 0x0000000dee088814UL, + 0x0000000b2a82d3a4UL, + 0x00000008e6afe6e5UL, + 0x0000000d6279a5daUL, + 0x00000004567cbc1aUL, + 0x00000005bec2b2fdUL, + 0x00000004ef452505UL, + 0x000000061d992cbaUL, + 0x0000000ab96be0cbUL, + 0x0000000708ef35d9UL, + 0x0000000b3f6f3623UL, + 0x000000036eb1801dUL, + 0x0000000badfee917UL, + 0x0000000a3db13cd0UL, + 0x00000001d1a12828UL, + 0x00000002500816ceUL, + 0x0000000cf7612148UL, + 0x00000000be6a3f4bUL, + 0x000000074142f3daUL, + 0x0000000ce5deed92UL, + 0x0000000f9530a786UL, + 0x0000000047c8bb38UL, + 0x0000000fcabfe88fUL, + 0x0000000bc83accb1UL, + 0x000000020cd9fb1fUL, + 0x0000000023dcceb3UL, + 0x00000009e969b8c4UL, + 0x00000006e28de934UL, + 0x000000080a399667UL, + 0x000000076a0b85adUL, + 0x000000021a84be3cUL, + 0x0000000a28d028b5UL, + 0x0000000c4e7690dfUL, + 0x0000000bfd9621e8UL, + 0x00000006f4bc0c24UL, + 0x0000000aa8e76bd7UL, + 0x0000000deb55dac9UL, + 0x0000000bb344fa8bUL, + 0x0000000fcaab4decUL, + 0x0000000146aba6cbUL, + 0x0000000f49ed6eb8UL, + 0x0000000dd57e9deaUL, + 0x0000000225d5d090UL, + 0x0000000d6e86c1c5UL, + 0x0000000639be5f39UL, + 0x0000000f5e7a6132UL, + 0x0000000d2968b09fUL, + 0x000000082b30ba1eUL, + 0x0000000803fa46ccUL, + 0x0000000c290fab00UL, + 0x000000010df59de5UL, + 0x000000051ae9dcfbUL, + 0x000000049af8516dUL, + 0x000000002b564ce6UL, + 0x0000000c615a1de0UL, + 0x0000000fef9864a4UL, + 0x0000000c16e27341UL, + 0x000000039e846736UL, + 0x00000001ecbb6746UL, + 0x0000000588d03a7cUL, + 0x000000010a0eaf9cUL, + 0x0000000671ccea6bUL, + 0x000000033a154603UL, + 0x0000000a7b003bc1UL, + 0x0000000c5fc3848dUL, + 0x000000078e50a9c7UL, + 0x000000017dbfb88eUL, + 0x00000004fd0ed541UL, + 0x000000084221debaUL, + 0x00000003132cf7e6UL, + 0x0000000b67e7ac53UL, + 0x0000000df6b28024UL, + 0x0000000785b9f7edUL, + 0x0000000e3d35320dUL, + 0x0000000159c06583UL, + 0x00000005c54a80a3UL, + 0x0000000ed4d4533bUL, + 0x0000000cf16c601aUL, + 0x00000005e94efbd1UL, + 0x00000005d587126eUL, + 0x0000000eef2f2807UL, + 0x000000009f3c558eUL, + 0x0000000736cfd539UL, + 0x0000000f5a922ae1UL, + 0x00000004e2ab9959UL, + 0x00000006a2dd34e7UL, + 0x00000008c9d30d23UL, + 0x0000000eba20b791UL, + 0x0000000d5c5095e3UL, + 0x0000000423d75a82UL, + 0x000000040cebaafeUL, + 0x000000065e08d288UL, + 0x00000002e4f6d767UL, + 0x0000000fe10d2f21UL, + 0x0000000110347bdaUL, + 0x0000000e43a9bfb3UL, + 0x0000000cdea483ccUL, + 0x0000000fb1e2d8c6UL, + 0x0000000d8a0af7a7UL, + 0x000000037d05b182UL, + 0x00000008d1241d83UL, + 0x0000000da1ea7b6eUL, + 0x000000065bea93dbUL, + 0x00000002a02f8753UL, + 0x0000000454243289UL, + 0x00000004150bc5a2UL, + 0x0000000bbabe5911UL, + 0x00000004cbcdbc59UL, + 0x0000000f0e61340bUL, + 0x000000030a2cdea8UL, + 0x00000005daecb091UL, + 0x00000005dc93d891UL, + 0x0000000c501b4051UL, + 0x0000000782cfba78UL, + 0x00000004c191b61eUL, + 0x0000000b7e27ef35UL, + 0x000000005a476838UL, + 0x00000009b0209574UL, + 0x0000000a775164cfUL, + 0x0000000d33d21701UL, + 0x00000003afcb7d45UL, + 0x00000004df2035cdUL, + 0x0000000498819a21UL, + 0x0000000293f9e506UL, + 0x00000009a35ff1c8UL, + 0x0000000c090ebe6bUL, + 0x0000000a4f0551d4UL, + 0x00000005dc0dc194UL, + 0x00000001388aeb31UL, + 0x0000000340b27bf4UL, + 0x00000003a0f320abUL, + 0x00000000996be75dUL, + 0x0000000b257ecf39UL, + 0x000000078d86f2f1UL, + 0x0000000673f5ff91UL, + 0x00000004538d7e3eUL, + 0x0000000de5bc4369UL + } +}; + + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "tag36h11" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const apriltag_family_t tag36h11 = { + .ncodes = 587, + .black_border = 1, + .d = 6, + .h = 11, + .codes = { + 0x0000000d5d628584UL, + 0x0000000d97f18b49UL, + 0x0000000dd280910eUL, + 0x0000000e479e9c98UL, + 0x0000000ebcbca822UL, + 0x0000000f31dab3acUL, + 0x0000000056a5d085UL, + 0x000000010652e1d4UL, + 0x000000022b1dfeadUL, + 0x0000000265ad0472UL, + 0x000000034fe91b86UL, + 0x00000003ff962cd5UL, + 0x000000043a25329aUL, + 0x0000000474b4385fUL, + 0x00000004e9d243e9UL, + 0x00000005246149aeUL, + 0x00000005997f5538UL, + 0x0000000683bb6c4cUL, + 0x00000006be4a7211UL, + 0x00000007e3158eeaUL, + 0x000000081da494afUL, + 0x0000000858339a74UL, + 0x00000008cd51a5feUL, + 0x00000009f21cc2d7UL, + 0x0000000a2cabc89cUL, + 0x0000000adc58d9ebUL, + 0x0000000b16e7dfb0UL, + 0x0000000b8c05eb3aUL, + 0x0000000d25ef139dUL, + 0x0000000d607e1962UL, + 0x0000000e4aba3076UL, + 0x00000002dde6a3daUL, + 0x000000043d40c678UL, + 0x00000005620be351UL, + 0x000000064c47fa65UL, + 0x0000000686d7002aUL, + 0x00000006c16605efUL, + 0x00000006fbf50bb4UL, + 0x00000008d06d39dcUL, + 0x00000009f53856b5UL, + 0x0000000adf746dc9UL, + 0x0000000bc9b084ddUL, + 0x0000000d290aa77bUL, + 0x0000000d9e28b305UL, + 0x0000000e4dd5c454UL, + 0x0000000fad2fe6f2UL, + 0x0000000181a8151aUL, + 0x000000026be42c2eUL, + 0x00000002e10237b8UL, + 0x0000000405cd5491UL, + 0x00000007742eab1cUL, + 0x000000085e6ac230UL, + 0x00000008d388cdbaUL, + 0x00000009f853ea93UL, + 0x0000000c41ea2445UL, + 0x0000000cf1973594UL, + 0x000000014a34a333UL, + 0x000000031eacd15bUL, + 0x00000006c79d2dabUL, + 0x000000073cbb3935UL, + 0x000000089c155bd3UL, + 0x00000008d6a46198UL, + 0x000000091133675dUL, + 0x0000000a708d89fbUL, + 0x0000000ae5ab9585UL, + 0x0000000b9558a6d4UL, + 0x0000000b98743ab2UL, + 0x0000000d6cec68daUL, + 0x00000001506bcaefUL, + 0x00000004becd217aUL, + 0x00000004f95c273fUL, + 0x0000000658b649ddUL, + 0x0000000a76c4b1b7UL, + 0x0000000ecf621f56UL, + 0x00000001c8a56a57UL, + 0x00000003628e92baUL, + 0x000000053706c0e2UL, + 0x00000005e6b3d231UL, + 0x00000007809cfa94UL, + 0x0000000e97eead6fUL, + 0x00000005af40604aUL, + 0x00000007492988adUL, + 0x0000000ed5994712UL, + 0x00000005eceaf9edUL, + 0x00000007c1632815UL, + 0x0000000c1a0095b4UL, + 0x0000000e9e25d52bUL, + 0x00000003a6705419UL, + 0x0000000a8333012fUL, + 0x00000004ce5704d0UL, + 0x0000000508e60a95UL, + 0x0000000877476120UL, + 0x0000000a864e950dUL, + 0x0000000ea45cfce7UL, + 0x000000019da047e8UL, + 0x000000024d4d5937UL, + 0x00000006e079cc9bUL, + 0x000000099f2e11d7UL, + 0x000000033aa50429UL, + 0x0000000499ff26c7UL, + 0x000000050f1d3251UL, + 0x000000066e7754efUL, + 0x000000096ad633ceUL, + 0x00000009a5653993UL, + 0x0000000aca30566cUL, + 0x0000000c298a790aUL, + 0x00000008be44b65dUL, + 0x0000000dc68f354bUL, + 0x000000016f7f919bUL, + 0x00000004dde0e826UL, + 0x0000000d548cbd9fUL, + 0x0000000e0439ceeeUL, + 0x0000000fd8b1fd16UL, + 0x000000076521bb7bUL, + 0x0000000d92375742UL, + 0x0000000cab16d40cUL, + 0x0000000730c9dd72UL, + 0x0000000ad9ba39c2UL, + 0x0000000b14493f87UL, + 0x000000052b15651fUL, + 0x0000000185409cadUL, + 0x000000077ae2c68dUL, + 0x000000094f5af4b5UL, + 0x00000000a13bad55UL, + 0x000000061ea437cdUL, + 0x0000000a022399e2UL, + 0x0000000203b163d1UL, + 0x00000007bba8f40eUL, + 0x000000095bc9442dUL, + 0x000000041c0b5358UL, + 0x00000008e9c6cc81UL, + 0x00000000eb549670UL, + 0x00000009da3a0b51UL, + 0x0000000d832a67a1UL, + 0x0000000dcd4350bcUL, + 0x00000004aa05fdd2UL, + 0x000000060c7bb44eUL, + 0x00000004b358b96cUL, + 0x0000000067299b45UL, + 0x0000000b9c89b5faUL, + 0x00000006975acaeaUL, + 0x000000062b8f7afaUL, + 0x000000033567c3d7UL, + 0x0000000bac139950UL, + 0x0000000a5927c62aUL, + 0x00000005c916e6a4UL, + 0x0000000260ecb7d5UL, + 0x000000029b7bbd9aUL, + 0x0000000903205f26UL, + 0x0000000ae72270a4UL, + 0x00000003d2ec51a7UL, + 0x000000082ea55324UL, + 0x000000011a6f3427UL, + 0x00000001ca1c4576UL, + 0x0000000a40c81aefUL, + 0x0000000bddccd730UL, + 0x00000000e617561eUL, + 0x0000000969317b0fUL, + 0x000000067f781364UL, + 0x0000000610912f96UL, + 0x0000000b2549fdfcUL, + 0x000000006e5aaa6bUL, + 0x0000000b6c475339UL, + 0x0000000c56836a4dUL, + 0x0000000844e351ebUL, + 0x00000004647f83b4UL, + 0x00000000908a04f5UL, + 0x00000007f51034c9UL, + 0x0000000aee537fcaUL, + 0x00000005e92494baUL, + 0x0000000d445808f4UL, + 0x000000028d68b563UL, + 0x000000004d25374bUL, + 0x00000002bc065f65UL, + 0x000000096dc3ea0cUL, + 0x00000004b2ade817UL, + 0x000000007c3fd502UL, + 0x0000000e768b5cafUL, + 0x000000017605cf6cUL, + 0x0000000182741ee4UL, + 0x000000062846097cUL, + 0x000000072b5ebf80UL, + 0x0000000263da6e13UL, + 0x0000000fa841bcb5UL, + 0x00000007e45e8c69UL, + 0x0000000653c81fa0UL, + 0x00000007443b5e70UL, + 0x00000000a5234afdUL, + 0x000000074756f24eUL, + 0x0000000157ebf02aUL, + 0x000000082ef46939UL, + 0x000000080d420264UL, + 0x00000002aeed3e98UL, + 0x0000000b0a1dd4f8UL, + 0x0000000b5436be13UL, + 0x00000007b7b4b13bUL, + 0x00000001ce80d6d3UL, + 0x000000016c08427dUL, + 0x0000000ee54462ddUL, + 0x00000001f7644cceUL, + 0x00000009c7b5cc92UL, + 0x0000000e369138f8UL, + 0x00000005d5a66e91UL, + 0x0000000485d62f49UL, + 0x0000000e6e819e94UL, + 0x0000000b1f340eb5UL, + 0x000000009d198ce2UL, + 0x0000000d60717437UL, + 0x00000000196b856cUL, + 0x0000000f0a6173a5UL, + 0x000000012c0e1ec6UL, + 0x000000062b82d5cfUL, + 0x0000000ad154c067UL, + 0x0000000ce3778832UL, + 0x00000006b0a7b864UL, + 0x00000004c7686694UL, + 0x00000005058ff3ecUL, + 0x0000000d5e21ea23UL, + 0x00000009ff4a76eeUL, + 0x00000009dd981019UL, + 0x00000001bad4d30aUL, + 0x0000000c601896d1UL, + 0x0000000973439b48UL, + 0x00000001ce7431a8UL, + 0x000000057a8021d6UL, + 0x0000000f9dba96e6UL, + 0x000000083a2e4e7cUL, + 0x00000008ea585380UL, + 0x0000000af6c0e744UL, + 0x0000000875b73babUL, + 0x0000000da34ca901UL, + 0x00000002ab9727efUL, + 0x0000000d39f21b9aUL, + 0x00000008a10b742fUL, + 0x00000005f8952dbaUL, + 0x0000000f8da71ab0UL, + 0x0000000c25f9df96UL, + 0x000000006f8a5d94UL, + 0x0000000e42e63e1aUL, + 0x0000000b78409d1bUL, + 0x0000000792229addUL, + 0x00000005acf8c455UL, + 0x00000002fc29a9b0UL, + 0x0000000ea486237bUL, + 0x0000000b0c9685a0UL, + 0x00000001ad748a47UL, + 0x000000003b4712d5UL, + 0x0000000f29216d30UL, + 0x00000008dad65e49UL, + 0x00000000a2cf09ddUL, + 0x00000000b5f174c6UL, + 0x0000000e54f57743UL, + 0x0000000b9cf54d78UL, + 0x00000004a312a88aUL, + 0x000000027babc962UL, + 0x0000000b86897111UL, + 0x0000000f2ff6c116UL, + 0x000000082274bd8aUL, + 0x000000097023505eUL, + 0x000000052d46edd1UL, + 0x0000000585c1f538UL, + 0x0000000bddd00e43UL, + 0x00000005590b74dfUL, + 0x0000000729404a1fUL, + 0x000000065320855eUL, + 0x0000000d3d4b6956UL, + 0x00000007ae374f14UL, + 0x00000002d7a60e06UL, + 0x0000000315cd9b5eUL, + 0x0000000fd36b4eacUL, + 0x0000000f1df7642bUL, + 0x000000055db27726UL, + 0x00000008f15ebc19UL, + 0x0000000992f8c531UL, + 0x000000062dea2a40UL, + 0x0000000928275cabUL, + 0x000000069c263cb9UL, + 0x0000000a774cca9eUL, + 0x0000000266b2110eUL, + 0x00000001b14acbb8UL, + 0x0000000624b8a71bUL, + 0x00000001c539406bUL, + 0x00000003086d529bUL, + 0x00000000111dd66eUL, + 0x000000098cd630bfUL, + 0x00000008b9d1ffdcUL, + 0x000000072b2f61e7UL, + 0x00000009ed9d672bUL, + 0x000000096cdd15f3UL, + 0x00000006366c2504UL, + 0x00000006ca9df73aUL, + 0x0000000a066d60f0UL, + 0x0000000e7a4b8addUL, + 0x00000008264647efUL, + 0x0000000aa195bf81UL, + 0x00000009a3db8244UL, + 0x0000000014d2df6aUL, + 0x00000000b63265b7UL, + 0x00000002f010de73UL, + 0x000000097e774986UL, + 0x0000000248affc29UL, + 0x0000000fb57dcd11UL, + 0x00000000b1a7e4d9UL, + 0x00000004bfa2d07dUL, + 0x000000054e5cdf96UL, + 0x00000004c15c1c86UL, + 0x0000000cd9c61166UL, + 0x0000000499380b2aUL, + 0x0000000540308d09UL, + 0x00000008b63fe66fUL, + 0x0000000c81aeb35eUL, + 0x000000086fe0bd5cUL, + 0x0000000ce2480c2aUL, + 0x00000001ab29ee60UL, + 0x00000008048daa15UL, + 0x0000000dbfeb2d39UL, + 0x0000000567c9858cUL, + 0x00000002b6edc5bcUL, + 0x00000002078fca82UL, + 0x0000000adacc22aaUL, + 0x0000000b92486f49UL, + 0x000000051fac5964UL, + 0x0000000691ee6420UL, + 0x0000000f63b3e129UL, + 0x000000039be7e572UL, + 0x0000000da2ce6c74UL, + 0x000000020cf17a5cUL, + 0x0000000ee55f9b6eUL, + 0x0000000fb8572726UL, + 0x0000000b2c2de548UL, + 0x0000000caa9bce92UL, + 0x0000000ae9182db3UL, + 0x000000074b6e5bd1UL, + 0x0000000137b252afUL, + 0x000000051f686881UL, + 0x0000000d672f6c02UL, + 0x0000000654146ce4UL, + 0x0000000f944bc825UL, + 0x0000000e8327f809UL, + 0x000000076a73fd59UL, + 0x0000000f79da4cb4UL, + 0x0000000956f8099bUL, + 0x00000007b5f2655cUL, + 0x0000000d06b114a6UL, + 0x0000000d0697ca50UL, + 0x000000027c390797UL, + 0x0000000bc61ed9b2UL, + 0x0000000cc12dd19bUL, + 0x0000000eb7818d2cUL, + 0x0000000092fcecdaUL, + 0x000000089ded4ea1UL, + 0x0000000256a0ba34UL, + 0x0000000b6948e627UL, + 0x00000001ef6b1054UL, + 0x00000008639294a2UL, + 0x0000000eda3780a4UL, + 0x000000039ee2af1dUL, + 0x0000000cd257edc5UL, + 0x00000002d9d6bc22UL, + 0x0000000121d3b47dUL, + 0x000000037e23f8adUL, + 0x0000000119f31cf6UL, + 0x00000002c97f4f09UL, + 0x0000000d502abfe0UL, + 0x000000010bc3ca77UL, + 0x000000053d7190efUL, + 0x000000090c3e62a6UL, + 0x00000007e9ebf675UL, + 0x0000000979ce23d1UL, + 0x000000027f0c98e9UL, + 0x0000000eafb4ae59UL, + 0x00000007ca7fe2bdUL, + 0x00000001490ca8f6UL, + 0x00000009123387baUL, + 0x0000000b3bc73888UL, + 0x00000003ea87e325UL, + 0x00000004888964aaUL, + 0x0000000a0188a6b9UL, + 0x0000000cd383c666UL, + 0x000000040029a3fdUL, + 0x0000000e1c00ac5cUL, + 0x000000039e6f2b6eUL, + 0x0000000de664f622UL, + 0x0000000e979a75e8UL, + 0x00000007c6b4c86cUL, + 0x0000000fd492e071UL, + 0x00000008fbb35118UL, + 0x000000040b4a09b7UL, + 0x0000000af80bd6daUL, + 0x000000070e0b2521UL, + 0x00000002f5c54d93UL, + 0x00000003f4a118d5UL, + 0x000000009c1897b9UL, + 0x0000000079776eacUL, + 0x0000000084b00b17UL, + 0x00000003a95ad90eUL, + 0x000000028c544095UL, + 0x000000039d457c05UL, + 0x00000007a3791a78UL, + 0x0000000bb770e22eUL, + 0x00000009a822bd6cUL, + 0x000000068a4b1fedUL, + 0x0000000a5fd27b3bUL, + 0x00000000c3995b79UL, + 0x0000000d1519dff1UL, + 0x00000008e7eee359UL, + 0x0000000cd3ca50b1UL, + 0x0000000b73b8b793UL, + 0x000000057aca1c43UL, + 0x0000000ec2655277UL, + 0x0000000785a2c1b3UL, + 0x000000075a07985aUL, + 0x0000000a4b01eb69UL, + 0x0000000a18a11347UL, + 0x0000000db1f28ca3UL, + 0x0000000877ec3e25UL, + 0x000000031f6341b8UL, + 0x00000001363a3a4cUL, + 0x0000000075d8b9baUL, + 0x00000007ae0792a9UL, + 0x0000000a83a21651UL, + 0x00000007f08f9fb5UL, + 0x00000000d0cf73a9UL, + 0x0000000b04dcc98eUL, + 0x0000000f65c7b0f8UL, + 0x000000065ddaf69aUL, + 0x00000002cf9b86b3UL, + 0x000000014cb51e25UL, + 0x0000000f48027b5bUL, + 0x00000000ec26ea8bUL, + 0x000000044bafd45cUL, + 0x0000000b12c7c0c4UL, + 0x0000000959fd9d82UL, + 0x0000000c77c9725aUL, + 0x000000048a22d462UL, + 0x00000008398e8072UL, + 0x0000000ec89b05ceUL, + 0x0000000bb682d4c9UL, + 0x0000000e5a86d2ffUL, + 0x0000000358f01134UL, + 0x00000008556ddcf6UL, + 0x000000067584b6e2UL, + 0x000000011609439fUL, + 0x000000008488816eUL, + 0x0000000aaf1a2c46UL, + 0x0000000f879898cfUL, + 0x00000008bbe5e2f7UL, + 0x0000000101eee363UL, + 0x0000000690f69377UL, + 0x0000000f5bd93cd9UL, + 0x0000000cea4c2bf6UL, + 0x00000009550be706UL, + 0x00000002c5b38a60UL, + 0x0000000e72033547UL, + 0x00000004458b0629UL, + 0x0000000ee8d9ed41UL, + 0x0000000d2f918d72UL, + 0x000000078dc39fd3UL, + 0x00000008212636f6UL, + 0x00000007450a72a7UL, + 0x0000000c4f0cf4c6UL, + 0x0000000367bcddcdUL, + 0x0000000c1caf8cc6UL, + 0x0000000a7f5b853dUL, + 0x00000009d536818bUL, + 0x0000000535e021b0UL, + 0x0000000a7eb8729eUL, + 0x0000000422a67b49UL, + 0x0000000929e928a6UL, + 0x000000048e8aefccUL, + 0x0000000a9897393cUL, + 0x00000005eb81d37eUL, + 0x00000001e80287b7UL, + 0x000000034770d903UL, + 0x00000002eef86728UL, + 0x000000059266ccb6UL, + 0x00000000110bba61UL, + 0x00000001dfd284efUL, + 0x0000000447439d1bUL, + 0x0000000fece0e599UL, + 0x00000009309f3703UL, + 0x000000080764d1ddUL, + 0x0000000353f1e6a0UL, + 0x00000002c1c12dccUL, + 0x0000000c1d21b9d7UL, + 0x0000000457ee453eUL, + 0x0000000d66faf540UL, + 0x000000044831e652UL, + 0x0000000cfd49a848UL, + 0x00000009312d4133UL, + 0x00000003f097d3eeUL, + 0x00000008c9ebef7aUL, + 0x0000000a99e29e88UL, + 0x00000000e9fab22cUL, + 0x00000004e748f4fbUL, + 0x0000000ecdee4288UL, + 0x0000000abce5f1d0UL, + 0x0000000c42f6876cUL, + 0x00000007ed402ea0UL, + 0x0000000e5c4242c3UL, + 0x0000000d5b2c31aeUL, + 0x0000000286863be6UL, + 0x0000000160444d94UL, + 0x00000005f0f5808eUL, + 0x0000000ae3d44b2aUL, + 0x00000009f5c5d109UL, + 0x00000008ad9316d7UL, + 0x00000003422ba064UL, + 0x00000002fed11d56UL, + 0x0000000bea6e3e04UL, + 0x000000004b029eecUL, + 0x00000006deed7435UL, + 0x00000003718ce17cUL, + 0x000000055857f5e2UL, + 0x00000002edac7b62UL, + 0x0000000085d6c512UL, + 0x0000000d6ca88e0fUL, + 0x00000002b7e1fc69UL, + 0x0000000a699d5c1bUL, + 0x0000000f05ad74deUL, + 0x00000004cf5fb56dUL, + 0x00000005725e07e1UL, + 0x000000072f18a2deUL, + 0x00000001cec52609UL, + 0x000000048534243cUL, + 0x00000002523a4d69UL, + 0x000000035c1b80d1UL, + 0x0000000a4d7338a7UL, + 0x00000000db1af012UL, + 0x0000000e61a9475dUL, + 0x000000005df03f91UL, + 0x000000097ae260bbUL, + 0x000000032d627fefUL, + 0x0000000b640f73c2UL, + 0x000000045a1ac9c6UL, + 0x00000006a2202de1UL, + 0x000000057d3e25f2UL, + 0x00000005aa9f986eUL, + 0x00000000cc859d8aUL, + 0x0000000e3ec6cca8UL, + 0x000000054e95e1aeUL, + 0x0000000446887b06UL, + 0x00000007516732beUL, + 0x00000003817ac8f5UL, + 0x00000003e26d938cUL, + 0x0000000aa81bc235UL, + 0x0000000df387ca1bUL, + 0x00000000f3a3b3f2UL, + 0x0000000b4bf69677UL, + 0x0000000ae21868edUL, + 0x000000081e1d2d9dUL, + 0x0000000a0a9ea14cUL, + 0x00000008eee297a9UL, + 0x00000004740c0559UL, + 0x0000000e8b141837UL, + 0x0000000ac69e0a3dUL, + 0x00000009ed83a1e1UL, + 0x00000005edb55ecbUL, + 0x000000007340fe81UL, + 0x000000050dfbc6bfUL, + 0x00000004f583508aUL, + 0x0000000cb1fb78bcUL, + 0x00000004025ced2fUL, + 0x000000039791ebecUL, + 0x000000053ee388f1UL, + 0x00000007d6c0bd23UL, + 0x000000093a995fbeUL, + 0x00000008a41728deUL, + 0x00000002fe70e053UL, + 0x0000000ab3db443aUL, + 0x00000001364edb05UL, + 0x000000047b6eeed6UL, + 0x000000012e71af01UL, + 0x000000052ff83587UL, + 0x00000003a1575dd8UL, + 0x00000003feaa3564UL, + 0x0000000eacf78ba7UL, + 0x00000000872b94f8UL, + 0x0000000da8ddf9a2UL, + 0x00000009aa920d2bUL, + 0x00000001f350ed36UL, + 0x000000018a5e861fUL, + 0x00000002c35b89c3UL, + 0x00000003347ac48aUL, + 0x00000007f23e022eUL, + 0x00000002459068fbUL, + 0x0000000e83be4b73UL + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "artoolkit" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +const apriltag_family_t artoolkit = { + .ncodes = 512, + .black_border = 1, + .d = 6, + .h = 7, + .codes = { + 0x0006dc269c27UL, + 0x0006d4229e26UL, + 0x0006cc2e9825UL, + 0x0006c42a9a24UL, + 0x0006fc369423UL, + 0x0006f4329622UL, + 0x0006ec3e9021UL, + 0x0006e43a9220UL, + 0x00069c068c2fUL, + 0x000694028e2eUL, + 0x00068c0e882dUL, + 0x0006840a8a2cUL, + 0x0006bc16842bUL, + 0x0006b412862aUL, + 0x0006ac1e8029UL, + 0x0006a41a8228UL, + 0x00065c66bc37UL, + 0x00065462be36UL, + 0x00064c6eb835UL, + 0x0006446aba34UL, + 0x00067c76b433UL, + 0x00067472b632UL, + 0x00066c7eb031UL, + 0x0006647ab230UL, + 0x00061c46ac3fUL, + 0x00061442ae3eUL, + 0x00060c4ea83dUL, + 0x0006044aaa3cUL, + 0x00063c56a43bUL, + 0x00063452a63aUL, + 0x00062c5ea039UL, + 0x0006245aa238UL, + 0x0007dca6dc07UL, + 0x0007d4a2de06UL, + 0x0007ccaed805UL, + 0x0007c4aada04UL, + 0x0007fcb6d403UL, + 0x0007f4b2d602UL, + 0x0007ecbed001UL, + 0x0007e4bad200UL, + 0x00079c86cc0fUL, + 0x00079482ce0eUL, + 0x00078c8ec80dUL, + 0x0007848aca0cUL, + 0x0007bc96c40bUL, + 0x0007b492c60aUL, + 0x0007ac9ec009UL, + 0x0007a49ac208UL, + 0x00075ce6fc17UL, + 0x000754e2fe16UL, + 0x00074ceef815UL, + 0x000744eafa14UL, + 0x00077cf6f413UL, + 0x000774f2f612UL, + 0x00076cfef011UL, + 0x000764faf210UL, + 0x00071cc6ec1fUL, + 0x000714c2ee1eUL, + 0x00070ccee81dUL, + 0x000704caea1cUL, + 0x00073cd6e41bUL, + 0x000734d2e61aUL, + 0x00072cdee019UL, + 0x000724dae218UL, + 0x0004dd261c67UL, + 0x0004d5221e66UL, + 0x0004cd2e1865UL, + 0x0004c52a1a64UL, + 0x0004fd361463UL, + 0x0004f5321662UL, + 0x0004ed3e1061UL, + 0x0004e53a1260UL, + 0x00049d060c6fUL, + 0x000495020e6eUL, + 0x00048d0e086dUL, + 0x0004850a0a6cUL, + 0x0004bd16046bUL, + 0x0004b512066aUL, + 0x0004ad1e0069UL, + 0x0004a51a0268UL, + 0x00045d663c77UL, + 0x000455623e76UL, + 0x00044d6e3875UL, + 0x0004456a3a74UL, + 0x00047d763473UL, + 0x000475723672UL, + 0x00046d7e3071UL, + 0x0004657a3270UL, + 0x00041d462c7fUL, + 0x000415422e7eUL, + 0x00040d4e287dUL, + 0x0004054a2a7cUL, + 0x00043d56247bUL, + 0x00043552267aUL, + 0x00042d5e2079UL, + 0x0004255a2278UL, + 0x0005dda65c47UL, + 0x0005d5a25e46UL, + 0x0005cdae5845UL, + 0x0005c5aa5a44UL, + 0x0005fdb65443UL, + 0x0005f5b25642UL, + 0x0005edbe5041UL, + 0x0005e5ba5240UL, + 0x00059d864c4fUL, + 0x000595824e4eUL, + 0x00058d8e484dUL, + 0x0005858a4a4cUL, + 0x0005bd96444bUL, + 0x0005b592464aUL, + 0x0005ad9e4049UL, + 0x0005a59a4248UL, + 0x00055de67c57UL, + 0x000555e27e56UL, + 0x00054dee7855UL, + 0x000545ea7a54UL, + 0x00057df67453UL, + 0x000575f27652UL, + 0x00056dfe7051UL, + 0x000565fa7250UL, + 0x00051dc66c5fUL, + 0x000515c26e5eUL, + 0x00050dce685dUL, + 0x000505ca6a5cUL, + 0x00053dd6645bUL, + 0x000535d2665aUL, + 0x00052dde6059UL, + 0x000525da6258UL, + 0x0002de279ca7UL, + 0x0002d6239ea6UL, + 0x0002ce2f98a5UL, + 0x0002c62b9aa4UL, + 0x0002fe3794a3UL, + 0x0002f63396a2UL, + 0x0002ee3f90a1UL, + 0x0002e63b92a0UL, + 0x00029e078cafUL, + 0x000296038eaeUL, + 0x00028e0f88adUL, + 0x0002860b8aacUL, + 0x0002be1784abUL, + 0x0002b61386aaUL, + 0x0002ae1f80a9UL, + 0x0002a61b82a8UL, + 0x00025e67bcb7UL, + 0x00025663beb6UL, + 0x00024e6fb8b5UL, + 0x0002466bbab4UL, + 0x00027e77b4b3UL, + 0x00027673b6b2UL, + 0x00026e7fb0b1UL, + 0x0002667bb2b0UL, + 0x00021e47acbfUL, + 0x00021643aebeUL, + 0x00020e4fa8bdUL, + 0x0002064baabcUL, + 0x00023e57a4bbUL, + 0x00023653a6baUL, + 0x00022e5fa0b9UL, + 0x0002265ba2b8UL, + 0x0003dea7dc87UL, + 0x0003d6a3de86UL, + 0x0003ceafd885UL, + 0x0003c6abda84UL, + 0x0003feb7d483UL, + 0x0003f6b3d682UL, + 0x0003eebfd081UL, + 0x0003e6bbd280UL, + 0x00039e87cc8fUL, + 0x00039683ce8eUL, + 0x00038e8fc88dUL, + 0x0003868bca8cUL, + 0x0003be97c48bUL, + 0x0003b693c68aUL, + 0x0003ae9fc089UL, + 0x0003a69bc288UL, + 0x00035ee7fc97UL, + 0x000356e3fe96UL, + 0x00034eeff895UL, + 0x000346ebfa94UL, + 0x00037ef7f493UL, + 0x000376f3f692UL, + 0x00036efff091UL, + 0x000366fbf290UL, + 0x00031ec7ec9fUL, + 0x000316c3ee9eUL, + 0x00030ecfe89dUL, + 0x000306cbea9cUL, + 0x00033ed7e49bUL, + 0x000336d3e69aUL, + 0x00032edfe099UL, + 0x000326dbe298UL, + 0x0000df271ce7UL, + 0x0000d7231ee6UL, + 0x0000cf2f18e5UL, + 0x0000c72b1ae4UL, + 0x0000ff3714e3UL, + 0x0000f73316e2UL, + 0x0000ef3f10e1UL, + 0x0000e73b12e0UL, + 0x00009f070cefUL, + 0x000097030eeeUL, + 0x00008f0f08edUL, + 0x0000870b0aecUL, + 0x0000bf1704ebUL, + 0x0000b71306eaUL, + 0x0000af1f00e9UL, + 0x0000a71b02e8UL, + 0x00005f673cf7UL, + 0x000057633ef6UL, + 0x00004f6f38f5UL, + 0x0000476b3af4UL, + 0x00007f7734f3UL, + 0x0000777336f2UL, + 0x00006f7f30f1UL, + 0x0000677b32f0UL, + 0x00001f472cffUL, + 0x000017432efeUL, + 0x00000f4f28fdUL, + 0x0000074b2afcUL, + 0x00003f5724fbUL, + 0x0000375326faUL, + 0x00002f5f20f9UL, + 0x0000275b22f8UL, + 0x0001dfa75cc7UL, + 0x0001d7a35ec6UL, + 0x0001cfaf58c5UL, + 0x0001c7ab5ac4UL, + 0x0001ffb754c3UL, + 0x0001f7b356c2UL, + 0x0001efbf50c1UL, + 0x0001e7bb52c0UL, + 0x00019f874ccfUL, + 0x000197834eceUL, + 0x00018f8f48cdUL, + 0x0001878b4accUL, + 0x0001bf9744cbUL, + 0x0001b79346caUL, + 0x0001af9f40c9UL, + 0x0001a79b42c8UL, + 0x00015fe77cd7UL, + 0x000157e37ed6UL, + 0x00014fef78d5UL, + 0x000147eb7ad4UL, + 0x00017ff774d3UL, + 0x000177f376d2UL, + 0x00016fff70d1UL, + 0x000167fb72d0UL, + 0x00011fc76cdfUL, + 0x000117c36edeUL, + 0x00010fcf68ddUL, + 0x000107cb6adcUL, + 0x00013fd764dbUL, + 0x000137d366daUL, + 0x00012fdf60d9UL, + 0x000127db62d8UL, + 0x000ed8249d27UL, + 0x000ed0209f26UL, + 0x000ec82c9925UL, + 0x000ec0289b24UL, + 0x000ef8349523UL, + 0x000ef0309722UL, + 0x000ee83c9121UL, + 0x000ee0389320UL, + 0x000e98048d2fUL, + 0x000e90008f2eUL, + 0x000e880c892dUL, + 0x000e80088b2cUL, + 0x000eb814852bUL, + 0x000eb010872aUL, + 0x000ea81c8129UL, + 0x000ea0188328UL, + 0x000e5864bd37UL, + 0x000e5060bf36UL, + 0x000e486cb935UL, + 0x000e4068bb34UL, + 0x000e7874b533UL, + 0x000e7070b732UL, + 0x000e687cb131UL, + 0x000e6078b330UL, + 0x000e1844ad3fUL, + 0x000e1040af3eUL, + 0x000e084ca93dUL, + 0x000e0048ab3cUL, + 0x000e3854a53bUL, + 0x000e3050a73aUL, + 0x000e285ca139UL, + 0x000e2058a338UL, + 0x000fd8a4dd07UL, + 0x000fd0a0df06UL, + 0x000fc8acd905UL, + 0x000fc0a8db04UL, + 0x000ff8b4d503UL, + 0x000ff0b0d702UL, + 0x000fe8bcd101UL, + 0x000fe0b8d300UL, + 0x000f9884cd0fUL, + 0x000f9080cf0eUL, + 0x000f888cc90dUL, + 0x000f8088cb0cUL, + 0x000fb894c50bUL, + 0x000fb090c70aUL, + 0x000fa89cc109UL, + 0x000fa098c308UL, + 0x000f58e4fd17UL, + 0x000f50e0ff16UL, + 0x000f48ecf915UL, + 0x000f40e8fb14UL, + 0x000f78f4f513UL, + 0x000f70f0f712UL, + 0x000f68fcf111UL, + 0x000f60f8f310UL, + 0x000f18c4ed1fUL, + 0x000f10c0ef1eUL, + 0x000f08cce91dUL, + 0x000f00c8eb1cUL, + 0x000f38d4e51bUL, + 0x000f30d0e71aUL, + 0x000f28dce119UL, + 0x000f20d8e318UL, + 0x000cd9241d67UL, + 0x000cd1201f66UL, + 0x000cc92c1965UL, + 0x000cc1281b64UL, + 0x000cf9341563UL, + 0x000cf1301762UL, + 0x000ce93c1161UL, + 0x000ce1381360UL, + 0x000c99040d6fUL, + 0x000c91000f6eUL, + 0x000c890c096dUL, + 0x000c81080b6cUL, + 0x000cb914056bUL, + 0x000cb110076aUL, + 0x000ca91c0169UL, + 0x000ca1180368UL, + 0x000c59643d77UL, + 0x000c51603f76UL, + 0x000c496c3975UL, + 0x000c41683b74UL, + 0x000c79743573UL, + 0x000c71703772UL, + 0x000c697c3171UL, + 0x000c61783370UL, + 0x000c19442d7fUL, + 0x000c11402f7eUL, + 0x000c094c297dUL, + 0x000c01482b7cUL, + 0x000c3954257bUL, + 0x000c3150277aUL, + 0x000c295c2179UL, + 0x000c21582378UL, + 0x000dd9a45d47UL, + 0x000dd1a05f46UL, + 0x000dc9ac5945UL, + 0x000dc1a85b44UL, + 0x000df9b45543UL, + 0x000df1b05742UL, + 0x000de9bc5141UL, + 0x000de1b85340UL, + 0x000d99844d4fUL, + 0x000d91804f4eUL, + 0x000d898c494dUL, + 0x000d81884b4cUL, + 0x000db994454bUL, + 0x000db190474aUL, + 0x000da99c4149UL, + 0x000da1984348UL, + 0x000d59e47d57UL, + 0x000d51e07f56UL, + 0x000d49ec7955UL, + 0x000d41e87b54UL, + 0x000d79f47553UL, + 0x000d71f07752UL, + 0x000d69fc7151UL, + 0x000d61f87350UL, + 0x000d19c46d5fUL, + 0x000d11c06f5eUL, + 0x000d09cc695dUL, + 0x000d01c86b5cUL, + 0x000d39d4655bUL, + 0x000d31d0675aUL, + 0x000d29dc6159UL, + 0x000d21d86358UL, + 0x000ada259da7UL, + 0x000ad2219fa6UL, + 0x000aca2d99a5UL, + 0x000ac2299ba4UL, + 0x000afa3595a3UL, + 0x000af23197a2UL, + 0x000aea3d91a1UL, + 0x000ae23993a0UL, + 0x000a9a058dafUL, + 0x000a92018faeUL, + 0x000a8a0d89adUL, + 0x000a82098bacUL, + 0x000aba1585abUL, + 0x000ab21187aaUL, + 0x000aaa1d81a9UL, + 0x000aa21983a8UL, + 0x000a5a65bdb7UL, + 0x000a5261bfb6UL, + 0x000a4a6db9b5UL, + 0x000a4269bbb4UL, + 0x000a7a75b5b3UL, + 0x000a7271b7b2UL, + 0x000a6a7db1b1UL, + 0x000a6279b3b0UL, + 0x000a1a45adbfUL, + 0x000a1241afbeUL, + 0x000a0a4da9bdUL, + 0x000a0249abbcUL, + 0x000a3a55a5bbUL, + 0x000a3251a7baUL, + 0x000a2a5da1b9UL, + 0x000a2259a3b8UL, + 0x000bdaa5dd87UL, + 0x000bd2a1df86UL, + 0x000bcaadd985UL, + 0x000bc2a9db84UL, + 0x000bfab5d583UL, + 0x000bf2b1d782UL, + 0x000beabdd181UL, + 0x000be2b9d380UL, + 0x000b9a85cd8fUL, + 0x000b9281cf8eUL, + 0x000b8a8dc98dUL, + 0x000b8289cb8cUL, + 0x000bba95c58bUL, + 0x000bb291c78aUL, + 0x000baa9dc189UL, + 0x000ba299c388UL, + 0x000b5ae5fd97UL, + 0x000b52e1ff96UL, + 0x000b4aedf995UL, + 0x000b42e9fb94UL, + 0x000b7af5f593UL, + 0x000b72f1f792UL, + 0x000b6afdf191UL, + 0x000b62f9f390UL, + 0x000b1ac5ed9fUL, + 0x000b12c1ef9eUL, + 0x000b0acde99dUL, + 0x000b02c9eb9cUL, + 0x000b3ad5e59bUL, + 0x000b32d1e79aUL, + 0x000b2adde199UL, + 0x000b22d9e398UL, + 0x0008db251de7UL, + 0x0008d3211fe6UL, + 0x0008cb2d19e5UL, + 0x0008c3291be4UL, + 0x0008fb3515e3UL, + 0x0008f33117e2UL, + 0x0008eb3d11e1UL, + 0x0008e33913e0UL, + 0x00089b050defUL, + 0x000893010feeUL, + 0x00088b0d09edUL, + 0x000883090becUL, + 0x0008bb1505ebUL, + 0x0008b31107eaUL, + 0x0008ab1d01e9UL, + 0x0008a31903e8UL, + 0x00085b653df7UL, + 0x000853613ff6UL, + 0x00084b6d39f5UL, + 0x000843693bf4UL, + 0x00087b7535f3UL, + 0x0008737137f2UL, + 0x00086b7d31f1UL, + 0x0008637933f0UL, + 0x00081b452dffUL, + 0x000813412ffeUL, + 0x00080b4d29fdUL, + 0x000803492bfcUL, + 0x00083b5525fbUL, + 0x0008335127faUL, + 0x00082b5d21f9UL, + 0x0008235923f8UL, + 0x0009dba55dc7UL, + 0x0009d3a15fc6UL, + 0x0009cbad59c5UL, + 0x0009c3a95bc4UL, + 0x0009fbb555c3UL, + 0x0009f3b157c2UL, + 0x0009ebbd51c1UL, + 0x0009e3b953c0UL, + 0x00099b854dcfUL, + 0x000993814fceUL, + 0x00098b8d49cdUL, + 0x000983894bccUL, + 0x0009bb9545cbUL, + 0x0009b39147caUL, + 0x0009ab9d41c9UL, + 0x0009a39943c8UL, + 0x00095be57dd7UL, + 0x000953e17fd6UL, + 0x00094bed79d5UL, + 0x000943e97bd4UL, + 0x00097bf575d3UL, + 0x000973f177d2UL, + 0x00096bfd71d1UL, + 0x000963f973d0UL, + 0x00091bc56ddfUL, + 0x000913c16fdeUL, + 0x00090bcd69ddUL, + 0x000903c96bdcUL, + 0x00093bd565dbUL, + 0x000933d167daUL, + 0x00092bdd61d9UL, + 0x000923d963d8UL + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "union_find.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef struct unionfind unionfind_t; + +struct unionfind +{ + struct ufrec *data; +}; + +struct ufrec +{ + // the parent of this node. If a node's parent is its own index, + // then it is a root. +#ifdef IMLIB_ENABLE_HIGH_RES_APRILTAGS + uint32_t parent; +#else + uint16_t parent; +#endif +}; + +static inline unionfind_t *unionfind_create(uint32_t maxid) +{ + unionfind_t *uf = (unionfind_t*) fb_alloc(sizeof(unionfind_t), FB_ALLOC_NO_HINT); + uf->data = (struct ufrec*) fb_alloc((maxid+1) * sizeof(struct ufrec), FB_ALLOC_NO_HINT); + for (int i = 0; i <= maxid; i++) { + uf->data[i].parent = i; + } + return uf; +} + +static inline void unionfind_destroy(unionfind_t *pt) +{ + fb_free(pt->data); + fb_free(pt); +} + +/* +static inline uint32_t unionfind_get_representative(unionfind_t *uf, uint32_t id) +{ + // base case: a node is its own parent + if (uf->data[id].parent == id) + return id; + + // otherwise, recurse + uint32_t root = unionfind_get_representative(uf, uf->data[id].parent); + + // short circuit the path. [XXX This write prevents tail recursion] + uf->data[id].parent = root; + + return root; +} +*/ + +// this one seems to be every-so-slightly faster than the recursive +// version above. +static inline uint32_t unionfind_get_representative(unionfind_t *uf, uint32_t id) +{ + uint32_t root = id; + + // chase down the root + while (uf->data[root].parent != root) { + root = uf->data[root].parent; + } + + // go back and collapse the tree. + // + // XXX: on some of our workloads that have very shallow trees + // (e.g. image segmentation), we are actually faster not doing + // this... + while (uf->data[id].parent != root) { + uint32_t tmp = uf->data[id].parent; + uf->data[id].parent = root; + id = tmp; + } + + return root; +} + +static inline uint32_t unionfind_connect(unionfind_t *uf, uint32_t aid, uint32_t bid) +{ + uint32_t aroot = unionfind_get_representative(uf, aid); + uint32_t broot = unionfind_get_representative(uf, bid); + + if (aroot != broot) + uf->data[broot].parent = aroot; + + return aroot; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "union_find.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "apriltag_quad_thresh.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +// limitation: image size must be <32768 in width and height. This is +// because we use a fixed-point 16 bit integer representation with one +// fractional bit. + +static inline uint32_t u64hash_2(uint64_t x) { + return (2654435761 * x) >> 32; + return (uint32_t) x; +} + +struct uint32_zarray_entry +{ + uint32_t id; + zarray_t *cluster; + + struct uint32_zarray_entry *next; +}; + +#ifndef M_PI +# define M_PI 3.141592653589793238462643383279502884196 +#endif + +struct pt +{ + // Note: these represent 2*actual value. + uint16_t x, y; + float theta; + int16_t gx, gy; +}; + +struct remove_vertex +{ + int i; // which vertex to remove? + int left, right; // left vertex, right vertex + + float err; +}; + +struct segment +{ + int is_vertex; + + // always greater than zero, but right can be > size, which denotes + // a wrap around back to the beginning of the points. and left < right. + int left, right; +}; + +struct line_fit_pt +{ + float Mx, My; + float Mxx, Myy, Mxy; + float W; // total weight +}; + +static inline void ptsort(struct pt *pts, int sz) +{ +#define MAYBE_SWAP(arr,apos,bpos) \ + if (arr[apos].theta > arr[bpos].theta) { \ + tmp = arr[apos]; arr[apos] = arr[bpos]; arr[bpos] = tmp; \ + }; + + if (sz <= 1) + return; + + if (sz == 2) { + struct pt tmp; + MAYBE_SWAP(pts, 0, 1); + return; + } + + // NB: Using less-branch-intensive sorting networks here on the + // hunch that it's better for performance. + if (sz == 3) { // 3 element bubble sort is optimal + struct pt tmp; + MAYBE_SWAP(pts, 0, 1); + MAYBE_SWAP(pts, 1, 2); + MAYBE_SWAP(pts, 0, 1); + return; + } + + if (sz == 4) { // 4 element optimal sorting network. + struct pt tmp; + MAYBE_SWAP(pts, 0, 1); // sort each half, like a merge sort + MAYBE_SWAP(pts, 2, 3); + MAYBE_SWAP(pts, 0, 2); // minimum value is now at 0. + MAYBE_SWAP(pts, 1, 3); // maximum value is now at end. + MAYBE_SWAP(pts, 1, 2); // that only leaves the middle two. + return; + } + + if (sz == 5) { + // this 9-step swap is optimal for a sorting network, but two + // steps slower than a generic sort. + struct pt tmp; + MAYBE_SWAP(pts, 0, 1); // sort each half (3+2), like a merge sort + MAYBE_SWAP(pts, 3, 4); + MAYBE_SWAP(pts, 1, 2); + MAYBE_SWAP(pts, 0, 1); + MAYBE_SWAP(pts, 0, 3); // minimum element now at 0 + MAYBE_SWAP(pts, 2, 4); // maximum element now at end + MAYBE_SWAP(pts, 1, 2); // now resort the three elements 1-3. + MAYBE_SWAP(pts, 2, 3); + MAYBE_SWAP(pts, 1, 2); + return; + } + +#undef MAYBE_SWAP + + // a merge sort with temp storage. + + struct pt *tmp = fb_alloc(sizeof(struct pt) * sz, FB_ALLOC_NO_HINT); + + memcpy(tmp, pts, sizeof(struct pt) * sz); + + int asz = sz/2; + int bsz = sz - asz; + + struct pt *as = &tmp[0]; + struct pt *bs = &tmp[asz]; + + ptsort(as, asz); + ptsort(bs, bsz); + +#define MERGE(apos,bpos) \ + if (as[apos].theta < bs[bpos].theta) \ + pts[outpos++] = as[apos++]; \ + else \ + pts[outpos++] = bs[bpos++]; + + int apos = 0, bpos = 0, outpos = 0; + while (apos + 8 < asz && bpos + 8 < bsz) { + MERGE(apos,bpos); MERGE(apos,bpos); MERGE(apos,bpos); MERGE(apos,bpos); + MERGE(apos,bpos); MERGE(apos,bpos); MERGE(apos,bpos); MERGE(apos,bpos); + } + + while (apos < asz && bpos < bsz) { + MERGE(apos,bpos); + } + + if (apos < asz) + memcpy(&pts[outpos], &as[apos], (asz-apos)*sizeof(struct pt)); + if (bpos < bsz) + memcpy(&pts[outpos], &bs[bpos], (bsz-bpos)*sizeof(struct pt)); + + fb_free(tmp); // tmp + +#undef MERGE +} + +// lfps contains *cumulative* moments for N points, with +// index j reflecting points [0,j] (inclusive). +// +// fit a line to the points [i0, i1] (inclusive). i0, i1 are both [0, +// sz) if i1 < i0, we treat this as a wrap around. +void fit_line(struct line_fit_pt *lfps, int sz, int i0, int i1, float *lineparm, float *err, float *mse) +{ + assert(i0 != i1); + assert(i0 >= 0 && i1 >= 0 && i0 < sz && i1 < sz); + + float Mx, My, Mxx, Myy, Mxy, W; + int N; // how many points are included in the set? + + if (i0 < i1) { + N = i1 - i0 + 1; + + Mx = lfps[i1].Mx; + My = lfps[i1].My; + Mxx = lfps[i1].Mxx; + Mxy = lfps[i1].Mxy; + Myy = lfps[i1].Myy; + W = lfps[i1].W; + + if (i0 > 0) { + Mx -= lfps[i0-1].Mx; + My -= lfps[i0-1].My; + Mxx -= lfps[i0-1].Mxx; + Mxy -= lfps[i0-1].Mxy; + Myy -= lfps[i0-1].Myy; + W -= lfps[i0-1].W; + } + + } else { + // i0 > i1, e.g. [15, 2]. Wrap around. + assert(i0 > 0); + + Mx = lfps[sz-1].Mx - lfps[i0-1].Mx; + My = lfps[sz-1].My - lfps[i0-1].My; + Mxx = lfps[sz-1].Mxx - lfps[i0-1].Mxx; + Mxy = lfps[sz-1].Mxy - lfps[i0-1].Mxy; + Myy = lfps[sz-1].Myy - lfps[i0-1].Myy; + W = lfps[sz-1].W - lfps[i0-1].W; + + Mx += lfps[i1].Mx; + My += lfps[i1].My; + Mxx += lfps[i1].Mxx; + Mxy += lfps[i1].Mxy; + Myy += lfps[i1].Myy; + W += lfps[i1].W; + + N = sz - i0 + i1 + 1; + } + + assert(N >= 2); + + float Ex = Mx / W; + float Ey = My / W; + float Cxx = Mxx / W - Ex*Ex; + float Cxy = Mxy / W - Ex*Ey; + float Cyy = Myy / W - Ey*Ey; + + float nx, ny; + + if (1) { + // on iOS about 5% of total CPU spent in these trig functions. + // 85 ms per frame on 5S, example.pnm + // + // XXX this was using the float-precision atan2. Was there a case where + // we needed that precision? Seems doubtful. + float normal_theta = .5 * atan2f(-2*Cxy, (Cyy - Cxx)); + nx = cosf(normal_theta); + ny = sinf(normal_theta); + } else { + // 73.5 ms per frame on 5S, example.pnm + float ty = -2*Cxy; + float tx = (Cyy - Cxx); + float mag = ty*ty + tx*tx; + + if (mag == 0) { + nx = 1; + ny = 0; + } else { + float norm = sqrtf(ty*ty + tx*tx); + tx /= norm; + + // ty is now sin(2theta) + // tx is now cos(2theta). We want sin(theta) and cos(theta) + + // due to precision err, tx could still have slightly too large magnitude. + if (tx > 1) { + ny = 0; + nx = 1; + } else if (tx < -1) { + ny = 1; + nx = 0; + } else { + // half angle formula + ny = sqrtf((1 - tx)/2); + nx = sqrtf((1 + tx)/2); + + // pick a consistent branch cut + if (ty < 0) + ny = - ny; + } + } + } + + if (lineparm) { + lineparm[0] = Ex; + lineparm[1] = Ey; + lineparm[2] = nx; + lineparm[3] = ny; + } + + // sum of squared errors = + // + // SUM_i ((p_x - ux)*nx + (p_y - uy)*ny)^2 + // SUM_i nx*nx*(p_x - ux)^2 + 2nx*ny(p_x -ux)(p_y-uy) + ny*ny*(p_y-uy)*(p_y-uy) + // nx*nx*SUM_i((p_x -ux)^2) + 2nx*ny*SUM_i((p_x-ux)(p_y-uy)) + ny*ny*SUM_i((p_y-uy)^2) + // + // nx*nx*N*Cxx + 2nx*ny*N*Cxy + ny*ny*N*Cyy + + // sum of squared errors + if (err) + *err = nx*nx*N*Cxx + 2*nx*ny*N*Cxy + ny*ny*N*Cyy; + + // mean squared error + if (mse) + *mse = nx*nx*Cxx + 2*nx*ny*Cxy + ny*ny*Cyy; +} + +int pt_compare_theta(const void *_a, const void *_b) +{ + struct pt *a = (struct pt*) _a; + struct pt *b = (struct pt*) _b; + + return (a->theta < b->theta) ? -1 : 1; +} + +int err_compare_descending(const void *_a, const void *_b) +{ + const float *a = _a; + const float *b = _b; + + return ((*a) < (*b)) ? 1 : -1; +} + +/* + + 1. Identify A) white points near a black point and B) black points near a white point. + + 2. Find the connected components within each of the classes above, + yielding clusters of "white-near-black" and + "black-near-white". (These two classes are kept separate). Each + segment has a unique id. + + 3. For every pair of "white-near-black" and "black-near-white" + clusters, find the set of points that are in one and adjacent to the + other. In other words, a "boundary" layer between the two + clusters. (This is actually performed by iterating over the pixels, + rather than pairs of clusters.) Critically, this helps keep nearby + edges from becoming connected. +*/ +int quad_segment_maxima(apriltag_detector_t *td, zarray_t *cluster, struct line_fit_pt *lfps, int indices[4]) +{ + int sz = zarray_size(cluster); + + // ksz: when fitting points, how many points on either side do we consider? + // (actual "kernel" width is 2ksz). + // + // This value should be about: 0.5 * (points along shortest edge). + // + // If all edges were equally-sized, that would give a value of + // sz/8. We make it somewhat smaller to account for tags at high + // aspects. + + // XXX Tunable. Maybe make a multiple of JPEG block size to increase robustness + // to JPEG compression artifacts? + int ksz = imin(20, sz / 12); + + // can't fit a quad if there are too few points. + if (ksz < 2) + return 0; + +// printf("sz %5d, ksz %3d\n", sz, ksz); + + float *errs = fb_alloc(sz * sizeof(float), FB_ALLOC_NO_HINT); + + for (int i = 0; i < sz; i++) { + fit_line(lfps, sz, (i + sz - ksz) % sz, (i + ksz) % sz, NULL, &errs[i], NULL); + } + + // apply a low-pass filter to errs + if (1) { + float *y = fb_alloc(sz * sizeof(float), FB_ALLOC_NO_HINT); + + // how much filter to apply? + + // XXX Tunable + float sigma = 1; // was 3 + + // cutoff = exp(-j*j/(2*sigma*sigma)); + // log(cutoff) = -j*j / (2*sigma*sigma) + // log(cutoff)*2*sigma*sigma = -j*j; + + // how big a filter should we use? We make our kernel big + // enough such that we represent any values larger than + // 'cutoff'. + + // XXX Tunable (though not super useful to change) + float cutoff = 0.05; + int fsz = sqrt(-log(cutoff)*2*sigma*sigma) + 1; + fsz = 2*fsz + 1; + + // For default values of cutoff = 0.05, sigma = 3, + // we have fsz = 17. + float *f = fb_alloc(fsz * sizeof(float), FB_ALLOC_NO_HINT); + + for (int i = 0; i < fsz; i++) { + int j = i - fsz / 2; + f[i] = exp(-j*j/(2*sigma*sigma)); + } + + for (int iy = 0; iy < sz; iy++) { + float acc = 0; +#ifdef OPTIMIZED + int index = (iy - fsz/2 + sz) % sz; + for (int i = 0; i < fsz; i++) { + acc += errs[index] * f[i]; + index++; + if (index >= sz) // faster to compare than divide (%) + index -= sz; + } +#else + for (int i = 0; i < fsz; i++) { + acc += errs[(iy + i - fsz / 2 + sz) % sz] * f[i]; + } +#endif + y[iy] = acc; + } + + fb_free(f); // f + memcpy(errs, y, sz * sizeof(float)); + fb_free(y); // y + } + + int *maxima = fb_alloc(sz * sizeof(int), FB_ALLOC_NO_HINT); + float *maxima_errs = fb_alloc(sz * sizeof(float), FB_ALLOC_NO_HINT); + int nmaxima = 0; + + for (int i = 0; i < sz; i++) { + if (errs[i] > errs[(i+1)%sz] && errs[i] > errs[(i+sz-1)%sz]) { + maxima[nmaxima] = i; + maxima_errs[nmaxima] = errs[i]; + nmaxima++; + } + } + + // if we didn't get at least 4 maxima, we can't fit a quad. + if (nmaxima < 4) + { + fb_free(maxima_errs); + fb_free(maxima); + fb_free(errs); + return 0; + } + + // select only the best maxima if we have too many + int max_nmaxima = td->qtp.max_nmaxima; + + if (nmaxima > max_nmaxima) { + float *maxima_errs_copy = fb_alloc(nmaxima * sizeof(float), FB_ALLOC_NO_HINT); + memcpy(maxima_errs_copy, maxima_errs, nmaxima * sizeof(float)); + + // throw out all but the best handful of maxima. Sorts descending. + qsort(maxima_errs_copy, nmaxima, sizeof(float), err_compare_descending); + + float maxima_thresh = maxima_errs_copy[max_nmaxima]; + int out = 0; + for (int in = 0; in < nmaxima; in++) { + if (maxima_errs[in] <= maxima_thresh) + continue; + maxima[out++] = maxima[in]; + } + nmaxima = out; + + fb_free(maxima_errs_copy); // maxima_errs_copy + } + + fb_free(maxima_errs); // maxima_errs + fb_free(maxima); // maxima + fb_free(errs); // errs + + int best_indices[4]; + float best_error = HUGE_VALF; + + float err01, err12, err23, err30; + float mse01, mse12, mse23, mse30; + float params01[4], params12[4], params23[4], params30[4]; + + // disallow quads where the angle is less than a critical value. + float max_dot = cos(td->qtp.critical_rad); //25*M_PI/180); + + for (int m0 = 0; m0 < nmaxima - 3; m0++) { + int i0 = maxima[m0]; + + for (int m1 = m0+1; m1 < nmaxima - 2; m1++) { + int i1 = maxima[m1]; + + fit_line(lfps, sz, i0, i1, params01, &err01, &mse01); + + if (mse01 > td->qtp.max_line_fit_mse) + continue; + + for (int m2 = m1+1; m2 < nmaxima - 1; m2++) { + int i2 = maxima[m2]; + + fit_line(lfps, sz, i1, i2, params12, &err12, &mse12); + if (mse12 > td->qtp.max_line_fit_mse) + continue; + + float dot = params01[2]*params12[2] + params01[3]*params12[3]; + if (fabs(dot) > max_dot) + continue; + + for (int m3 = m2+1; m3 < nmaxima; m3++) { + int i3 = maxima[m3]; + + fit_line(lfps, sz, i2, i3, params23, &err23, &mse23); + if (mse23 > td->qtp.max_line_fit_mse) + continue; + + fit_line(lfps, sz, i3, i0, params30, &err30, &mse30); + if (mse30 > td->qtp.max_line_fit_mse) + continue; + + float err = err01 + err12 + err23 + err30; + if (err < best_error) { + best_error = err; + best_indices[0] = i0; + best_indices[1] = i1; + best_indices[2] = i2; + best_indices[3] = i3; + } + } + } + } + } + + if (best_error == HUGE_VALF) + return 0; + + for (int i = 0; i < 4; i++) + indices[i] = best_indices[i]; + + if (best_error / sz < td->qtp.max_line_fit_mse) + return 1; + return 0; +} + +// return 1 if the quad looks okay, 0 if it should be discarded +int fit_quad(apriltag_detector_t *td, image_u8_t *im, zarray_t *cluster, struct quad *quad, bool overrideMode) +{ + int res = 0; + + int sz = zarray_size(cluster); + if (sz < 4) // can't fit a quad to less than 4 points + return 0; + + ///////////////////////////////////////////////////////////// + // Step 1. Sort points so they wrap around the center of the + // quad. We will constrain our quad fit to simply partition this + // ordered set into 4 groups. + + // compute a bounding box so that we can order the points + // according to their angle WRT the center. + int32_t xmax = 0, xmin = INT32_MAX, ymax = 0, ymin = INT32_MAX; + + for (int pidx = 0; pidx < zarray_size(cluster); pidx++) { + struct pt *p; + zarray_get_volatile(cluster, pidx, &p); + + xmax = imax(xmax, p->x); + xmin = imin(xmin, p->x); + + ymax = imax(ymax, p->y); + ymin = imin(ymin, p->y); + } + + // add some noise to (cx,cy) so that pixels get a more diverse set + // of theta estimates. This will help us remove more points. + // (Only helps a small amount. The actual noise values here don't + // matter much at all, but we want them [-1, 1]. (XXX with + // fixed-point, should range be bigger?) + float cx = (xmin + xmax) * 0.5 + 0.05118; + float cy = (ymin + ymax) * 0.5 + -0.028581; + + float dot = 0; + + for (int pidx = 0; pidx < zarray_size(cluster); pidx++) { + struct pt *p; + zarray_get_volatile(cluster, pidx, &p); + + float dx = p->x - cx; + float dy = p->y - cy; + + p->theta = atan2f(dy, dx); + + dot += dx*p->gx + dy*p->gy; +// p->theta = terrible_atan2(dy, dx); + } + + // Ensure that the black border is inside the white border. + if ((!overrideMode) && (dot < 0)) + return 0; + + // we now sort the points according to theta. This is a prepatory + // step for segmenting them into four lines. + if (1) { + // zarray_sort(cluster, pt_compare_theta); + ptsort((struct pt*) cluster->data, zarray_size(cluster)); + + // remove duplicate points. (A byproduct of our segmentation system.) + if (1) { + int outpos = 1; + + struct pt *last; + zarray_get_volatile(cluster, 0, &last); + + for (int i = 1; i < sz; i++) { + + struct pt *p; + zarray_get_volatile(cluster, i, &p); + + if (p->x != last->x || p->y != last->y) { + + if (i != outpos) { + struct pt *out; + zarray_get_volatile(cluster, outpos, &out); + memcpy(out, p, sizeof(struct pt)); + } + + outpos++; + } + + last = p; + } + + cluster->size = outpos; + sz = outpos; + } + + } else { + // This is a counting sort in which we retain at most one + // point for every bucket; the bucket index is computed from + // theta. Since a good quad completes a complete revolution, + // there's reason to think that we should get a good + // distribution of thetas. We might "lose" a few points due + // to collisions, but this shouldn't affect quality very much. + + // XXX tunable. Increase to reduce the likelihood of "losing" + // points due to collisions. + int nbuckets = 4*sz; + +#define ASSOC 2 + struct pt v[nbuckets][ASSOC]; + memset(v, 0, sizeof(v)); + + // put each point into a bucket. + for (int i = 0; i < sz; i++) { + struct pt *p; + zarray_get_volatile(cluster, i, &p); + + assert(p->theta >= -M_PI && p->theta <= M_PI); + + int bucket = (nbuckets - 1) * (p->theta + M_PI) / (2*M_PI); + assert(bucket >= 0 && bucket < nbuckets); + + for (int i = 0; i < ASSOC; i++) { + if (v[bucket][i].theta == 0) { + v[bucket][i] = *p; + break; + } + } + } + + // collect the points from the buckets and put them back into the array. + int outsz = 0; + for (int i = 0; i < nbuckets; i++) { + for (int j = 0; j < ASSOC; j++) { + if (v[i][j].theta != 0) { + zarray_set(cluster, outsz, &v[i][j], NULL); + outsz++; + } + } + } + + zarray_truncate(cluster, outsz); + sz = outsz; + } + + if (sz < 4) + return 0; + + ///////////////////////////////////////////////////////////// + // Step 2. Precompute statistics that allow line fit queries to be + // efficiently computed for any contiguous range of indices. + + struct line_fit_pt *lfps = fb_alloc0(sz * sizeof(struct line_fit_pt), FB_ALLOC_NO_HINT); + + for (int i = 0; i < sz; i++) { + struct pt *p; + zarray_get_volatile(cluster, i, &p); + + if (i > 0) { + memcpy(&lfps[i], &lfps[i-1], sizeof(struct line_fit_pt)); + } + + if (0) { + // we now undo our fixed-point arithmetic. + float delta = 0.5; + float x = p->x * .5 + delta; + float y = p->y * .5 + delta; + float W; + + for (int dy = -1; dy <= 1; dy++) { + int iy = y + dy; + + if (iy < 0 || iy + 1 >= im->height) + continue; + + for (int dx = -1; dx <= 1; dx++) { + int ix = x + dx; + + if (ix < 0 || ix + 1 >= im->width) + continue; + + int grad_x = im->buf[iy * im->stride + ix + 1] - + im->buf[iy * im->stride + ix - 1]; + + int grad_y = im->buf[(iy+1) * im->stride + ix] - + im->buf[(iy-1) * im->stride + ix]; + + W = sqrtf(grad_x*grad_x + grad_y*grad_y) + 1; + +// float fx = x + dx, fy = y + dy; + float fx = ix + .5, fy = iy + .5; + lfps[i].Mx += W * fx; + lfps[i].My += W * fy; + lfps[i].Mxx += W * fx * fx; + lfps[i].Mxy += W * fx * fy; + lfps[i].Myy += W * fy * fy; + lfps[i].W += W; + } + } + } else { + // we now undo our fixed-point arithmetic. + float delta = 0.5; // adjust for pixel center bias + float x = p->x * .5 + delta; + float y = p->y * .5 + delta; + int ix = x, iy = y; + float W = 1; + + if (ix > 0 && ix+1 < im->width && iy > 0 && iy+1 < im->height) { + int grad_x = im->buf[iy * im->stride + ix + 1] - + im->buf[iy * im->stride + ix - 1]; + + int grad_y = im->buf[(iy+1) * im->stride + ix] - + im->buf[(iy-1) * im->stride + ix]; + + // XXX Tunable. How to shape the gradient magnitude? + W = sqrt(grad_x*grad_x + grad_y*grad_y) + 1; + } + + float fx = x, fy = y; + lfps[i].Mx += W * fx; + lfps[i].My += W * fy; + lfps[i].Mxx += W * fx * fx; + lfps[i].Mxy += W * fx * fy; + lfps[i].Myy += W * fy * fy; + lfps[i].W += W; + } + } + + int indices[4]; + if (1) { + if (!quad_segment_maxima(td, cluster, lfps, indices)) + goto finish; + } + +// printf("%d %d %d %d\n", indices[0], indices[1], indices[2], indices[3]); + + if (0) { + // no refitting here; just use those points as the vertices. + // Note, this is useful for debugging, but pretty bad in + // practice since this code path also omits several + // plausibility checks that save us tons of time in quad + // decoding. + for (int i = 0; i < 4; i++) { + struct pt *p; + zarray_get_volatile(cluster, indices[i], &p); + + quad->p[i][0] = .5*p->x; // undo fixed-point arith. + quad->p[i][1] = .5*p->y; + } + + res = 1; + + } else { + float lines[4][4]; + + for (int i = 0; i < 4; i++) { + int i0 = indices[i]; + int i1 = indices[(i+1)&3]; + + if (0) { + // if there are enough points, skip the points near the corners + // (because those tend not to be very good.) + if (i1-i0 > 8) { + int t = (i1-i0)/6; + if (t < 0) + t = -t; + + i0 = (i0 + t) % sz; + i1 = (i1 + sz - t) % sz; + } + } + + float err; + fit_line(lfps, sz, i0, i1, lines[i], NULL, &err); + + if (err > td->qtp.max_line_fit_mse) { + res = 0; + goto finish; + } + } + + for (int i = 0; i < 4; i++) { + // solve for the intersection of lines (i) and (i+1)&3. + // p0 + lambda0*u0 = p1 + lambda1*u1, where u0 and u1 + // are the line directions. + // + // lambda0*u0 - lambda1*u1 = (p1 - p0) + // + // rearrange (solve for lambdas) + // + // [u0_x -u1_x ] [lambda0] = [ p1_x - p0_x ] + // [u0_y -u1_y ] [lambda1] [ p1_y - p0_y ] + // + // remember that lines[i][0,1] = p, lines[i][2,3] = NORMAL vector. + // We want the unit vector, so we need the perpendiculars. Thus, below + // we have swapped the x and y components and flipped the y components. + + float A00 = lines[i][3], A01 = -lines[(i+1)&3][3]; + float A10 = -lines[i][2], A11 = lines[(i+1)&3][2]; + float B0 = -lines[i][0] + lines[(i+1)&3][0]; + float B1 = -lines[i][1] + lines[(i+1)&3][1]; + + float det = A00 * A11 - A10 * A01; + + // inverse. + float W00 = A11 / det, W01 = -A01 / det; + if (fabs(det) < 0.001) { + res = 0; + goto finish; + } + + // solve + float L0 = W00*B0 + W01*B1; + + // compute intersection + quad->p[i][0] = lines[i][0] + L0*A00; + quad->p[i][1] = lines[i][1] + L0*A10; + + if (0) { + // we should get the same intersection starting + // from point p1 and moving L1*u1. + float W10 = -A10 / det, W11 = A00 / det; + float L1 = W10*B0 + W11*B1; + + float x = lines[(i+1)&3][0] - L1*A10; + float y = lines[(i+1)&3][1] - L1*A11; + assert(fabs(x - quad->p[i][0]) < 0.001 && + fabs(y - quad->p[i][1]) < 0.001); + } + + res = 1; + } + } + + // reject quads that are too small + if (1) { + float area = 0; + + // get area of triangle formed by points 0, 1, 2, 0 + float length[3], p; + for (int i = 0; i < 3; i++) { + int idxa = i; // 0, 1, 2, + int idxb = (i+1) % 3; // 1, 2, 0 + length[i] = sqrt(sq(quad->p[idxb][0] - quad->p[idxa][0]) + + sq(quad->p[idxb][1] - quad->p[idxa][1])); + } + p = (length[0] + length[1] + length[2]) / 2; + + area += sqrt(p*(p-length[0])*(p-length[1])*(p-length[2])); + + // get area of triangle formed by points 2, 3, 0, 2 + for (int i = 0; i < 3; i++) { + int idxs[] = { 2, 3, 0, 2 }; + int idxa = idxs[i]; + int idxb = idxs[i+1]; + length[i] = sqrt(sq(quad->p[idxb][0] - quad->p[idxa][0]) + + sq(quad->p[idxb][1] - quad->p[idxa][1])); + } + p = (length[0] + length[1] + length[2]) / 2; + + area += sqrt(p*(p-length[0])*(p-length[1])*(p-length[2])); + + // we don't actually know the family yet (quad detection is generic.) + // This threshold is based on a 6x6 tag (which is actually 8x8) +// int d = fam->d + fam->black_border*2; + int d = 8; + if (area < d*d) { + res = 0; + goto finish; + } + } + + // reject quads whose cumulative angle change isn't equal to 2PI + if (1) { + float total = 0; + + for (int i = 0; i < 4; i++) { + int i0 = i, i1 = (i+1)&3, i2 = (i+2)&3; + + float theta0 = atan2f(quad->p[i0][1] - quad->p[i1][1], + quad->p[i0][0] - quad->p[i1][0]); + float theta1 = atan2f(quad->p[i2][1] - quad->p[i1][1], + quad->p[i2][0] - quad->p[i1][0]); + + float dtheta = theta0 - theta1; + if (dtheta < 0) + dtheta += 2*M_PI; + + if (dtheta < td->qtp.critical_rad || dtheta > (M_PI - td->qtp.critical_rad)) + res = 0; + + total += dtheta; + } + + // looking for 2PI + if (total < 6.2 || total > 6.4) { + res = 0; + goto finish; + } + } + + // adjust pixel coordinates; all math up 'til now uses pixel + // coordinates in which (0,0) is the lower left corner. But each + // pixel actually spans from to [x, x+1), [y, y+1) the mean value of which + // is +.5 higher than x & y. +/* float delta = .5; + for (int i = 0; i < 4; i++) { + quad->p[i][0] += delta; + quad->p[i][1] += delta; + } +*/ + finish: + + fb_free(lfps); // lfps + + return res; +} + +#ifdef OPTIMIZED +#define DO_UNIONFIND(dx, dy) if (im->buf[y*s + dy*s + x + dx] == v) { broot = unionfind_get_representative(uf, y*w + dy*w + x + dx); if (aroot != broot) uf->data[broot].parent = aroot; } + +static void do_unionfind_line(unionfind_t *uf, image_u8_t *im, int h, int w, int s, int y) +{ + assert(y+1 < im->height); + uint8_t v, *p; + p = &im->buf[y*s + 1]; + for (int x = 1; x < w - 1; x++) { + v = *p++; //im->buf[y*s + x]; + + if (v == 127) + continue; + uint32_t broot; + uint32_t aroot = unionfind_get_representative(uf, y*w+x); + // (dx,dy) pairs for 8 connectivity: + // (REFERENCE) (1, 0) + // (-1, 1) (0, 1) (1, 1) + // + DO_UNIONFIND(1, 0); + DO_UNIONFIND(0, 1); + if (v == 255) { + DO_UNIONFIND(-1, 1); + DO_UNIONFIND(1, 1); + } + } +} +#else // not optimized +#define DO_UNIONFIND(dx, dy) if (im->buf[y*s + dy*s + x + dx] == v) unionfind_connect(uf, y*w + x, y*w + dy*w + x + dx); + +static void do_unionfind_line(unionfind_t *uf, image_u8_t *im, int h, int w, int s, int y) +{ + assert(y+1 < im->height); + + for (int x = 1; x < w - 1; x++) { + uint8_t v = im->buf[y*s + x]; + + if (v == 127) + continue; + + // (dx,dy) pairs for 8 connectivity: + // (REFERENCE) (1, 0) + // (-1, 1) (0, 1) (1, 1) + // + DO_UNIONFIND(1, 0); + DO_UNIONFIND(0, 1); + if (v == 255) { + DO_UNIONFIND(-1, 1); + DO_UNIONFIND(1, 1); + } + } +} +#undef DO_UNIONFIND +#endif // OPTIMIZED + +image_u8_t *threshold(apriltag_detector_t *td, image_u8_t *im) +{ + int w = im->width, h = im->height, s = im->stride; + assert(w < 32768); + assert(h < 32768); + + image_u8_t *threshim = fb_alloc(sizeof(image_u8_t), FB_ALLOC_NO_HINT); + threshim->width = w; + threshim->height = h; + threshim->stride = s; + threshim->buf = fb_alloc(w * h, FB_ALLOC_NO_HINT); + assert(threshim->stride == s); + + // The idea is to find the maximum and minimum values in a + // window around each pixel. If it's a contrast-free region + // (max-min is small), don't try to binarize. Otherwise, + // threshold according to (max+min)/2. + // + // Mark low-contrast regions with value 127 so that we can skip + // future work on these areas too. + + // however, computing max/min around every pixel is needlessly + // expensive. We compute max/min for tiles. To avoid artifacts + // that arise when high-contrast features appear near a tile + // edge (and thus moving from one tile to another results in a + // large change in max/min value), the max/min values used for + // any pixel are computed from all 3x3 surrounding tiles. Thus, + // the max/min sampling area for nearby pixels overlap by at least + // one tile. + // + // The important thing is that the windows be large enough to + // capture edge transitions; the tag does not need to fit into + // a tile. + + // XXX Tunable. Generally, small tile sizes--- so long as they're + // large enough to span a single tag edge--- seem to be a winner. + const int tilesz = 4; + + // the last (possibly partial) tiles along each row and column will + // just use the min/max value from the last full tile. + int tw = w / tilesz; + int th = h / tilesz; + + uint8_t *im_max = fb_alloc(tw*th*sizeof(uint8_t), FB_ALLOC_NO_HINT); + uint8_t *im_min = fb_alloc(tw*th*sizeof(uint8_t), FB_ALLOC_NO_HINT); + + // first, collect min/max statistics for each tile + for (int ty = 0; ty < th; ty++) { + for (int tx = 0; tx < tw; tx++) { +#if defined( OPTIMIZED ) && (defined(ARM_MATH_CM7) || defined(ARM_MATH_CM4)) + uint32_t tmp, max32 = 0, min32 = 0xffffffff; + for (int dy=0; dy < tilesz; dy++) { + uint32_t v = *(uint32_t *)&im->buf[(ty*tilesz+dy)*s + tx*tilesz]; + tmp = __USUB8(v, max32); + max32 = __SEL(v, max32); + tmp = __USUB8(min32, v); + min32 = __SEL(v, min32); + } + // find the min/max of the 4 remaining values + tmp = max32 >> 16; + __USUB8(max32, tmp); // 4->2 + max32 = __SEL(max32, tmp); + tmp = max32 >> 8; + __USUB8(max32, tmp); // 2->1 + max32 = __SEL(max32, tmp); + tmp = min32 >> 16; + __USUB8(min32, tmp); + min32 = __SEL(tmp, min32); // 4-->2 + tmp = min32 >> 8; + __USUB8(min32, tmp); + min32 = __SEL(tmp, min32); // 2-->1 + im_max[ty*tw+tx] = (uint8_t)max32; + im_min[ty*tw+tx] = (uint8_t)min32; +#else + uint8_t max = 0, min = 255; + for (int dy = 0; dy < tilesz; dy++) { + for (int dx = 0; dx < tilesz; dx++) { + uint8_t v = im->buf[(ty*tilesz+dy)*s + tx*tilesz + dx]; + if (v < min) + min = v; + if (v > max) + max = v; + } + } + im_max[ty*tw+tx] = max; + im_min[ty*tw+tx] = min; +#endif + } + } + + // second, apply 3x3 max/min convolution to "blur" these values + // over larger areas. This reduces artifacts due to abrupt changes + // in the threshold value. + if (1) { + uint8_t *im_max_tmp = fb_alloc(tw*th*sizeof(uint8_t), FB_ALLOC_NO_HINT); + uint8_t *im_min_tmp = fb_alloc(tw*th*sizeof(uint8_t), FB_ALLOC_NO_HINT); + +#ifdef OPTIMIZED + // Checking boundaries on every pixel wastes significant time; just break it into 5 pieces + // (center, top, bottom, left right) + // First pass does the entire center area + int ty, tx, dy, dx; + for (ty = 1; ty < th-1; ty++) { + for (tx = 1; tx < tw-1; tx++) { + uint8_t max = 0, min = 255; + for (dy = -1; dy <= 1; dy++) { + for (dx = -1; dx <= 1; dx++) { + uint8_t m = im_max[(ty+dy)*tw+tx+dx]; + if (m > max) + max = m; + m = im_min[(ty+dy)*tw+tx+dx]; + if (m < min) + min = m; + } + } + im_max_tmp[ty*tw + tx] = max; + im_min_tmp[ty*tw + tx] = min; + } + } + // top edge + ty = 0; + for (tx = 1; tx < tw-1; tx++) { + uint8_t max = 0, min = 255; + for (dy = 0; dy <= 1; dy++) { + for (dx = -1; dx <= 1; dx++) { + uint8_t m = im_max[(ty+dy)*tw+tx+dx]; + if (m > max) + max = m; + m = im_min[(ty+dy)*tw+tx+dx]; + if (m < min) + min = m; + } + } + im_max_tmp[ty*tw + tx] = max; + im_min_tmp[ty*tw + tx] = min; + } + // bottom edge + ty = th-1; + for (tx = 1; tx < tw-1; tx++) { + uint8_t max = 0, min = 255; + for (dy = -1; dy <= 0; dy++) { + for (dx = -1; dx <= 1; dx++) { + uint8_t m = im_max[(ty+dy)*tw+tx+dx]; + if (m > max) + max = m; + m = im_min[(ty+dy)*tw+tx+dx]; + if (m < min) + min = m; + } + } + im_max_tmp[ty*tw + tx] = max; + im_min_tmp[ty*tw + tx] = min; + } + // left edge + tx = 0; + for (ty = 1; ty < th-1; ty++) { + uint8_t max = 0, min = 255; + for (dy = -1; dy <= 1; dy++) { + for (dx = 0; dx <= 1; dx++) { + uint8_t m = im_max[(ty+dy)*tw+tx+dx]; + if (m > max) + max = m; + m = im_min[(ty+dy)*tw+tx+dx]; + if (m < min) + min = m; + } + } + im_max_tmp[ty*tw + tx] = max; + im_min_tmp[ty*tw + tx] = min; + } + // right edge + tx = tw-1; + for (ty = 1; ty < th-1; ty++) { + uint8_t max = 0, min = 255; + for (dy = -1; dy <= 1; dy++) { + for (dx = -1; dx <= 0; dx++) { + uint8_t m = im_max[(ty+dy)*tw+tx+dx]; + if (m > max) + max = m; + m = im_min[(ty+dy)*tw+tx+dx]; + if (m < min) + min = m; + } + } + im_max_tmp[ty*tw + tx] = max; + im_min_tmp[ty*tw + tx] = min; + } +#else + for (int ty = 0; ty < th; ty++) { + for (int tx = 0; tx < tw; tx++) { + uint8_t max = 0, min = 255; + + for (int dy = -1; dy <= 1; dy++) { + if (ty+dy < 0 || ty+dy >= th) + continue; + for (int dx = -1; dx <= 1; dx++) { + if (tx+dx < 0 || tx+dx >= tw) + continue; + + uint8_t m = im_max[(ty+dy)*tw+tx+dx]; + if (m > max) + max = m; + m = im_min[(ty+dy)*tw+tx+dx]; + if (m < min) + min = m; + } + } + + im_max_tmp[ty*tw + tx] = max; + im_min_tmp[ty*tw + tx] = min; + } + } +#endif + memcpy(im_max, im_max_tmp, tw*th*sizeof(uint8_t)); + memcpy(im_min, im_min_tmp, tw*th*sizeof(uint8_t)); + fb_free(im_min_tmp); // im_min_tmp + fb_free(im_max_tmp); // im_max_tmp + } +#if defined( OPTIMIZED ) && (defined(ARM_MATH_CM7) || defined(ARM_MATH_CM4)) + if ((s & 0x3) == 0 && tilesz == 4) // if each line is a multiple of 4, we can do this faster + { + const uint32_t lowcontrast = 0x7f7f7f7f; + const int s32 = s/4; // pitch for 32-bit values + const int minmax = td->qtp.min_white_black_diff; // local var to avoid constant dereferencing of the pointer + for (int ty = 0; ty < th; ty++) { + for (int tx = 0; tx < tw; tx++) { + + int min = im_min[ty*tw + tx]; + int max = im_max[ty*tw + tx]; + + // low contrast region? (no edges) + if (max - min < minmax) { + uint32_t *d32 = (uint32_t *)&threshim->buf[ty*tilesz*s + tx*tilesz]; + d32[0] = d32[s32] = d32[s32*2] = d32[s32*3] = lowcontrast; + continue; + } // if low contrast + // otherwise, actually threshold this tile. + + // argument for biasing towards dark; specular highlights + // can be substantially brighter than white tag parts + uint32_t thresh32 = (min + (max - min) / 2) + 1; // plus 1 to make GT become GE for the __USUB8 and __SEL instructions + uint32_t u32tmp; + thresh32 *= 0x01010101; // spread value to all 4 slots + for (int dy = 0; dy < tilesz; dy++) { + uint32_t *d32 = (uint32_t *)&threshim->buf[(ty*tilesz+dy)*s + tx*tilesz]; + uint32_t *s32 = (uint32_t *)&im->buf[(ty*tilesz+dy)*s + tx*tilesz]; + // process 4 pixels at a time + u32tmp = s32[0]; + u32tmp = __USUB8(u32tmp, thresh32); + u32tmp = __SEL(0xffffffff, 0x00000000); // 4 thresholded pixels + d32[0] = u32tmp; + } // dy + } // tx + } // ty + } + else // need to do it the slow way +#endif // OPTIMIZED + { + for (int ty = 0; ty < th; ty++) { + for (int tx = 0; tx < tw; tx++) { + + int min = im_min[ty*tw + tx]; + int max = im_max[ty*tw + tx]; + + // low contrast region? (no edges) + if (max - min < td->qtp.min_white_black_diff) { + for (int dy = 0; dy < tilesz; dy++) { + int y = ty*tilesz + dy; + + for (int dx = 0; dx < tilesz; dx++) { + int x = tx*tilesz + dx; + + threshim->buf[y*s+x] = 127; + } + } + continue; + } + + // otherwise, actually threshold this tile. + + // argument for biasing towards dark; specular highlights + // can be substantially brighter than white tag parts + uint8_t thresh = min + (max - min) / 2; + + for (int dy = 0; dy < tilesz; dy++) { + int y = ty*tilesz + dy; + + for (int dx = 0; dx < tilesz; dx++) { + int x = tx*tilesz + dx; + + uint8_t v = im->buf[y*s+x]; + if (v > thresh) + threshim->buf[y*s+x] = 255; + else + threshim->buf[y*s+x] = 0; + } + } + } + } + } + + // we skipped over the non-full-sized tiles above. Fix those now. + if (1) { + for (int y = 0; y < h; y++) { + + // what is the first x coordinate we need to process in this row? + + int x0; + + if (y >= th*tilesz) { + x0 = 0; // we're at the bottom; do the whole row. + } else { + x0 = tw*tilesz; // we only need to do the right most part. + } + + // compute tile coordinates and clamp. + int ty = y / tilesz; + if (ty >= th) + ty = th - 1; + + for (int x = x0; x < w; x++) { + int tx = x / tilesz; + if (tx >= tw) + tx = tw - 1; + + int max = im_max[ty*tw + tx]; + int min = im_min[ty*tw + tx]; + int thresh = min + (max - min) / 2; + + uint8_t v = im->buf[y*s+x]; + if (v > thresh) + threshim->buf[y*s+x] = 255; + else + threshim->buf[y*s+x] = 0; + } + } + } + + fb_free(im_min); // im_min + fb_free(im_max); // im_max + + // this is a dilate/erode deglitching scheme that does not improve + // anything as far as I can tell. + if (0 || td->qtp.deglitch) { + image_u8_t *tmp = fb_alloc(sizeof(image_u8_t), FB_ALLOC_NO_HINT); + tmp->width = w; + tmp->height = h; + tmp->stride = s; + tmp->buf = fb_alloc(w * h, FB_ALLOC_NO_HINT); + + for (int y = 1; y + 1 < h; y++) { + for (int x = 1; x + 1 < w; x++) { + uint8_t max = 0; + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + uint8_t v = threshim->buf[(y+dy)*s + x + dx]; + if (v > max) + max = v; + } + } + tmp->buf[y*s+x] = max; + } + } + + for (int y = 1; y + 1 < h; y++) { + for (int x = 1; x + 1 < w; x++) { + uint8_t min = 255; + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + uint8_t v = tmp->buf[(y+dy)*s + x + dx]; + if (v < min) + min = v; + } + } + threshim->buf[y*s+x] = min; + } + } + + fb_free(tmp->buf); // tmp->buf + fb_free(tmp); // tmp + } + + return threshim; +} + +zarray_t *apriltag_quad_thresh(apriltag_detector_t *td, image_u8_t *im, bool overrideMode) +{ + //////////////////////////////////////////////////////// + // step 1. threshold the image, creating the edge image. + + int w = im->width, h = im->height; + + image_u8_t *threshim = threshold(td, im); + int ts = threshim->stride; + + //////////////////////////////////////////////////////// + // step 2. find connected components. + + unionfind_t *uf = unionfind_create(w * h); + + for (int y = 0; y < h - 1; y++) { + do_unionfind_line(uf, threshim, h, w, ts, y); + } + + uint32_t nclustermap; + struct uint32_zarray_entry **clustermap = fb_alloc0_all(&nclustermap, FB_ALLOC_PREFER_SPEED); + nclustermap /= sizeof(struct uint32_zarray_entry*); + if (!nclustermap) fb_alloc_fail(); + + for (int y = 1; y < h-1; y++) { + for (int x = 1; x < w-1; x++) { + + uint8_t v0 = threshim->buf[y*ts + x]; + if (v0 == 127) + continue; + + // XXX don't query this until we know we need it? + uint32_t rep0 = unionfind_get_representative(uf, y*w + x); + + // whenever we find two adjacent pixels such that one is + // white and the other black, we add the point half-way + // between them to a cluster associated with the unique + // ids of the white and black regions. + // + // We additionally compute the gradient direction (i.e., which + // direction was the white pixel?) Note: if (v1-v0) == 255, then + // (dx,dy) points towards the white pixel. if (v1-v0) == -255, then + // (dx,dy) points towards the black pixel. p.gx and p.gy will thus + // be -255, 0, or 255. + // + // Note that any given pixel might be added to multiple + // different clusters. But in the common case, a given + // pixel will be added multiple times to the same cluster, + // which increases the size of the cluster and thus the + // computational costs. + // + // A possible optimization would be to combine entries + // within the same cluster. + +#define DO_CONN(dx, dy) \ + if (1) { \ + uint8_t v1 = threshim->buf[y*ts + dy*ts + x + dx]; \ + \ + while (v0 + v1 == 255) { \ + uint32_t rep1 = unionfind_get_representative(uf, y*w + dy*w + x + dx); \ + uint32_t clusterid; \ + if (rep0 < rep1) \ + clusterid = (rep1 << 16) + rep0; \ + else \ + clusterid = (rep0 << 16) + rep1; \ + \ + /* XXX lousy hash function */ \ + uint32_t clustermap_bucket = u64hash_2(clusterid) % nclustermap; \ + struct uint32_zarray_entry *entry = clustermap[clustermap_bucket]; \ + while (entry && entry->id != clusterid) { \ + entry = entry->next; \ + } \ + \ + if (!entry) { \ + entry = umm_calloc(1, sizeof(struct uint32_zarray_entry)); \ + if (!entry) break; \ + entry->id = clusterid; \ + entry->cluster = zarray_create_fail_ok(sizeof(struct pt)); \ + if (!entry->cluster) { \ + free(entry); \ + break; \ + } \ + entry->next = clustermap[clustermap_bucket]; \ + clustermap[clustermap_bucket] = entry; \ + } \ + \ + struct pt p = { .x = 2*x + dx, .y = 2*y + dy, .gx = dx*((int) v1-v0), .gy = dy*((int) v1-v0)}; \ + zarray_add_fail_ok(entry->cluster, &p); \ + break; \ + } \ + } + + // do 4 connectivity. NB: Arguments must be [-1, 1] or we'll overflow .gx, .gy + DO_CONN(1, 0); + DO_CONN(0, 1); + +#ifdef IMLIB_ENABLE_FINE_APRILTAGS + // do 8 connectivity + DO_CONN(-1, 1); + DO_CONN(1, 1); +#endif + } + } +#undef DO_CONN + + //////////////////////////////////////////////////////// + // step 3. process each connected component. + zarray_t *clusters = zarray_create_fail_ok(sizeof(zarray_t*)); //, uint32_zarray_hash_size(clustermap)); + if (clusters) { + for (int i = 0; i < nclustermap; i++) { + + for (struct uint32_zarray_entry *entry = clustermap[i]; entry; entry = entry->next) { + // XXX reject clusters here? + zarray_add_fail_ok(clusters, &entry->cluster); + } + } + } + + + int sz = clusters ? zarray_size(clusters) : 0; + + if (1) { + for (int i = 0; i < nclustermap; i++) { + struct uint32_zarray_entry *entry = clustermap[i]; + while (entry) { + // free any leaked cluster (zarray_add_fail_ok) + bool leaked = true; + for (int j = 0; j < sz && leaked; j++) { + zarray_t *cluster; + zarray_get(clusters, j, &cluster); + leaked &= entry->cluster != cluster; + } + if (leaked) free(entry->cluster); + struct uint32_zarray_entry *tmp = entry->next; + free(entry); + entry = tmp; + } + } + fb_free(clustermap); // clustermap + } + + unionfind_destroy(uf); + + fb_free(threshim->buf); // threshim->buf + fb_free(threshim); // threshim + + zarray_t *quads = zarray_create_fail_ok(sizeof(struct quad)); + + if (quads) { + for (int i = 0; i < sz; i++) { + + zarray_t *cluster; + zarray_get(clusters, i, &cluster); + + if (zarray_size(cluster) < td->qtp.min_cluster_pixels) + continue; + + // a cluster should contain only boundary points around the + // tag. it cannot be bigger than the whole screen. (Reject + // large connected blobs that will be prohibitively slow to + // fit quads to.) A typical point along an edge is added three + // times (because it has 3 neighbors). The maximum perimeter + // is 2w+2h. + if (zarray_size(cluster) > 3*(2*w+2*h)) { + continue; + } + + struct quad quad; + memset(&quad, 0, sizeof(struct quad)); + + if (fit_quad(td, im, cluster, &quad, overrideMode)) { + + zarray_add_fail_ok(quads, &quad); + } + } + } + + // printf(" %d %d %d %d\n", indices[0], indices[1], indices[2], indices[3]); + + for (int i = 0; i < sz; i++) { + zarray_t *cluster; + zarray_get(clusters, i, &cluster); + zarray_destroy(cluster); + } + + if (clusters) zarray_destroy(clusters); + + + if (!quads) { + // we should have enough memory now + quads = zarray_create(sizeof(struct quad)); + } + return quads; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "apriltag.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef M_PI +# define M_PI 3.141592653589793238462643383279502884196 +#endif + +// Regresses a model of the form: +// intensity(x,y) = C0*x + C1*y + CC2 +// The J matrix is the: +// J = [ x1 y1 1 ] +// [ x2 y2 1 ] +// [ ... ] +// The A matrix is J'J + +struct graymodel +{ + float A[3][3]; + float B[3]; + float C[3]; +}; + +void graymodel_init(struct graymodel *gm) +{ + memset(gm, 0, sizeof(struct graymodel)); +} + +void graymodel_add(struct graymodel *gm, float x, float y, float gray) +{ + // update upper right entries of A = J'J + gm->A[0][0] += x*x; + gm->A[0][1] += x*y; + gm->A[0][2] += x; + gm->A[1][1] += y*y; + gm->A[1][2] += y; + gm->A[2][2] += 1; + + // update B = J'gray + gm->B[0] += x * gray; + gm->B[1] += y * gray; + gm->B[2] += gray; +} + +void graymodel_solve(struct graymodel *gm) +{ + mat33_sym_solve((float*) gm->A, gm->B, gm->C); +} + +float graymodel_interpolate(struct graymodel *gm, float x, float y) +{ + return gm->C[0]*x + gm->C[1]*y + gm->C[2]; +} + +struct quick_decode_entry +{ + uint64_t rcode; // the queried code + uint16_t id; // the tag ID (a small integer) + uint8_t hamming; // how many errors corrected? + uint8_t rotation; // number of rotations [0, 3] + bool hmirror; + bool vflip; +}; + +struct quick_decode +{ + int nentries; + struct quick_decode_entry *entries; +}; + +/** if the bits in w were arranged in a d*d grid and that grid was + * rotated, what would the new bits in w be? + * The bits are organized like this (for d = 3): + * + * 8 7 6 2 5 8 0 1 2 + * 5 4 3 ==> 1 4 7 ==> 3 4 5 (rotate90 applied twice) + * 2 1 0 0 3 6 6 7 8 + **/ +static uint64_t rotate90(uint64_t w, uint32_t d) +{ + uint64_t wr = 0; + + for (int32_t r = d-1; r >=0; r--) { + for (int32_t c = 0; c < d; c++) { + int32_t b = r + d*c; + + wr = wr << 1; + + if ((w & (((uint64_t) 1) << b))!=0) + wr |= 1; + } + } + + return wr; +} + +static uint64_t hmirror_code(uint64_t w, uint32_t d) +{ + uint64_t wr = 0; + + for (int32_t r = d-1; r >=0; r--) { + for (int32_t c = 0; c < d; c++) { + int32_t b = c + d*r; + + wr = wr << 1; + + if ((w & (((uint64_t) 1) << b))!=0) + wr |= 1; + } + } + + return wr; +} + +static uint64_t vflip_code(uint64_t w, uint32_t d) +{ + uint64_t wr = 0; + + for (int32_t r = 0; r < d; r++) { + for (int32_t c = d-1; c >=0; c--) { + int32_t b = c + d*r; + + wr = wr << 1; + + if ((w & (((uint64_t) 1) << b))!=0) + wr |= 1; + } + } + + return wr; +} + +void quad_destroy(struct quad *quad) +{ + if (!quad) + return; + + matd_destroy(quad->H); + matd_destroy(quad->Hinv); + free(quad); +} + +struct quad *quad_copy(struct quad *quad) +{ + struct quad *q = calloc(1, sizeof(struct quad)); + memcpy(q, quad, sizeof(struct quad)); + if (quad->H) + q->H = matd_copy(quad->H); + if (quad->Hinv) + q->Hinv = matd_copy(quad->Hinv); + return q; +} + +// http://en.wikipedia.org/wiki/Hamming_weight + +//types and constants used in the functions below +//uint64_t is an unsigned 64-bit integer variable type (defined in C99 version of C language) +const uint64_t m1 = 0x5555555555555555; //binary: 0101... +const uint64_t m2 = 0x3333333333333333; //binary: 00110011.. +const uint64_t m4 = 0x0f0f0f0f0f0f0f0f; //binary: 4 zeros, 4 ones ... +const uint64_t m8 = 0x00ff00ff00ff00ff; //binary: 8 zeros, 8 ones ... +const uint64_t m16 = 0x0000ffff0000ffff; //binary: 16 zeros, 16 ones ... +const uint64_t m32 = 0x00000000ffffffff; //binary: 32 zeros, 32 ones +const uint64_t hff = 0xffffffffffffffff; //binary: all ones +const uint64_t h01 = 0x0101010101010101; //the sum of 256 to the power of 0,1,2,3... + +//This is a naive implementation, shown for comparison, +//and to help in understanding the better functions. +//This algorithm uses 24 arithmetic operations (shift, add, and). +int popcount64a(uint64_t x) +{ + x = (x & m1 ) + ((x >> 1) & m1 ); //put count of each 2 bits into those 2 bits + x = (x & m2 ) + ((x >> 2) & m2 ); //put count of each 4 bits into those 4 bits + x = (x & m4 ) + ((x >> 4) & m4 ); //put count of each 8 bits into those 8 bits + x = (x & m8 ) + ((x >> 8) & m8 ); //put count of each 16 bits into those 16 bits + x = (x & m16) + ((x >> 16) & m16); //put count of each 32 bits into those 32 bits + x = (x & m32) + ((x >> 32) & m32); //put count of each 64 bits into those 64 bits + return x; +} + +//This uses fewer arithmetic operations than any other known +//implementation on machines with slow multiplication. +//This algorithm uses 17 arithmetic operations. +int popcount64b(uint64_t x) +{ + x -= (x >> 1) & m1; //put count of each 2 bits into those 2 bits + x = (x & m2) + ((x >> 2) & m2); //put count of each 4 bits into those 4 bits + x = (x + (x >> 4)) & m4; //put count of each 8 bits into those 8 bits + x += x >> 8; //put count of each 16 bits into their lowest 8 bits + x += x >> 16; //put count of each 32 bits into their lowest 8 bits + x += x >> 32; //put count of each 64 bits into their lowest 8 bits + return x & 0x7f; +} + +//This uses fewer arithmetic operations than any other known +//implementation on machines with fast multiplication. +//This algorithm uses 12 arithmetic operations, one of which is a multiply. +int popcount64c(uint64_t x) +{ + x -= (x >> 1) & m1; //put count of each 2 bits into those 2 bits + x = (x & m2) + ((x >> 2) & m2); //put count of each 4 bits into those 4 bits + x = (x + (x >> 4)) & m4; //put count of each 8 bits into those 8 bits + return (x * h01) >> 56; //returns left 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ... +} + +// returns an entry with hamming set to 255 if no decode was found. +static void quick_decode_codeword(apriltag_family_t *tf, uint64_t rcode, + struct quick_decode_entry *entry) +{ + int threshold = imax(tf->h - tf->d - 1, 0); + + for (int ridx = 0; ridx < 4; ridx++) { + + for (int i = 0, j = tf->ncodes; i < j; i++) { + int hamming = popcount64c(tf->codes[i] ^ rcode); + if(hamming <= threshold) { + entry->rcode = rcode; + entry->id = i; + entry->hamming = hamming; + entry->rotation = ridx; + entry->hmirror = false; + entry->vflip = false; + return; + } + } + + rcode = rotate90(rcode, tf->d); + } + + rcode = hmirror_code(rcode, tf->d); // handle hmirror + + for (int ridx = 0; ridx < 4; ridx++) { + + for (int i = 0, j = tf->ncodes; i < j; i++) { + int hamming = popcount64c(tf->codes[i] ^ rcode); + if(hamming <= threshold) { + entry->rcode = rcode; + entry->id = i; + entry->hamming = hamming; + entry->rotation = ridx; + entry->hmirror = true; + entry->vflip = false; + return; + } + } + + rcode = rotate90(rcode, tf->d); + } + + rcode = vflip_code(rcode, tf->d); // handle hmirror+vflip + + for (int ridx = 0; ridx < 4; ridx++) { + + for (int i = 0, j = tf->ncodes; i < j; i++) { + int hamming = popcount64c(tf->codes[i] ^ rcode); + if(hamming <= threshold) { + entry->rcode = rcode; + entry->id = i; + entry->hamming = hamming; + entry->rotation = ridx; + entry->hmirror = true; + entry->vflip = true; + return; + } + } + + rcode = rotate90(rcode, tf->d); + } + + rcode = hmirror_code(rcode, tf->d); // handle vflip + + for (int ridx = 0; ridx < 4; ridx++) { + + for (int i = 0, j = tf->ncodes; i < j; i++) { + int hamming = popcount64c(tf->codes[i] ^ rcode); + if(hamming <= threshold) { + entry->rcode = rcode; + entry->id = i; + entry->hamming = hamming; + entry->rotation = ridx; + entry->hmirror = false; + entry->vflip = true; + return; + } + } + + rcode = rotate90(rcode, tf->d); + } + + entry->rcode = 0; + entry->id = 65535; + entry->hamming = 255; + entry->rotation = 0; + entry->hmirror = false; + entry->vflip = false; +} + +static inline int detection_compare_function(const void *_a, const void *_b) +{ + apriltag_detection_t *a = *(apriltag_detection_t**) _a; + apriltag_detection_t *b = *(apriltag_detection_t**) _b; + + return a->id - b->id; +} + +void apriltag_detector_remove_family(apriltag_detector_t *td, apriltag_family_t *fam) +{ + zarray_remove_value(td->tag_families, &fam, 0); +} + +void apriltag_detector_add_family_bits(apriltag_detector_t *td, apriltag_family_t *fam, int bits_corrected) +{ + zarray_add(td->tag_families, &fam); +} + +void apriltag_detector_clear_families(apriltag_detector_t *td) +{ + zarray_clear(td->tag_families); +} + +apriltag_detector_t *apriltag_detector_create() +{ + apriltag_detector_t *td = (apriltag_detector_t*) calloc(1, sizeof(apriltag_detector_t)); + + td->qtp.max_nmaxima = 10; + td->qtp.min_cluster_pixels = 5; + + td->qtp.max_line_fit_mse = 10.0; + td->qtp.critical_rad = 10 * M_PI / 180; + td->qtp.deglitch = 0; + td->qtp.min_white_black_diff = 5; + + td->tag_families = zarray_create(sizeof(apriltag_family_t*)); + + td->refine_edges = 1; + td->refine_pose = 0; + td->refine_decode = 0; + + return td; +} + +void apriltag_detector_destroy(apriltag_detector_t *td) +{ + apriltag_detector_clear_families(td); + + zarray_destroy(td->tag_families); + free(td); +} + +struct evaluate_quad_ret +{ + int64_t rcode; + float score; + matd_t *H, *Hinv; + + int decode_status; + struct quick_decode_entry e; +}; + +// returns non-zero if an error occurs (i.e., H has no inverse) +int quad_update_homographies(struct quad *quad) +{ + zarray_t *correspondences = zarray_create(sizeof(float[4])); + + for (int i = 0; i < 4; i++) { + float corr[4]; + + // At this stage of the pipeline, we have not attempted to decode the + // quad into an oriented tag. Thus, just act as if the quad is facing + // "up" with respect to our desired corners. We'll fix the rotation + // later. + // [-1, -1], [1, -1], [1, 1], [-1, 1] + corr[0] = (i==0 || i==3) ? -1 : 1; + corr[1] = (i==0 || i==1) ? -1 : 1; + + corr[2] = quad->p[i][0]; + corr[3] = quad->p[i][1]; + + zarray_add(correspondences, &corr); + } + + if (quad->H) + matd_destroy(quad->H); + if (quad->Hinv) + matd_destroy(quad->Hinv); + + // XXX Tunable + quad->H = homography_compute(correspondences, HOMOGRAPHY_COMPUTE_FLAG_SVD); + quad->Hinv = matd_inverse(quad->H); + zarray_destroy(correspondences); + + if (quad->H && quad->Hinv) + return 0; + + return -1; +} + +// compute a "score" for a quad that is independent of tag family +// encoding (but dependent upon the tag geometry) by considering the +// contrast around the exterior of the tag. +float quad_goodness(apriltag_family_t *family, image_u8_t *im, struct quad *quad) +{ + // when sampling from the white border, how much white border do + // we actually consider valid, measured in bit-cell units? (the + // outside portions are often intruded upon, so it could be advantageous to use + // less than the "nominal" 1.0. (Less than 1.0 not well tested.) + + // XXX Tunable + float white_border = 1; + + // in tag coordinates, how big is each bit cell? + float bit_size = 2.0 / (2*family->black_border + family->d); +// float inv_bit_size = 1.0 / bit_size; + + int32_t xmin = INT32_MAX, xmax = 0, ymin = INT32_MAX, ymax = 0; + + for (int i = 0; i < 4; i++) { + float tx = (i == 0 || i == 3) ? -1 - bit_size : 1 + bit_size; + float ty = (i == 0 || i == 1) ? -1 - bit_size : 1 + bit_size; + float x, y; + + homography_project(quad->H, tx, ty, &x, &y); + xmin = imin(xmin, x); + xmax = imax(xmax, x); + ymin = imin(ymin, y); + ymax = imax(ymax, y); + } + + // clamp bounding box to image dimensions + xmin = imax(0, xmin); + xmax = imin(im->width-1, xmax); + ymin = imax(0, ymin); + ymax = imin(im->height-1, ymax); + +// int nbits = family->d * family->d; + + int32_t W1 = 0, B1 = 0, Wn = 0, Bn = 0; // int64_t W1 = 0, B1 = 0, Wn = 0, Bn = 0; + + float wsz = bit_size*white_border; + float bsz = bit_size*family->black_border; + + matd_t *Hinv = quad->Hinv; +// matd_t *H = quad->H; + + // iterate over all the pixels in the tag. (Iterating in pixel space) + for (int y = ymin; y <= ymax; y++) { + + // we'll incrementally compute the homography + // projections. Begin by evaluating the homogeneous position + // [(xmin - .5f), y, 1]. Then, we'll update as we stride in + // the +x direction. + float Hx = MATD_EL(Hinv, 0, 0) * (.5 + (int) xmin) + + MATD_EL(Hinv, 0, 1) * (y + .5) + MATD_EL(Hinv, 0, 2); + float Hy = MATD_EL(Hinv, 1, 0) * (.5 + (int) xmin) + + MATD_EL(Hinv, 1, 1) * (y + .5) + MATD_EL(Hinv, 1, 2); + float Hh = MATD_EL(Hinv, 2, 0) * (.5 + (int) xmin) + + MATD_EL(Hinv, 2, 1) * (y + .5) + MATD_EL(Hinv, 2, 2); + + for (int x = xmin; x <= xmax; x++) { + // project the pixel center. + float tx, ty; + + // divide by homogeneous coordinate + tx = Hx / Hh; + ty = Hy / Hh; + + // if we move x one pixel to the right, here's what + // happens to our three pre-normalized coordinates. + Hx += MATD_EL(Hinv, 0, 0); + Hy += MATD_EL(Hinv, 1, 0); + Hh += MATD_EL(Hinv, 2, 0); + + float txa = fabsf((float) tx), tya = fabsf((float) ty); + float xymax = fmaxf(txa, tya); + +// if (txa >= 1 + wsz || tya >= 1 + wsz) + if (xymax >= 1 + wsz) + continue; + + uint8_t v = im->buf[y*im->stride + x]; + + // it's within the white border? +// if (txa >= 1 || tya >= 1) { + if (xymax >= 1) { + W1 += v; + Wn ++; + continue; + } + + // it's within the black border? +// if (txa >= 1 - bsz || tya >= 1 - bsz) { + if (xymax >= 1 - bsz) { + B1 += v; + Bn ++; + continue; + } + + // it must be a data bit. We don't do anything with these. + continue; + } + } + + + // score = average margin between white and black pixels near border. + float margin = 1.0 * W1 / Wn - 1.0 * B1 / Bn; +// printf("margin %f: W1 %f, B1 %f\n", margin, W1, B1); + + return margin; +} + +// returns the decision margin. Return < 0 if the detection should be rejected. +float quad_decode(apriltag_family_t *family, image_u8_t *im, struct quad *quad, struct quick_decode_entry *entry, image_u8_t *im_samples) +{ + // decode the tag binary contents by sampling the pixel + // closest to the center of each bit cell. + + int64_t rcode = 0; + + // how wide do we assume the white border is? + float white_border = 1.0; + + // We will compute a threshold by sampling known white/black cells around this tag. + // This sampling is achieved by considering a set of samples along lines. + // + // coordinates are given in bit coordinates. ([0, fam->d]). + // + // { initial x, initial y, delta x, delta y, WHITE=1 } + float patterns[] = { + // left white column + 0 - white_border / 2.0, 0.5, + 0, 1, + 1, + + // left black column + 0 + family->black_border / 2.0, 0.5, + 0, 1, + 0, + + // right white column + 2*family->black_border + family->d + white_border / 2.0, .5, + 0, 1, + 1, + + // right black column + 2*family->black_border + family->d - family->black_border / 2.0, .5, + 0, 1, + 0, + + // top white row + 0.5, -white_border / 2.0, + 1, 0, + 1, + + // top black row + 0.5, family->black_border / 2.0, + 1, 0, + 0, + + // bottom white row + 0.5, 2*family->black_border + family->d + white_border / 2.0, + 1, 0, + 1, + + // bottom black row + 0.5, 2*family->black_border + family->d - family->black_border / 2.0, + 1, 0, + 0 + + // XXX float-counts the corners. + }; + + struct graymodel whitemodel, blackmodel; + graymodel_init(&whitemodel); + graymodel_init(&blackmodel); + + for (int pattern_idx = 0; pattern_idx < sizeof(patterns)/(5*sizeof(float)); pattern_idx ++) { + float *pattern = &patterns[pattern_idx * 5]; + + int is_white = pattern[4]; + + for (int i = 0; i < 2*family->black_border + family->d; i++) { + float tagx01 = (pattern[0] + i*pattern[2]) / (2*family->black_border + family->d); + float tagy01 = (pattern[1] + i*pattern[3]) / (2*family->black_border + family->d); + + float tagx = 2*(tagx01-0.5); + float tagy = 2*(tagy01-0.5); + + float px, py; + homography_project(quad->H, tagx, tagy, &px, &py); + + // don't round + int ix = px; + int iy = py; + if (ix < 0 || iy < 0 || ix >= im->width || iy >= im->height) + continue; + + int v = im->buf[iy*im->stride + ix]; + + if (im_samples) { + im_samples->buf[iy*im_samples->stride + ix] = (1-is_white)*255; + } + + if (is_white) + graymodel_add(&whitemodel, tagx, tagy, v); + else + graymodel_add(&blackmodel, tagx, tagy, v); + } + } + + graymodel_solve(&whitemodel); + graymodel_solve(&blackmodel); + + // XXX Tunable + if (graymodel_interpolate(&whitemodel, 0, 0) - graymodel_interpolate(&blackmodel, 0, 0) < 0) + return -1; + + // compute the average decision margin (how far was each bit from + // the decision boundary? + // + // we score this separately for white and black pixels and return + // the minimum average threshold for black/white pixels. This is + // to penalize thresholds that are too close to an extreme. + float black_score = 0, white_score = 0; + float black_score_count = 1, white_score_count = 1; + + for (int bitidx = 0; bitidx < family->d * family->d; bitidx++) { + int bitx = bitidx % family->d; + int bity = bitidx / family->d; + + float tagx01 = (family->black_border + bitx + 0.5) / (2*family->black_border + family->d); + float tagy01 = (family->black_border + bity + 0.5) / (2*family->black_border + family->d); + + // scale to [-1, 1] + float tagx = 2*(tagx01-0.5); + float tagy = 2*(tagy01-0.5); + + float px, py; + homography_project(quad->H, tagx, tagy, &px, &py); + + rcode = (rcode << 1); + + // don't round. + int ix = px; + int iy = py; + + if (ix < 0 || iy < 0 || ix >= im->width || iy >= im->height) + continue; + + int v = im->buf[iy*im->stride + ix]; + + float thresh = (graymodel_interpolate(&blackmodel, tagx, tagy) + graymodel_interpolate(&whitemodel, tagx, tagy)) / 2.0; + if (v > thresh) { + white_score += (v - thresh); + white_score_count ++; + rcode |= 1; + } else { + black_score += (thresh - v); + black_score_count ++; + } + + if (im_samples) + im_samples->buf[iy*im_samples->stride + ix] = (1 - (rcode & 1)) * 255; + } + + quick_decode_codeword(family, rcode, entry); + + return fmin(white_score / white_score_count, black_score / black_score_count); +} + +float score_goodness(apriltag_family_t *family, image_u8_t *im, struct quad *quad, void *user) +{ + return quad_goodness(family, im, quad); +} + +float score_decodability(apriltag_family_t *family, image_u8_t *im, struct quad *quad, void *user) +{ + struct quick_decode_entry entry; + + float decision_margin = quad_decode(family, im, quad, &entry, NULL); + + // hamming trumps decision margin; maximum value for decision_margin is 255. + return decision_margin - entry.hamming*1000; +} + +// returns score of best quad +float optimize_quad_generic(apriltag_family_t *family, image_u8_t *im, struct quad *quad0, + float *stepsizes, int nstepsizes, + float (*score)(apriltag_family_t *family, image_u8_t *im, struct quad *quad, void *user), + void *user) +{ + struct quad *best_quad = quad_copy(quad0); + float best_score = score(family, im, best_quad, user); + + for (int stepsize_idx = 0; stepsize_idx < nstepsizes; stepsize_idx++) { + + int improved = 1; + + // when we make progress with a particular step size, how many + // times will we try to perform that same step size again? + // (max_repeat = 0 means ("don't repeat--- just move to the + // next step size"). + // XXX Tunable + int max_repeat = 1; + + for (int repeat = 0; repeat <= max_repeat && improved; repeat++) { + + improved = 0; + + // wiggle point i + for (int i = 0; i < 4; i++) { + + float stepsize = stepsizes[stepsize_idx]; + + // XXX Tunable (really 1 makes the best sense since) + int nsteps = 1; + + struct quad *this_best_quad = NULL; + float this_best_score = best_score; + + for (int sx = -nsteps; sx <= nsteps; sx++) { + for (int sy = -nsteps; sy <= nsteps; sy++) { + if (sx==0 && sy==0) + continue; + + struct quad *this_quad = quad_copy(best_quad); + this_quad->p[i][0] = best_quad->p[i][0] + sx*stepsize; + this_quad->p[i][1] = best_quad->p[i][1] + sy*stepsize; + if (quad_update_homographies(this_quad)) + continue; + + float this_score = score(family, im, this_quad, user); + + if (this_score > this_best_score) { + quad_destroy(this_best_quad); + + this_best_quad = this_quad; + this_best_score = this_score; + } else { + quad_destroy(this_quad); + } + } + } + + if (this_best_score > best_score) { + quad_destroy(best_quad); + best_quad = this_best_quad; + best_score = this_best_score; + improved = 1; + } + } + } + } + + matd_destroy(quad0->H); + matd_destroy(quad0->Hinv); + memcpy(quad0, best_quad, sizeof(struct quad)); // copy pointers + free(best_quad); + return best_score; +} + +static void refine_edges(apriltag_detector_t *td, image_u8_t *im_orig, struct quad *quad) +{ + float lines[4][4]; // for each line, [Ex Ey nx ny] + + for (int edge = 0; edge < 4; edge++) { + int a = edge, b = (edge + 1) & 3; // indices of the end points. + + // compute the normal to the current line estimate + float nx = quad->p[b][1] - quad->p[a][1]; + float ny = -quad->p[b][0] + quad->p[a][0]; + float mag = sqrt(nx*nx + ny*ny); + nx /= mag; + ny /= mag; + + // we will now fit a NEW line by sampling points near + // our original line that have large gradients. On really big tags, + // we're willing to sample more to get an even better estimate. + int nsamples = imax(16, mag / 8); // XXX tunable + + // stats for fitting a line... + float Mx = 0, My = 0, Mxx = 0, Mxy = 0, Myy = 0, N = 0; + + for (int s = 0; s < nsamples; s++) { + // compute a point along the line... Note, we're avoiding + // sampling *right* at the corners, since those points are + // the least reliable. + float alpha = (1.0 + s) / (nsamples + 1); + float x0 = alpha*quad->p[a][0] + (1-alpha)*quad->p[b][0]; + float y0 = alpha*quad->p[a][1] + (1-alpha)*quad->p[b][1]; + + // search along the normal to this line, looking at the + // gradients along the way. We're looking for a strong + // response. + float Mn = 0; + float Mcount = 0; + + // XXX tunable: how far to search? We want to search far + // enough that we find the best edge, but not so far that + // we hit other edges that aren't part of the tag. We + // shouldn't ever have to search more than quad_decimate, + // since otherwise we would (ideally) have started our + // search on another pixel in the first place. Likewise, + // for very small tags, we don't want the range to be too + // big. + float range = 1.0 + 1; + + // XXX tunable step size. + for (float n = -range; n <= range; n += 0.25) { + // Because of the guaranteed winding order of the + // points in the quad, we will start inside the white + // portion of the quad and work our way outward. + // + // sample to points (x1,y1) and (x2,y2) XXX tunable: + // how far +/- to look? Small values compute the + // gradient more precisely, but are more sensitive to + // noise. + float grange = 1; + int x1 = x0 + (n + grange)*nx; + int y1 = y0 + (n + grange)*ny; + if (x1 < 0 || x1 >= im_orig->width || y1 < 0 || y1 >= im_orig->height) + continue; + + int x2 = x0 + (n - grange)*nx; + int y2 = y0 + (n - grange)*ny; + if (x2 < 0 || x2 >= im_orig->width || y2 < 0 || y2 >= im_orig->height) + continue; + + int g1 = im_orig->buf[y1*im_orig->stride + x1]; + int g2 = im_orig->buf[y2*im_orig->stride + x2]; + + if (g1 < g2) // reject points whose gradient is "backwards". They can only hurt us. + continue; + + float weight = (g2 - g1)*(g2 - g1); // XXX tunable. What shape for weight=f(g2-g1)? + + // compute weighted average of the gradient at this point. + Mn += weight*n; + Mcount += weight; + } + + // what was the average point along the line? + if (Mcount == 0) + continue; + + float n0 = Mn / Mcount; + + // where is the point along the line? + float bestx = x0 + n0*nx; + float besty = y0 + n0*ny; + + // update our line fit statistics + Mx += bestx; + My += besty; + Mxx += bestx*bestx; + Mxy += bestx*besty; + Myy += besty*besty; + N++; + } + + // fit a line + float Ex = Mx / N, Ey = My / N; + float Cxx = Mxx / N - Ex*Ex; + float Cxy = Mxy / N - Ex*Ey; + float Cyy = Myy / N - Ey*Ey; + + float normal_theta = .5 * atan2f(-2*Cxy, (Cyy - Cxx)); + nx = cosf(normal_theta); + ny = sinf(normal_theta); + lines[edge][0] = Ex; + lines[edge][1] = Ey; + lines[edge][2] = nx; + lines[edge][3] = ny; + } + + // now refit the corners of the quad + for (int i = 0; i < 4; i++) { + + // solve for the intersection of lines (i) and (i+1)&3. + float A00 = lines[i][3], A01 = -lines[(i+1)&3][3]; + float A10 = -lines[i][2], A11 = lines[(i+1)&3][2]; + float B0 = -lines[i][0] + lines[(i+1)&3][0]; + float B1 = -lines[i][1] + lines[(i+1)&3][1]; + + float det = A00 * A11 - A10 * A01; + + // inverse. + if (fabs(det) > 0.001) { + // solve + float W00 = A11 / det, W01 = -A01 / det; + + float L0 = W00*B0 + W01*B1; + + // compute intersection + quad->p[i][0] = lines[i][0] + L0*A00; + quad->p[i][1] = lines[i][1] + L0*A10; + } else { + // this is a bad sign. We'll just keep the corner we had. +// printf("bad det: %15f %15f %15f %15f %15f\n", A00, A11, A10, A01, det); + } + } +} + +void apriltag_detection_destroy(apriltag_detection_t *det) +{ + if (det == NULL) + return; + + matd_destroy(det->H); + free(det); +} + +int prefer_smaller(int pref, float q0, float q1) +{ + if (pref) // already prefer something? exit. + return pref; + + if (q0 < q1) + return -1; // we now prefer q0 + if (q1 < q0) + return 1; // we now prefer q1 + + // no preference + return 0; +} + +zarray_t *apriltag_detector_detect(apriltag_detector_t *td, image_u8_t *im_orig) +{ + if (zarray_size(td->tag_families) == 0) { + zarray_t *s = zarray_create(sizeof(apriltag_detection_t*)); + printf("apriltag.c: No tag families enabled."); + return s; + } + + /////////////////////////////////////////////////////////// + // Step 1. Detect quads according to requested image decimation + // and blurring parameters. + +// zarray_t *quads = apriltag_quad_gradient(td, im_orig); + zarray_t *quads = apriltag_quad_thresh(td, im_orig, false); + + zarray_t *detections = zarray_create(sizeof(apriltag_detection_t*)); + + td->nquads = zarray_size(quads); + + //////////////////////////////////////////////////////////////// + // Step 2. Decode tags from each quad. + if (1) { + for (int i = 0; i < zarray_size(quads); i++) { + struct quad *quad_original; + zarray_get_volatile(quads, i, &quad_original); + + // refine edges is not dependent upon the tag family, thus + // apply this optimization BEFORE the other work. + //if (td->quad_decimate > 1 && td->refine_edges) { + if (td->refine_edges) { + refine_edges(td, im_orig, quad_original); + } + + // make sure the homographies are computed... + if (quad_update_homographies(quad_original)) + continue; + + for (int famidx = 0; famidx < zarray_size(td->tag_families); famidx++) { + apriltag_family_t *family; + zarray_get(td->tag_families, famidx, &family); + + float goodness = 0; + + // since the geometry of tag families can vary, start any + // optimization process over with the original quad. + struct quad *quad = quad_copy(quad_original); + + // improve the quad corner positions by minimizing the + // variance within each intra-bit area. + if (td->refine_pose) { + // NB: We potentially step an integer + // number of times in each direction. To make each + // sample as useful as possible, the step sizes should + // not be integer multiples of each other. (I.e., + // probably don't use 1, 0.5, 0.25, etc.) + + // XXX Tunable + float stepsizes[] = { 1, .4, .16, .064 }; + int nstepsizes = sizeof(stepsizes)/sizeof(float); + + goodness = optimize_quad_generic(family, im_orig, quad, stepsizes, nstepsizes, score_goodness, NULL); + } + + if (td->refine_decode) { + // this optimizes decodability, but we don't report + // that value to the user. (so discard return value.) + // XXX Tunable + float stepsizes[] = { .4 }; + int nstepsizes = sizeof(stepsizes)/sizeof(float); + + optimize_quad_generic(family, im_orig, quad, stepsizes, nstepsizes, score_decodability, NULL); + } + + struct quick_decode_entry entry; + + float decision_margin = quad_decode(family, im_orig, quad, &entry, NULL); + + if (entry.hamming < 255 && decision_margin >= 0) { + apriltag_detection_t *det = calloc(1, sizeof(apriltag_detection_t)); + + det->family = family; + det->id = entry.id; + det->hamming = entry.hamming; + det->goodness = goodness; + det->decision_margin = decision_margin; + + float theta = -entry.rotation * M_PI / 2.0; + float c = cos(theta), s = sin(theta); + + // Fix the rotation of our homography to properly orient the tag + matd_t *R = matd_create(3,3); + MATD_EL(R, 0, 0) = c; + MATD_EL(R, 0, 1) = -s; + MATD_EL(R, 1, 0) = s; + MATD_EL(R, 1, 1) = c; + MATD_EL(R, 2, 2) = 1; + + matd_t *RHMirror = matd_create(3,3); + MATD_EL(RHMirror, 0, 0) = entry.hmirror ? -1 : 1; + MATD_EL(RHMirror, 1, 1) = 1; + MATD_EL(RHMirror, 2, 2) = entry.hmirror ? -1 : 1; + + matd_t *RVFlip = matd_create(3,3); + MATD_EL(RVFlip, 0, 0) = 1; + MATD_EL(RVFlip, 1, 1) = entry.vflip ? -1 : 1; + MATD_EL(RVFlip, 2, 2) = entry.vflip ? -1 : 1; + + det->H = matd_op("M*M*M*M", quad->H, R, RHMirror, RVFlip); + + matd_destroy(R); + matd_destroy(RHMirror); + matd_destroy(RVFlip); + + homography_project(det->H, 0, 0, &det->c[0], &det->c[1]); + + // [-1, -1], [1, -1], [1, 1], [-1, 1], Desired points + // [-1, 1], [1, 1], [1, -1], [-1, -1], FLIP Y + // adjust the points in det->p so that they correspond to + // counter-clockwise around the quad, starting at -1,-1. + for (int i = 0; i < 4; i++) { + int tcx = (i == 1 || i == 2) ? 1 : -1; + int tcy = (i < 2) ? 1 : -1; + + float p[2]; + + homography_project(det->H, tcx, tcy, &p[0], &p[1]); + + det->p[i][0] = p[0]; + det->p[i][1] = p[1]; + } + + zarray_add(detections, &det); + } + + quad_destroy(quad); + } + } + } + + //////////////////////////////////////////////////////////////// + // Step 3. Reconcile detections--- don't report the same tag more + // than once. (Allow non-overlapping duplicate detections.) + if (1) { + zarray_t *poly0 = g2d_polygon_create_zeros(4); + zarray_t *poly1 = g2d_polygon_create_zeros(4); + + for (int i0 = 0; i0 < zarray_size(detections); i0++) { + + apriltag_detection_t *det0; + zarray_get(detections, i0, &det0); + + for (int k = 0; k < 4; k++) + zarray_set(poly0, k, det0->p[k], NULL); + + for (int i1 = i0+1; i1 < zarray_size(detections); i1++) { + + apriltag_detection_t *det1; + zarray_get(detections, i1, &det1); + + if (det0->id != det1->id || det0->family != det1->family) + continue; + + for (int k = 0; k < 4; k++) + zarray_set(poly1, k, det1->p[k], NULL); + + if (g2d_polygon_overlaps_polygon(poly0, poly1)) { + // the tags overlap. Delete one, keep the other. + + int pref = 0; // 0 means undecided which one we'll keep. + pref = prefer_smaller(pref, det0->hamming, det1->hamming); // want small hamming + pref = prefer_smaller(pref, -det0->decision_margin, -det1->decision_margin); // want bigger margins + pref = prefer_smaller(pref, -det0->goodness, -det1->goodness); // want bigger goodness + + // if we STILL don't prefer one detection over the other, then pick + // any deterministic criterion. + for (int i = 0; i < 4; i++) { + pref = prefer_smaller(pref, det0->p[i][0], det1->p[i][0]); + pref = prefer_smaller(pref, det0->p[i][1], det1->p[i][1]); + } + + if (pref == 0) { + // at this point, we should only be undecided if the tag detections + // are *exactly* the same. How would that happen? + // printf("uh oh, no preference for overlappingdetection\n"); + } + + if (pref < 0) { + // keep det0, destroy det1 + apriltag_detection_destroy(det1); + zarray_remove_index(detections, i1, 1); + i1--; // retry the same index + goto retry1; + } else { + // keep det1, destroy det0 + apriltag_detection_destroy(det0); + zarray_remove_index(detections, i0, 1); + i0--; // retry the same index. + goto retry0; + } + } + + retry1: ; + } + + retry0: ; + } + + zarray_destroy(poly0); + zarray_destroy(poly1); + } + + for (int i = 0; i < zarray_size(quads); i++) { + struct quad *quad; + zarray_get_volatile(quads, i, &quad); + matd_destroy(quad->H); + matd_destroy(quad->Hinv); + } + + zarray_destroy(quads); + + zarray_sort(detections, detection_compare_function); + + return detections; +} + + +// Call this method on each of the tags returned by apriltag_detector_detect +void apriltag_detections_destroy(zarray_t *detections) +{ + for (int i = 0; i < zarray_size(detections); i++) { + apriltag_detection_t *det; + zarray_get(detections, i, &det); + + apriltag_detection_destroy(det); + } + + zarray_destroy(detections); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +struct custom_apriltag_data_t +{ + apriltag_detector_t *td; + image_u8_t im; + image_t img; +}; +void custom_imlib_find_apriltags(list_t *out, image_t *ptr, rectangle_t *roi, apriltag_families_t families, + float fx, float fy, float cx, float cy, int status) +{ + static struct custom_apriltag_data_t custom_apriltag_data; + switch (status) + { + case 1: + { + // Frame Buffer Memory Usage... + // -> GRAYSCALE Input Image = w*h*1 + // -> GRAYSCALE Threhsolded Image = w*h*1 + // -> UnionFind = w*h*2 (+w*h*1 for hash table) + + size_t resolution = roi->w * roi->h; + size_t fb_alloc_need = resolution * (1 + 1 + 2 + 1); // read above... + umm_init_x(((fb_avail() - fb_alloc_need) / resolution) * resolution); + custom_apriltag_data.td = apriltag_detector_create(); + + if (families & TAG16H5) { + apriltag_detector_add_family(custom_apriltag_data.td, (apriltag_family_t *) &tag16h5); + } + + if (families & TAG25H7) { + apriltag_detector_add_family(custom_apriltag_data.td, (apriltag_family_t *) &tag25h7); + } + + if (families & TAG25H9) { + apriltag_detector_add_family(custom_apriltag_data.td, (apriltag_family_t *) &tag25h9); + } + + if (families & TAG36H10) { + apriltag_detector_add_family(custom_apriltag_data.td, (apriltag_family_t *) &tag36h10); + } + + if (families & TAG36H11) { + apriltag_detector_add_family(custom_apriltag_data.td, (apriltag_family_t *) &tag36h11); + } + + if (families & ARTOOLKIT) { + apriltag_detector_add_family(custom_apriltag_data.td, (apriltag_family_t *) &artoolkit); + } + } + break; + case 2: + { + custom_apriltag_data.img.w = roi->w; + custom_apriltag_data.img.h = roi->h; + custom_apriltag_data.img.pixfmt = PIXFORMAT_GRAYSCALE; + custom_apriltag_data.img.size = image_size(&custom_apriltag_data.img); + if(ptr->pixfmt == PIXFORMAT_GRAYSCALE && custom_apriltag_data.img.w == ptr->w && custom_apriltag_data.img.h == ptr->h) + { + custom_apriltag_data.img.data = ptr->data; + } + else + { + custom_apriltag_data.img.data = fb_alloc(custom_apriltag_data.img.size, FB_ALLOC_NO_HINT); + imlib_pixfmt_to(&custom_apriltag_data.img, ptr, roi); + } + // imlib_draw_image(&img, ptr, 0, 0, 1.f, 1.f, roi, -1, 256, NULL, NULL, 0, NULL, NULL); + + custom_apriltag_data.im.width = roi->w; + custom_apriltag_data.im.height = roi->h; + custom_apriltag_data.im.stride = roi->w; + custom_apriltag_data.im.buf = custom_apriltag_data.img.data; + zarray_t *detections = apriltag_detector_detect(custom_apriltag_data.td, &custom_apriltag_data.im); + imlib_list_init(out, sizeof(find_apriltags_list_lnk_data_t)); + for (int i = 0, j = zarray_size(detections); i < j; i++) { + apriltag_detection_t *det; + zarray_get(detections, i, &det); + + find_apriltags_list_lnk_data_t lnk_data; + rectangle_init(&(lnk_data.rect), fast_roundf(det->p[0][0]) + roi->x, fast_roundf(det->p[0][1]) + roi->y, 0, 0); + + for (size_t k = 1, l = (sizeof(det->p) / sizeof(det->p[0])); k < l; k++) { + rectangle_t temp; + rectangle_init(&temp, fast_roundf(det->p[k][0]) + roi->x, fast_roundf(det->p[k][1]) + roi->y, 0, 0); + rectangle_united(&(lnk_data.rect), &temp); + } + + // Add corners... + lnk_data.corners[0].x = fast_roundf(det->p[3][0]) + roi->x; // top-left + lnk_data.corners[0].y = fast_roundf(det->p[3][1]) + roi->y; // top-left + lnk_data.corners[1].x = fast_roundf(det->p[2][0]) + roi->x; // top-right + lnk_data.corners[1].y = fast_roundf(det->p[2][1]) + roi->y; // top-right + lnk_data.corners[2].x = fast_roundf(det->p[1][0]) + roi->x; // bottom-right + lnk_data.corners[2].y = fast_roundf(det->p[1][1]) + roi->y; // bottom-right + lnk_data.corners[3].x = fast_roundf(det->p[0][0]) + roi->x; // bottom-left + lnk_data.corners[3].y = fast_roundf(det->p[0][1]) + roi->y; // bottom-left + + lnk_data.id = det->id; + lnk_data.family = 0; + + if(det->family == &tag16h5) { + lnk_data.family |= TAG16H5; + } + + if(det->family == &tag25h7) { + lnk_data.family |= TAG25H7; + } + + if(det->family == &tag25h9) { + lnk_data.family |= TAG25H9; + } + + if(det->family == &tag36h10) { + lnk_data.family |= TAG36H10; + } + + if(det->family == &tag36h11) { + lnk_data.family |= TAG36H11; + } + + if(det->family == &artoolkit) { + lnk_data.family |= ARTOOLKIT; + } + + lnk_data.hamming = det->hamming; + lnk_data.centroid.x = fast_roundf(det->c[0]) + roi->x; + lnk_data.centroid.y = fast_roundf(det->c[1]) + roi->y; + lnk_data.goodness = det->goodness / 255.0; // scale to [0:1] + lnk_data.decision_margin = det->decision_margin / 255.0; // scale to [0:1] + + // matd_t *pose = homography_to_pose(det->H, -fx, fy, cx, cy); + + // lnk_data.x_translation = MATD_EL(pose, 0, 3); + // lnk_data.y_translation = MATD_EL(pose, 1, 3); + // lnk_data.z_translation = MATD_EL(pose, 2, 3); + // lnk_data.x_rotation = fast_atan2f(MATD_EL(pose, 2, 1), MATD_EL(pose, 2, 2)); + // lnk_data.y_rotation = fast_atan2f(-MATD_EL(pose, 2, 0), fast_sqrtf(sq(MATD_EL(pose, 2, 1)) + sq(MATD_EL(pose, 2, 2)))); + // lnk_data.z_rotation = fast_atan2f(MATD_EL(pose, 1, 0), MATD_EL(pose, 0, 0)); + + // matd_destroy(pose); + + list_push_back(out, &lnk_data); + } + apriltag_detections_destroy(detections); + if(ptr->pixfmt == PIXFORMAT_GRAYSCALE && custom_apriltag_data.img.w == ptr->w && custom_apriltag_data.img.h == ptr->h) + fb_free(custom_apriltag_data.img.data); // grayscale_image; + } + break; + case 3: + { + apriltag_detector_destroy(custom_apriltag_data.td); + fb_free(NULL); // umm_init_x(); + } + break; + default: + break; + } +} +void imlib_find_apriltags(list_t *out, image_t *ptr, rectangle_t *roi, apriltag_families_t families, + float fx, float fy, float cx, float cy) +{ + // Frame Buffer Memory Usage... + // -> GRAYSCALE Input Image = w*h*1 + // -> GRAYSCALE Threhsolded Image = w*h*1 + // -> UnionFind = w*h*2 (+w*h*1 for hash table) + size_t resolution = roi->w * roi->h; + size_t fb_alloc_need = resolution * (1 + 1 + 2 + 1); // read above... + umm_init_x(((fb_avail() - fb_alloc_need) / resolution) * resolution); + apriltag_detector_t *td = apriltag_detector_create(); + + if (families & TAG16H5) { + apriltag_detector_add_family(td, (apriltag_family_t *) &tag16h5); + } + + if (families & TAG25H7) { + apriltag_detector_add_family(td, (apriltag_family_t *) &tag25h7); + } + + if (families & TAG25H9) { + apriltag_detector_add_family(td, (apriltag_family_t *) &tag25h9); + } + + if (families & TAG36H10) { + apriltag_detector_add_family(td, (apriltag_family_t *) &tag36h10); + } + + if (families & TAG36H11) { + apriltag_detector_add_family(td, (apriltag_family_t *) &tag36h11); + } + + if (families & ARTOOLKIT) { + apriltag_detector_add_family(td, (apriltag_family_t *) &artoolkit); + } + + image_t img; + img.w = roi->w; + img.h = roi->h; + img.pixfmt = PIXFORMAT_GRAYSCALE; + img.size = image_size(&img); + img.data = fb_alloc(img.size, FB_ALLOC_NO_HINT); + imlib_pixfmt_to(&img, ptr, roi); + // imlib_draw_image(&img, ptr, 0, 0, 1.f, 1.f, roi, -1, 256, NULL, NULL, 0, NULL, NULL); + + image_u8_t im; + im.width = roi->w; + im.height = roi->h; + im.stride = roi->w; + im.buf = img.data; + + zarray_t *detections = apriltag_detector_detect(td, &im); + imlib_list_init(out, sizeof(find_apriltags_list_lnk_data_t)); + + for (int i = 0, j = zarray_size(detections); i < j; i++) { + apriltag_detection_t *det; + zarray_get(detections, i, &det); + + find_apriltags_list_lnk_data_t lnk_data; + rectangle_init(&(lnk_data.rect), fast_roundf(det->p[0][0]) + roi->x, fast_roundf(det->p[0][1]) + roi->y, 0, 0); + + for (size_t k = 1, l = (sizeof(det->p) / sizeof(det->p[0])); k < l; k++) { + rectangle_t temp; + rectangle_init(&temp, fast_roundf(det->p[k][0]) + roi->x, fast_roundf(det->p[k][1]) + roi->y, 0, 0); + rectangle_united(&(lnk_data.rect), &temp); + } + + // Add corners... + lnk_data.corners[0].x = fast_roundf(det->p[3][0]) + roi->x; // top-left + lnk_data.corners[0].y = fast_roundf(det->p[3][1]) + roi->y; // top-left + lnk_data.corners[1].x = fast_roundf(det->p[2][0]) + roi->x; // top-right + lnk_data.corners[1].y = fast_roundf(det->p[2][1]) + roi->y; // top-right + lnk_data.corners[2].x = fast_roundf(det->p[1][0]) + roi->x; // bottom-right + lnk_data.corners[2].y = fast_roundf(det->p[1][1]) + roi->y; // bottom-right + lnk_data.corners[3].x = fast_roundf(det->p[0][0]) + roi->x; // bottom-left + lnk_data.corners[3].y = fast_roundf(det->p[0][1]) + roi->y; // bottom-left + + lnk_data.id = det->id; + lnk_data.family = 0; + + if(det->family == &tag16h5) { + lnk_data.family |= TAG16H5; + } + + if(det->family == &tag25h7) { + lnk_data.family |= TAG25H7; + } + + if(det->family == &tag25h9) { + lnk_data.family |= TAG25H9; + } + + if(det->family == &tag36h10) { + lnk_data.family |= TAG36H10; + } + + if(det->family == &tag36h11) { + lnk_data.family |= TAG36H11; + } + + if(det->family == &artoolkit) { + lnk_data.family |= ARTOOLKIT; + } + + lnk_data.hamming = det->hamming; + lnk_data.centroid.x = fast_roundf(det->c[0]) + roi->x; + lnk_data.centroid.y = fast_roundf(det->c[1]) + roi->y; + lnk_data.goodness = det->goodness / 255.0; // scale to [0:1] + lnk_data.decision_margin = det->decision_margin / 255.0; // scale to [0:1] + + matd_t *pose = homography_to_pose(det->H, -fx, fy, cx, cy); + + lnk_data.x_translation = MATD_EL(pose, 0, 3); + lnk_data.y_translation = MATD_EL(pose, 1, 3); + lnk_data.z_translation = MATD_EL(pose, 2, 3); + lnk_data.x_rotation = fast_atan2f(MATD_EL(pose, 2, 1), MATD_EL(pose, 2, 2)); + lnk_data.y_rotation = fast_atan2f(-MATD_EL(pose, 2, 0), fast_sqrtf(sq(MATD_EL(pose, 2, 1)) + sq(MATD_EL(pose, 2, 2)))); + lnk_data.z_rotation = fast_atan2f(MATD_EL(pose, 1, 0), MATD_EL(pose, 0, 0)); + + matd_destroy(pose); + + list_push_back(out, &lnk_data); + } + + apriltag_detections_destroy(detections); + fb_free(img.data); // grayscale_image; + apriltag_detector_destroy(td); + fb_free(NULL); // umm_init_x(); +} +#endif +#ifdef IMLIB_ENABLE_FIND_RECTS +void imlib_find_rects(list_t *out, image_t *ptr, rectangle_t *roi, uint32_t threshold) +{ + // Frame Buffer Memory Usage... + // -> GRAYSCALE Input Image = w*h*1 + // -> GRAYSCALE Threhsolded Image = w*h*1 + // -> UnionFind = w*h*2 (+w*h*1 for hash table) + size_t resolution = roi->w * roi->h; + size_t fb_alloc_need = resolution * (1 + 1 + 2 + 2); // read above... + umm_init_x(((fb_avail() - fb_alloc_need) / resolution) * resolution); + apriltag_detector_t *td = apriltag_detector_create(); + + image_t img; + img.w = roi->w; + img.h = roi->h; + img.pixfmt = PIXFORMAT_GRAYSCALE; + img.size = image_size(&img); + img.data = fb_alloc(img.size, FB_ALLOC_NO_HINT); + imlib_pixfmt_to(&img, ptr, roi); + + // imlib_draw_image(&img, ptr, 0, 0, 1.f, 1.f, roi, -1, 256, NULL, NULL, 0, NULL, NULL); + + image_u8_t im; + im.width = roi->w; + im.height = roi->h; + im.stride = roi->w; + im.buf = img.data; + + /////////////////////////////////////////////////////////// + // Detect quads according to requested image decimation + // and blurring parameters. + +// zarray_t *detections = apriltag_quad_gradient(td, &im, true); + zarray_t *detections = apriltag_quad_thresh(td, &im, true); + + td->nquads = zarray_size(detections); + + //////////////////////////////////////////////////////////////// + // Decode tags from each quad. + if (1) { + for (int i = 0; i < zarray_size(detections); i++) { + struct quad *quad_original; + zarray_get_volatile(detections, i, &quad_original); + + // refine edges is not dependent upon the tag family, thus + // apply this optimization BEFORE the other work. + //if (td->quad_decimate > 1 && td->refine_edges) { + if (td->refine_edges) { + refine_edges(td, &im, quad_original); + } + + // make sure the homographies are computed... + if (quad_update_homographies(quad_original)) + continue; + } + } + + //////////////////////////////////////////////////////////////// + // Reconcile detections--- don't report the same tag more + // than once. (Allow non-overlapping duplicate detections.) + if (1) { + zarray_t *poly0 = g2d_polygon_create_zeros(4); + zarray_t *poly1 = g2d_polygon_create_zeros(4); + + for (int i0 = 0; i0 < zarray_size(detections); i0++) { + + struct quad *det0; + zarray_get_volatile(detections, i0, &det0); + + for (int k = 0; k < 4; k++) + zarray_set(poly0, k, det0->p[k], NULL); + + for (int i1 = i0+1; i1 < zarray_size(detections); i1++) { + + struct quad *det1; + zarray_get_volatile(detections, i1, &det1); + + for (int k = 0; k < 4; k++) + zarray_set(poly1, k, det1->p[k], NULL); + + if (g2d_polygon_overlaps_polygon(poly0, poly1)) { + // the tags overlap. Delete one, keep the other. + + int pref = 0; // 0 means undecided which one we'll keep. + + // if we STILL don't prefer one detection over the other, then pick + // any deterministic criterion. + for (int i = 0; i < 4; i++) { + pref = prefer_smaller(pref, det0->p[i][0], det1->p[i][0]); + pref = prefer_smaller(pref, det0->p[i][1], det1->p[i][1]); + } + + if (pref == 0) { + // at this point, we should only be undecided if the tag detections + // are *exactly* the same. How would that happen? + // printf("uh oh, no preference for overlappingdetection\n"); + } + + if (pref < 0) { + // keep det0, destroy det1 + matd_destroy(det1->H); + matd_destroy(det1->Hinv); + zarray_remove_index(detections, i1, 1); + i1--; // retry the same index + goto retry1; + } else { + // keep det1, destroy det0 + matd_destroy(det0->H); + matd_destroy(det0->Hinv); + zarray_remove_index(detections, i0, 1); + i0--; // retry the same index. + goto retry0; + } + } + + retry1: ; + } + + retry0: ; + } + + zarray_destroy(poly0); + zarray_destroy(poly1); + } + + imlib_list_init(out, sizeof(find_rects_list_lnk_data_t)); + + const int r_diag_len = fast_roundf(fast_sqrtf((roi->w * roi->w) + (roi->h * roi->h))) * 2; + int *theta_buffer = fb_alloc(sizeof(int) * r_diag_len, FB_ALLOC_NO_HINT); + uint32_t *mag_buffer = fb_alloc(sizeof(uint32_t) * r_diag_len, FB_ALLOC_NO_HINT); + point_t *point_buffer = fb_alloc(sizeof(point_t) * r_diag_len, FB_ALLOC_NO_HINT); + + for (int i = 0, j = zarray_size(detections); i < j; i++) { + struct quad *det; + zarray_get_volatile(detections, i, &det); + + line_t lines[4]; + lines[0].x1 = fast_roundf(det->p[0][0]) + roi->x; lines[0].y1 = fast_roundf(det->p[0][1]) + roi->y; + lines[0].x2 = fast_roundf(det->p[1][0]) + roi->x; lines[0].y2 = fast_roundf(det->p[1][1]) + roi->y; + lines[1].x1 = fast_roundf(det->p[1][0]) + roi->x; lines[1].y1 = fast_roundf(det->p[1][1]) + roi->y; + lines[1].x2 = fast_roundf(det->p[2][0]) + roi->x; lines[1].y2 = fast_roundf(det->p[2][1]) + roi->y; + lines[2].x1 = fast_roundf(det->p[2][0]) + roi->x; lines[2].y1 = fast_roundf(det->p[2][1]) + roi->y; + lines[2].x2 = fast_roundf(det->p[3][0]) + roi->x; lines[2].y2 = fast_roundf(det->p[3][1]) + roi->y; + lines[3].x1 = fast_roundf(det->p[3][0]) + roi->x; lines[3].y1 = fast_roundf(det->p[3][1]) + roi->y; + lines[3].x2 = fast_roundf(det->p[0][0]) + roi->x; lines[3].y2 = fast_roundf(det->p[0][1]) + roi->y; + + uint32_t magnitude = 0; + + for (int i = 0; i < 4; i++) { + if(!lb_clip_line(&lines[i], 0, 0, roi->w, roi->h)) { + continue; + } + + size_t index = trace_line(ptr, &lines[i], theta_buffer, mag_buffer, point_buffer); + + for (int j = 0; j < index; j++) { + magnitude += mag_buffer[j]; + } + } + + if (magnitude < threshold) { + continue; + } + + find_rects_list_lnk_data_t lnk_data; + rectangle_init(&(lnk_data.rect), fast_roundf(det->p[0][0]) + roi->x, fast_roundf(det->p[0][1]) + roi->y, 0, 0); + + for (size_t k = 1, l = (sizeof(det->p) / sizeof(det->p[0])); k < l; k++) { + rectangle_t temp; + rectangle_init(&temp, fast_roundf(det->p[k][0]) + roi->x, fast_roundf(det->p[k][1]) + roi->y, 0, 0); + rectangle_united(&(lnk_data.rect), &temp); + } + + // Add corners... + lnk_data.corners[0].x = fast_roundf(det->p[3][0]) + roi->x; // top-left + lnk_data.corners[0].y = fast_roundf(det->p[3][1]) + roi->y; // top-left + lnk_data.corners[1].x = fast_roundf(det->p[2][0]) + roi->x; // top-right + lnk_data.corners[1].y = fast_roundf(det->p[2][1]) + roi->y; // top-right + lnk_data.corners[2].x = fast_roundf(det->p[1][0]) + roi->x; // bottom-right + lnk_data.corners[2].y = fast_roundf(det->p[1][1]) + roi->y; // bottom-right + lnk_data.corners[3].x = fast_roundf(det->p[0][0]) + roi->x; // bottom-left + lnk_data.corners[3].y = fast_roundf(det->p[0][1]) + roi->y; // bottom-left + + lnk_data.magnitude = magnitude; + + list_push_back(out, &lnk_data); + } + + fb_free(point_buffer); // point_buffer + fb_free(mag_buffer); // mag_buffer + fb_free(theta_buffer); // theta_buffer + + zarray_destroy(detections); + fb_free(img.data); // grayscale_image; + apriltag_detector_destroy(td); + fb_free(NULL); // umm_init_x(); +} +#endif //IMLIB_ENABLE_FIND_RECTS + +#ifdef IMLIB_ENABLE_ROTATION_CORR +// http://jepsonsblog.blogspot.com/2012/11/rotation-in-3d-using-opencvs.html +void imlib_rotation_corr(image_t *img, float x_rotation, float y_rotation, float z_rotation, + float x_translation, float y_translation, + float zoom, float fov, float *corners) +{ + // Create a tmp copy of the image to pull pixels from. + size_t size = image_size(img); + void *data = fb_alloc(size, FB_ALLOC_NO_HINT); + memcpy(data, img->data, size); + memset(img->data, 0, size); + + umm_init_x(fb_avail()); + + int w = img->w; + int h = img->h; + float z = (fast_sqrtf((w * w) + (h * h)) / 2) / tanf(fov / 2); + float z_z = z * zoom; + + matd_t *A1 = matd_create(4, 3); + MATD_EL(A1, 0, 0) = 1; MATD_EL(A1, 0, 1) = 0; MATD_EL(A1, 0, 2) = -w / 2; + MATD_EL(A1, 1, 0) = 0; MATD_EL(A1, 1, 1) = 1; MATD_EL(A1, 1, 2) = -h / 2; + MATD_EL(A1, 2, 0) = 0; MATD_EL(A1, 2, 1) = 0; MATD_EL(A1, 2, 2) = 0; + MATD_EL(A1, 3, 0) = 0; MATD_EL(A1, 3, 1) = 0; MATD_EL(A1, 3, 2) = 1; // needed for z translation + + matd_t *RX = matd_create(4, 4); + MATD_EL(RX, 0, 0) = 1; MATD_EL(RX, 0, 1) = 0; MATD_EL(RX, 0, 2) = 0; MATD_EL(RX, 0, 3) = 0; + MATD_EL(RX, 1, 0) = 0; MATD_EL(RX, 1, 1) = +cosf(x_rotation); MATD_EL(RX, 1, 2) = -sinf(x_rotation); MATD_EL(RX, 1, 3) = 0; + MATD_EL(RX, 2, 0) = 0; MATD_EL(RX, 2, 1) = +sinf(x_rotation); MATD_EL(RX, 2, 2) = +cosf(x_rotation); MATD_EL(RX, 2, 3) = 0; + MATD_EL(RX, 3, 0) = 0; MATD_EL(RX, 3, 1) = 0; MATD_EL(RX, 3, 2) = 0; MATD_EL(RX, 3, 3) = 1; + + matd_t *RY = matd_create(4, 4); + MATD_EL(RY, 0, 0) = +cosf(y_rotation); MATD_EL(RY, 0, 1) = 0; MATD_EL(RY, 0, 2) = -sinf(y_rotation); MATD_EL(RY, 0, 3) = 0; + MATD_EL(RY, 1, 0) = 0; MATD_EL(RY, 1, 1) = 1; MATD_EL(RY, 1, 2) = 0; MATD_EL(RY, 1, 3) = 0; + MATD_EL(RY, 2, 0) = +sinf(y_rotation); MATD_EL(RY, 2, 1) = 0; MATD_EL(RY, 2, 2) = +cosf(y_rotation); MATD_EL(RY, 2, 3) = 0; + MATD_EL(RY, 3, 0) = 0; MATD_EL(RY, 3, 1) = 0; MATD_EL(RY, 3, 2) = 0; MATD_EL(RY, 3, 3) = 1; + + matd_t *RZ = matd_create(4, 4); + MATD_EL(RZ, 0, 0) = +cosf(z_rotation); MATD_EL(RZ, 0, 1) = -sinf(z_rotation); MATD_EL(RZ, 0, 2) = 0; MATD_EL(RZ, 0, 3) = 0; + MATD_EL(RZ, 1, 0) = +sinf(z_rotation); MATD_EL(RZ, 1, 1) = +cosf(z_rotation); MATD_EL(RZ, 1, 2) = 0; MATD_EL(RZ, 1, 3) = 0; + MATD_EL(RZ, 2, 0) = 0; MATD_EL(RZ, 2, 1) = 0; MATD_EL(RZ, 2, 2) = 1; MATD_EL(RZ, 2, 3) = 0; + MATD_EL(RZ, 3, 0) = 0; MATD_EL(RZ, 3, 1) = 0; MATD_EL(RZ, 3, 2) = 0; MATD_EL(RZ, 3, 3) = 1; + + matd_t *R = matd_op("M*M*M", RX, RY, RZ); + + matd_t *T = matd_create(4, 4); + MATD_EL(T, 0, 0) = 1; MATD_EL(T, 0, 1) = 0; MATD_EL(T, 0, 2) = 0; MATD_EL(T, 0, 3) = x_translation; + MATD_EL(T, 1, 0) = 0; MATD_EL(T, 1, 1) = 1; MATD_EL(T, 1, 2) = 0; MATD_EL(T, 1, 3) = y_translation; + MATD_EL(T, 2, 0) = 0; MATD_EL(T, 2, 1) = 0; MATD_EL(T, 2, 2) = 1; MATD_EL(T, 2, 3) = z; + MATD_EL(T, 3, 0) = 0; MATD_EL(T, 3, 1) = 0; MATD_EL(T, 3, 2) = 0; MATD_EL(T, 3, 3) = 1; + + matd_t *A2 = matd_create(3, 4); + MATD_EL(A2, 0, 0) = z_z; MATD_EL(A2, 0, 1) = 0; MATD_EL(A2, 0, 2) = w / 2; MATD_EL(A2, 0, 3) = 0; + MATD_EL(A2, 1, 0) = 0; MATD_EL(A2, 1, 1) = z_z; MATD_EL(A2, 1, 2) = h / 2; MATD_EL(A2, 1, 3) = 0; + MATD_EL(A2, 2, 0) = 0; MATD_EL(A2, 2, 1) = 0; MATD_EL(A2, 2, 2) = 1; MATD_EL(A2, 2, 3) = 0; + + matd_t *T1 = matd_op("M*M", R, A1); + matd_t *T2 = matd_op("M*M", T, T1); + matd_t *T3 = matd_op("M*M", A2, T2); + matd_t *T4 = matd_inverse(T3); + + if (T4 && corners) { + float corr[4]; + zarray_t *correspondences = zarray_create(sizeof(float[4])); + + corr[0] = 0; + corr[1] = 0; + corr[2] = corners[0]; + corr[3] = corners[1]; + zarray_add(correspondences, &corr); + + corr[0] = w - 1; + corr[1] = 0; + corr[2] = corners[2]; + corr[3] = corners[3]; + zarray_add(correspondences, &corr); + + corr[0] = w - 1; + corr[1] = h - 1; + corr[2] = corners[4]; + corr[3] = corners[5]; + zarray_add(correspondences, &corr); + + corr[0] = 0; + corr[1] = h - 1; + corr[2] = corners[6]; + corr[3] = corners[7]; + zarray_add(correspondences, &corr); + + matd_t *H = homography_compute(correspondences, HOMOGRAPHY_COMPUTE_FLAG_INVERSE); + + if (!H) { // try again... + H = homography_compute(correspondences, HOMOGRAPHY_COMPUTE_FLAG_SVD); + } + + if (H) { + matd_t *T5 = matd_op("M*M", H, T4); + matd_destroy(H); + matd_destroy(T4); + T4 = T5; + } + + zarray_destroy(correspondences); + } + + if (T4) { + float T4_00 = MATD_EL(T4, 0, 0), T4_01 = MATD_EL(T4, 0, 1), T4_02 = MATD_EL(T4, 0, 2); + float T4_10 = MATD_EL(T4, 1, 0), T4_11 = MATD_EL(T4, 1, 1), T4_12 = MATD_EL(T4, 1, 2); + float T4_20 = MATD_EL(T4, 2, 0), T4_21 = MATD_EL(T4, 2, 1), T4_22 = MATD_EL(T4, 2, 2); + + if ((fast_fabsf(T4_20) < MATD_EPS) && (fast_fabsf(T4_21) < MATD_EPS)) { // warp affine + T4_00 /= T4_22; + T4_01 /= T4_22; + T4_02 /= T4_22; + T4_10 /= T4_22; + T4_11 /= T4_22; + T4_12 /= T4_22; + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *tmp = (uint32_t *) data; + + for (int y = 0, yy = h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = w; x < xx; x++) { + int sourceX = fast_roundf(T4_00*x + T4_01*y + T4_02); + int sourceY = fast_roundf(T4_10*x + T4_11*y + T4_12); + + if ((0 <= sourceX) && (sourceX < w) && (0 <= sourceY) && (sourceY < h)) { + uint32_t *ptr = tmp + (((w + UINT32_T_MASK) >> UINT32_T_SHIFT) * sourceY); + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(ptr, sourceX); + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, pixel); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *tmp = (uint8_t *) data; + + for (int y = 0, yy = h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = w; x < xx; x++) { + int sourceX = fast_roundf(T4_00*x + T4_01*y + T4_02); + int sourceY = fast_roundf(T4_10*x + T4_11*y + T4_12); + + if ((0 <= sourceX) && (sourceX < w) && (0 <= sourceY) && (sourceY < h)) { + uint8_t *ptr = tmp + (w * sourceY); + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(ptr, sourceX); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, pixel); + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *tmp = (uint16_t *) data; + + for (int y = 0, yy = h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = w; x < xx; x++) { + int sourceX = fast_roundf(T4_00*x + T4_01*y + T4_02); + int sourceY = fast_roundf(T4_10*x + T4_11*y + T4_12); + + if ((0 <= sourceX) && (sourceX < w) && (0 <= sourceY) && (sourceY < h)) { + uint16_t *ptr = tmp + (w * sourceY); + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(ptr, sourceX); + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, pixel); + } + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *tmp = (pixel24_t *) data; + + for (int y = 0, yy = h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = w; x < xx; x++) { + int sourceX = fast_roundf(T4_00*x + T4_01*y + T4_02); + int sourceY = fast_roundf(T4_10*x + T4_11*y + T4_12); + + if ((0 <= sourceX) && (sourceX < w) && (0 <= sourceY) && (sourceY < h)) { + pixel24_t *ptr = tmp + (w * sourceY); + pixel24_t pixel = IMAGE_GET_RGB888_PIXEL_FAST_(ptr, sourceX); + IMAGE_PUT_RGB888_PIXEL_FAST_(row_ptr, x, pixel); + } + } + } + break; + } + default: { + break; + } + } + } else { // warp persepective + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *tmp = (uint32_t *) data; + + for (int y = 0, yy = h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = w; x < xx; x++) { + float xxx = T4_00*x + T4_01*y + T4_02; + float yyy = T4_10*x + T4_11*y + T4_12; + float zzz = T4_20*x + T4_21*y + T4_22; + int sourceX = fast_roundf(xxx / zzz); + int sourceY = fast_roundf(yyy / zzz); + + if ((0 <= sourceX) && (sourceX < w) && (0 <= sourceY) && (sourceY < h)) { + uint32_t *ptr = tmp + (((w + UINT32_T_MASK) >> UINT32_T_SHIFT) * sourceY); + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(ptr, sourceX); + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, pixel); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *tmp = (uint8_t *) data; + + for (int y = 0, yy = h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = w; x < xx; x++) { + float xxx = T4_00*x + T4_01*y + T4_02; + float yyy = T4_10*x + T4_11*y + T4_12; + float zzz = T4_20*x + T4_21*y + T4_22; + int sourceX = fast_roundf(xxx / zzz); + int sourceY = fast_roundf(yyy / zzz); + + if ((0 <= sourceX) && (sourceX < w) && (0 <= sourceY) && (sourceY < h)) { + uint8_t *ptr = tmp + (w * sourceY); + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(ptr, sourceX); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, pixel); + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *tmp = (uint16_t *) data; + + for (int y = 0, yy = h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = w; x < xx; x++) { + float xxx = T4_00*x + T4_01*y + T4_02; + float yyy = T4_10*x + T4_11*y + T4_12; + float zzz = T4_20*x + T4_21*y + T4_22; + int sourceX = fast_roundf(xxx / zzz); + int sourceY = fast_roundf(yyy / zzz); + + if ((0 <= sourceX) && (sourceX < w) && (0 <= sourceY) && (sourceY < h)) { + uint16_t *ptr = tmp + (w * sourceY); + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(ptr, sourceX); + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, pixel); + } + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *tmp = (pixel24_t *) data; + + for (int y = 0, yy = h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = w; x < xx; x++) { + float xxx = T4_00*x + T4_01*y + T4_02; + float yyy = T4_10*x + T4_11*y + T4_12; + float zzz = T4_20*x + T4_21*y + T4_22; + int sourceX = fast_roundf(xxx / zzz); + int sourceY = fast_roundf(yyy / zzz); + + if ((0 <= sourceX) && (sourceX < w) && (0 <= sourceY) && (sourceY < h)) { + pixel24_t *ptr = tmp + (w * sourceY); + pixel24_t pixel = IMAGE_GET_RGB888_PIXEL_FAST_(ptr, sourceX); + IMAGE_PUT_RGB888_PIXEL_FAST_(row_ptr, x, pixel); + } + } + } + break; + } + default: { + break; + } + } + } + + matd_destroy(T4); + } + + matd_destroy(T3); + matd_destroy(T2); + matd_destroy(T1); + matd_destroy(A2); + matd_destroy(T); + matd_destroy(R); + matd_destroy(RZ); + matd_destroy(RY); + matd_destroy(RX); + matd_destroy(A1); + + fb_free(NULL); // umm_init_x(); + + fb_free(data); +} +#endif //IMLIB_ENABLE_ROTATION_CORR +#pragma GCC diagnostic pop diff --git a/github_source/minicv2/src/arm_dsp_compat.c b/github_source/minicv2/src/arm_dsp_compat.c new file mode 100755 index 0000000..e49a317 --- /dev/null +++ b/github_source/minicv2/src/arm_dsp_compat.c @@ -0,0 +1,195 @@ +#include "omv_boardconfig.h" +#include "arm_compat.h" +#include +#define FAST_MATH_TABLE_SIZE 512 +static const float sinTable_f32[FAST_MATH_TABLE_SIZE + 1] = { + 0.00000000f, 0.01227154f, 0.02454123f, 0.03680722f, 0.04906767f, 0.06132074f, + 0.07356456f, 0.08579731f, 0.09801714f, 0.11022221f, 0.12241068f, 0.13458071f, + 0.14673047f, 0.15885814f, 0.17096189f, 0.18303989f, 0.19509032f, 0.20711138f, + 0.21910124f, 0.23105811f, 0.24298018f, 0.25486566f, 0.26671276f, 0.27851969f, + 0.29028468f, 0.30200595f, 0.31368174f, 0.32531029f, 0.33688985f, 0.34841868f, + 0.35989504f, 0.37131719f, 0.38268343f, 0.39399204f, 0.40524131f, 0.41642956f, + 0.42755509f, 0.43861624f, 0.44961133f, 0.46053871f, 0.47139674f, 0.48218377f, + 0.49289819f, 0.50353838f, 0.51410274f, 0.52458968f, 0.53499762f, 0.54532499f, + 0.55557023f, 0.56573181f, 0.57580819f, 0.58579786f, 0.59569930f, 0.60551104f, + 0.61523159f, 0.62485949f, 0.63439328f, 0.64383154f, 0.65317284f, 0.66241578f, + 0.67155895f, 0.68060100f, 0.68954054f, 0.69837625f, 0.70710678f, 0.71573083f, + 0.72424708f, 0.73265427f, 0.74095113f, 0.74913639f, 0.75720885f, 0.76516727f, + 0.77301045f, 0.78073723f, 0.78834643f, 0.79583690f, 0.80320753f, 0.81045720f, + 0.81758481f, 0.82458930f, 0.83146961f, 0.83822471f, 0.84485357f, 0.85135519f, + 0.85772861f, 0.86397286f, 0.87008699f, 0.87607009f, 0.88192126f, 0.88763962f, + 0.89322430f, 0.89867447f, 0.90398929f, 0.90916798f, 0.91420976f, 0.91911385f, + 0.92387953f, 0.92850608f, 0.93299280f, 0.93733901f, 0.94154407f, 0.94560733f, + 0.94952818f, 0.95330604f, 0.95694034f, 0.96043052f, 0.96377607f, 0.96697647f, + 0.97003125f, 0.97293995f, 0.97570213f, 0.97831737f, 0.98078528f, 0.98310549f, + 0.98527764f, 0.98730142f, 0.98917651f, 0.99090264f, 0.99247953f, 0.99390697f, + 0.99518473f, 0.99631261f, 0.99729046f, 0.99811811f, 0.99879546f, 0.99932238f, + 0.99969882f, 0.99992470f, 1.00000000f, 0.99992470f, 0.99969882f, 0.99932238f, + 0.99879546f, 0.99811811f, 0.99729046f, 0.99631261f, 0.99518473f, 0.99390697f, + 0.99247953f, 0.99090264f, 0.98917651f, 0.98730142f, 0.98527764f, 0.98310549f, + 0.98078528f, 0.97831737f, 0.97570213f, 0.97293995f, 0.97003125f, 0.96697647f, + 0.96377607f, 0.96043052f, 0.95694034f, 0.95330604f, 0.94952818f, 0.94560733f, + 0.94154407f, 0.93733901f, 0.93299280f, 0.92850608f, 0.92387953f, 0.91911385f, + 0.91420976f, 0.90916798f, 0.90398929f, 0.89867447f, 0.89322430f, 0.88763962f, + 0.88192126f, 0.87607009f, 0.87008699f, 0.86397286f, 0.85772861f, 0.85135519f, + 0.84485357f, 0.83822471f, 0.83146961f, 0.82458930f, 0.81758481f, 0.81045720f, + 0.80320753f, 0.79583690f, 0.78834643f, 0.78073723f, 0.77301045f, 0.76516727f, + 0.75720885f, 0.74913639f, 0.74095113f, 0.73265427f, 0.72424708f, 0.71573083f, + 0.70710678f, 0.69837625f, 0.68954054f, 0.68060100f, 0.67155895f, 0.66241578f, + 0.65317284f, 0.64383154f, 0.63439328f, 0.62485949f, 0.61523159f, 0.60551104f, + 0.59569930f, 0.58579786f, 0.57580819f, 0.56573181f, 0.55557023f, 0.54532499f, + 0.53499762f, 0.52458968f, 0.51410274f, 0.50353838f, 0.49289819f, 0.48218377f, + 0.47139674f, 0.46053871f, 0.44961133f, 0.43861624f, 0.42755509f, 0.41642956f, + 0.40524131f, 0.39399204f, 0.38268343f, 0.37131719f, 0.35989504f, 0.34841868f, + 0.33688985f, 0.32531029f, 0.31368174f, 0.30200595f, 0.29028468f, 0.27851969f, + 0.26671276f, 0.25486566f, 0.24298018f, 0.23105811f, 0.21910124f, 0.20711138f, + 0.19509032f, 0.18303989f, 0.17096189f, 0.15885814f, 0.14673047f, 0.13458071f, + 0.12241068f, 0.11022221f, 0.09801714f, 0.08579731f, 0.07356456f, 0.06132074f, + 0.04906767f, 0.03680722f, 0.02454123f, 0.01227154f, 0.00000000f, -0.01227154f, + -0.02454123f, -0.03680722f, -0.04906767f, -0.06132074f, -0.07356456f, + -0.08579731f, -0.09801714f, -0.11022221f, -0.12241068f, -0.13458071f, + -0.14673047f, -0.15885814f, -0.17096189f, -0.18303989f, -0.19509032f, + -0.20711138f, -0.21910124f, -0.23105811f, -0.24298018f, -0.25486566f, + -0.26671276f, -0.27851969f, -0.29028468f, -0.30200595f, -0.31368174f, + -0.32531029f, -0.33688985f, -0.34841868f, -0.35989504f, -0.37131719f, + -0.38268343f, -0.39399204f, -0.40524131f, -0.41642956f, -0.42755509f, + -0.43861624f, -0.44961133f, -0.46053871f, -0.47139674f, -0.48218377f, + -0.49289819f, -0.50353838f, -0.51410274f, -0.52458968f, -0.53499762f, + -0.54532499f, -0.55557023f, -0.56573181f, -0.57580819f, -0.58579786f, + -0.59569930f, -0.60551104f, -0.61523159f, -0.62485949f, -0.63439328f, + -0.64383154f, -0.65317284f, -0.66241578f, -0.67155895f, -0.68060100f, + -0.68954054f, -0.69837625f, -0.70710678f, -0.71573083f, -0.72424708f, + -0.73265427f, -0.74095113f, -0.74913639f, -0.75720885f, -0.76516727f, + -0.77301045f, -0.78073723f, -0.78834643f, -0.79583690f, -0.80320753f, + -0.81045720f, -0.81758481f, -0.82458930f, -0.83146961f, -0.83822471f, + -0.84485357f, -0.85135519f, -0.85772861f, -0.86397286f, -0.87008699f, + -0.87607009f, -0.88192126f, -0.88763962f, -0.89322430f, -0.89867447f, + -0.90398929f, -0.90916798f, -0.91420976f, -0.91911385f, -0.92387953f, + -0.92850608f, -0.93299280f, -0.93733901f, -0.94154407f, -0.94560733f, + -0.94952818f, -0.95330604f, -0.95694034f, -0.96043052f, -0.96377607f, + -0.96697647f, -0.97003125f, -0.97293995f, -0.97570213f, -0.97831737f, + -0.98078528f, -0.98310549f, -0.98527764f, -0.98730142f, -0.98917651f, + -0.99090264f, -0.99247953f, -0.99390697f, -0.99518473f, -0.99631261f, + -0.99729046f, -0.99811811f, -0.99879546f, -0.99932238f, -0.99969882f, + -0.99992470f, -1.00000000f, -0.99992470f, -0.99969882f, -0.99932238f, + -0.99879546f, -0.99811811f, -0.99729046f, -0.99631261f, -0.99518473f, + -0.99390697f, -0.99247953f, -0.99090264f, -0.98917651f, -0.98730142f, + -0.98527764f, -0.98310549f, -0.98078528f, -0.97831737f, -0.97570213f, + -0.97293995f, -0.97003125f, -0.96697647f, -0.96377607f, -0.96043052f, + -0.95694034f, -0.95330604f, -0.94952818f, -0.94560733f, -0.94154407f, + -0.93733901f, -0.93299280f, -0.92850608f, -0.92387953f, -0.91911385f, + -0.91420976f, -0.90916798f, -0.90398929f, -0.89867447f, -0.89322430f, + -0.88763962f, -0.88192126f, -0.87607009f, -0.87008699f, -0.86397286f, + -0.85772861f, -0.85135519f, -0.84485357f, -0.83822471f, -0.83146961f, + -0.82458930f, -0.81758481f, -0.81045720f, -0.80320753f, -0.79583690f, + -0.78834643f, -0.78073723f, -0.77301045f, -0.76516727f, -0.75720885f, + -0.74913639f, -0.74095113f, -0.73265427f, -0.72424708f, -0.71573083f, + -0.70710678f, -0.69837625f, -0.68954054f, -0.68060100f, -0.67155895f, + -0.66241578f, -0.65317284f, -0.64383154f, -0.63439328f, -0.62485949f, + -0.61523159f, -0.60551104f, -0.59569930f, -0.58579786f, -0.57580819f, + -0.56573181f, -0.55557023f, -0.54532499f, -0.53499762f, -0.52458968f, + -0.51410274f, -0.50353838f, -0.49289819f, -0.48218377f, -0.47139674f, + -0.46053871f, -0.44961133f, -0.43861624f, -0.42755509f, -0.41642956f, + -0.40524131f, -0.39399204f, -0.38268343f, -0.37131719f, -0.35989504f, + -0.34841868f, -0.33688985f, -0.32531029f, -0.31368174f, -0.30200595f, + -0.29028468f, -0.27851969f, -0.26671276f, -0.25486566f, -0.24298018f, + -0.23105811f, -0.21910124f, -0.20711138f, -0.19509032f, -0.18303989f, + -0.17096189f, -0.15885814f, -0.14673047f, -0.13458071f, -0.12241068f, + -0.11022221f, -0.09801714f, -0.08579731f, -0.07356456f, -0.06132074f, + -0.04906767f, -0.03680722f, -0.02454123f, -0.01227154f, -0.00000000f +}; + +float arm_sin_f32( + float x) +{ + float sinVal, fract, in; /* Temporary variables for input, output */ + uint16_t index; /* Index variable */ + float a, b; /* Two nearest output values */ + int32_t n; + float findex; + + /* Special case for small negative inputs */ + if ((x < 0.0f) && (x >= (float)(-1.9e-7f))) { + return x; + } + + /* input x is in radians */ + /* Scale the input to [0 1] range from [0 2*PI] , divide input by 2*pi */ + in = x * 0.159154943092f; + + /* Calculation of floor value of input */ + n = (int32_t) in; + + /* Make negative values towards -infinity */ + if (x < 0.0f) + { + n--; + } + + /* Map input value to [0 1] */ + in = in - (float) n; + + /* Calculation of index of the table */ + findex = (float) FAST_MATH_TABLE_SIZE * in; + + index = ((uint16_t)findex) & 0x1ff; + + /* fractional value calculation */ + fract = findex - (float) index; + + /* Read two nearest values of input value from the sin table */ + a = sinTable_f32[index]; + b = sinTable_f32[index+1]; + + /* Linear interpolation process */ + sinVal = (1.0f-fract)*a + fract*b; + + /* Return the output value */ + return (sinVal); +} + +float arm_cos_f32( + float x) +{ + float cosVal, fract, in; /* Temporary variables for input, output */ + uint16_t index; /* Index variable */ + float a, b; /* Two nearest output values */ + int32_t n; + float findex; + + /* input x is in radians */ + /* Scale the input to [0 1] range from [0 2*PI] , divide input by 2*pi, add 0.25 (pi/2) to read sine table */ + in = x * 0.159154943092f + 0.25f; + + /* Calculation of floor value of input */ + n = (int32_t) in; + + /* Make negative values towards -infinity */ + if (in < 0.0f) + { + n--; + } + + /* Map input value to [0 1] */ + in = in - (float) n; + + /* Calculation of index of the table */ + findex = (float) FAST_MATH_TABLE_SIZE * in; + index = ((uint16_t)findex) & 0x1ff; + + /* fractional value calculation */ + fract = findex - (float) index; + + /* Read two nearest values of input value from the cos table */ + a = sinTable_f32[index]; + b = sinTable_f32[index+1]; + + /* Linear interpolation process */ + cosVal = (1.0f-fract)*a + fract*b; + + /* Return the output value */ + return (cosVal); +} +int abs__(int n) { + return n<0 ? -n : n; +} diff --git a/github_source/minicv2/src/array.c b/github_source/minicv2/src/array.c new file mode 100644 index 0000000..5976a66 --- /dev/null +++ b/github_source/minicv2/src/array.c @@ -0,0 +1,178 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Dynamic array. + */ +#include +// #include "py/runtime.h" +// #include "py/stackctrl.h" +#include "xalloc.h" +#include "array.h" +#define ARRAY_INIT_SIZE (4) // Size of one GC block. + +void array_alloc(array_t **a, array_dtor_t dtor) +{ + array_t *array = xalloc(sizeof(array_t)); + array->index = 0; + array->length = ARRAY_INIT_SIZE; + array->dtor = dtor; + array->data = xalloc(ARRAY_INIT_SIZE * sizeof(void*)); + *a = array; +} + +void array_alloc_init(array_t **a, array_dtor_t dtor, int size) +{ + array_t *array = xalloc(sizeof(array_t)); + array->index = 0; + array->length = size; + array->dtor = dtor; + array->data = xalloc(size * sizeof(void*)); + *a = array; +} + +void array_clear(array_t *array) +{ + if (array->dtor != NULL) { + for (int i=0, j=array->index; idtor(array->data[i]); + } + } + xfree(array->data); + array->index = 0; + array->length = 0; + array->data = NULL; + // Note: realloc with null pointer and (size != 0) returns valid pointer. + // Note: realloc with valid pointer and (size == 0) returns null pointer. +} + +void array_free(array_t *array) +{ + array_clear(array); + xfree(array); +} + +int array_length(array_t *array) +{ + return array->index; // index is the actual length, length is the max length +} + +void *array_at(array_t *array, int idx) +{ + return array->data[idx]; +} + +void array_push_back(array_t *array, void *element) +{ + if (array->index == array->length) { + array->length += ARRAY_INIT_SIZE; + array->data = xrealloc(array->data, array->length * sizeof(void*)); + } + array->data[array->index++] = element; +} + +void *array_pop_back(array_t *array) +{ + void *el=NULL; + if (array->index) { + el = array->data[--array->index]; + } + return el; +} + +void *array_take(array_t *array, int idx) +{ + void *el=array->data[idx]; + if ((1 < array->index) && (idx < (array->index - 1))) { + /* Since dst is always < src we can just use memcpy */ + memcpy(array->data+idx, array->data+idx+1, (array->index-idx-1) * sizeof(void*)); + } + array->index--; + return el; +} + +void array_erase(array_t *array, int idx) +{ + if (array->dtor) { + array->dtor(array->data[idx]); + } + array_take(array, idx); +} + +void array_resize(array_t *array, int num) +{ + if (array->index != num) { + if (!num) { + array_clear(array); + } else { + if (array->index > num) { + if (array->dtor != NULL) { + for (int i=num, j=array->index; idtor(array->data[i]); + } + } + array->index = num; + } + // resize array + array->length = num; + array->data = xrealloc(array->data, array->length * sizeof(void*)); + } + } +} + +// see micropython quicksort (objlist.c -> mp_quicksort) +static void quicksort(void **head, void **tail, array_comp_t comp) +{ + // MP_STACK_CHECK(); + while (head < tail) { + void **h = head - 1; + void **t = tail; + void *v = tail[0]; + for (;;) { + do ++h; while(h < t && comp(h[0], v) < 0); + do --t; while(h < t && comp(v, t[0]) < 0); + if (h >= t) break; + void *x = h[0]; + h[0] = t[0]; + t[0] = x; + } + void *x = h[0]; + h[0] = tail[0]; + tail[0] = x; + // do the smaller recursive call first, to keep stack within O(log(N)) + if (t - head < tail - h - 1) { + quicksort(head, t, comp); + head = h + 1; + } else { + quicksort(h + 1, tail, comp); + tail = t; + } + } +} + +// TODO Python defines sort to be stable but ours is not +void array_sort(array_t *array, array_comp_t comp) +{ + if (array->index > 1) { + quicksort(array->data, array->data + array->index - 1, comp); + } +} + +void array_isort(array_t *array, array_comp_t comp) +{ + if (array->index > 1) { + for (int i = 1; i < array->index; i++) { + int j = i-1; + void *t = array->data[i]; + while (j >= 0 && comp(array->data[j], t)) { + array->data[j+1] = array->data[j]; + j--; + } + array->data[j+1] = t; + } + } +} diff --git a/github_source/minicv2/src/bayer.c b/github_source/minicv2/src/bayer.c new file mode 100644 index 0000000..1f8928b --- /dev/null +++ b/github_source/minicv2/src/bayer.c @@ -0,0 +1,931 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Debayering Functions + */ +#include "imlib.h" + +void imlib_debayer_line(int x_start, int x_end, int y_row, void *dst_row_ptr, pixformat_t pixfmt, image_t *src) +{ + int src_w = src->w, w_limit = src_w - 1, w_limit_m_1 = w_limit - 1; + int src_h = src->h, h_limit = src_h - 1, h_limit_m_1 = h_limit - 1; + + int y_row_odd = y_row & 1; + int y = (y_row / 2) * 2; + uint8_t *rowptr_grgr_0, *rowptr_bgbg_1, *rowptr_grgr_2, *rowptr_bgbg_3; + + // keep row pointers in bounds + if (y == 0) { + rowptr_bgbg_1 = src->data; + rowptr_grgr_2 = rowptr_bgbg_1 + ((src_h >= 2) ? src_w : 0); + rowptr_bgbg_3 = rowptr_bgbg_1 + ((src_h >= 3) ? (src_w * 2) : 0); + rowptr_grgr_0 = rowptr_grgr_2; + } else if (y == h_limit_m_1) { + rowptr_grgr_0 = src->data + ((y - 1) * src_w); + rowptr_bgbg_1 = rowptr_grgr_0 + src_w; + rowptr_grgr_2 = rowptr_bgbg_1 + src_w; + rowptr_bgbg_3 = rowptr_bgbg_1; + } else if (y >= h_limit) { + rowptr_grgr_0 = src->data + ((y - 1) * src_w); + rowptr_bgbg_1 = rowptr_grgr_0 + src_w; + rowptr_grgr_2 = rowptr_grgr_0; + rowptr_bgbg_3 = rowptr_bgbg_1; + } else { // get 4 neighboring rows + rowptr_grgr_0 = src->data + ((y - 1) * src_w); + rowptr_bgbg_1 = rowptr_grgr_0 + src_w; + rowptr_grgr_2 = rowptr_bgbg_1 + src_w; + rowptr_bgbg_3 = rowptr_grgr_2 + src_w; + } + + // If the image is an odd width this will go for the last loop and we drop the last column. + if (!y_row_odd) { // even + for (int x = x_start, i = 0; x < x_end; x += 2, i += 2) { + uint32_t row_grgr_0, row_bgbg_1, row_grgr_2; + + // keep pixels in bounds + if (x == 0) { + if (src_w >= 4) { + row_grgr_0 = *((uint32_t *) rowptr_grgr_0); + row_bgbg_1 = *((uint32_t *) rowptr_bgbg_1); + row_grgr_2 = *((uint32_t *) rowptr_grgr_2); + } else if (src_w >= 3) { + row_grgr_0 = *((uint16_t *) rowptr_grgr_0) | (*(rowptr_grgr_0 + 2) << 16); + row_bgbg_1 = *((uint16_t *) rowptr_bgbg_1) | (*(rowptr_bgbg_1 + 2) << 16); + row_grgr_2 = *((uint16_t *) rowptr_grgr_2) | (*(rowptr_grgr_2 + 2) << 16); + } else if (src_w >= 2) { + row_grgr_0 = *((uint16_t *) rowptr_grgr_0); + row_grgr_0 = (row_grgr_0 << 16) | row_grgr_0; + row_bgbg_1 = *((uint16_t *) rowptr_bgbg_1); + row_bgbg_1 = (row_bgbg_1 << 16) | row_bgbg_1; + row_grgr_2 = *((uint16_t *) rowptr_grgr_2); + row_grgr_2 = (row_grgr_2 << 16) | row_grgr_2; + } else { + row_grgr_0 = *(rowptr_grgr_0) * 0x01010101; + row_bgbg_1 = *(rowptr_bgbg_1) * 0x01010101; + row_grgr_2 = *(rowptr_grgr_2) * 0x01010101; + } + // The starting point needs to be offset by 1. The below patterns are actually + // rgrg, gbgb, rgrg, and gbgb. So, shift left and backfill the missing border pixel. + row_grgr_0 = (row_grgr_0 << 8) | __UXTB_RORn(row_grgr_0, 8); + row_bgbg_1 = (row_bgbg_1 << 8) | __UXTB_RORn(row_bgbg_1, 8); + row_grgr_2 = (row_grgr_2 << 8) | __UXTB_RORn(row_grgr_2, 8); + } else if (x == w_limit_m_1) { + row_grgr_0 = *((uint32_t *) (rowptr_grgr_0 + x - 2)); + row_grgr_0 = (row_grgr_0 >> 8) | ((row_grgr_0 << 8) & 0xff000000); + row_bgbg_1 = *((uint32_t *) (rowptr_bgbg_1 + x - 2)); + row_bgbg_1 = (row_bgbg_1 >> 8) | ((row_bgbg_1 << 8) & 0xff000000); + row_grgr_2 = *((uint32_t *) (rowptr_grgr_2 + x - 2)); + row_grgr_2 = (row_grgr_2 >> 8) | ((row_grgr_2 << 8) & 0xff000000); + } else if (x >= w_limit) { + row_grgr_0 = *((uint16_t *) (rowptr_grgr_0 + x - 1)); + row_grgr_0 = (row_grgr_0 << 16) | row_grgr_0; + row_bgbg_1 = *((uint16_t *) (rowptr_bgbg_1 + x - 1)); + row_bgbg_1 = (row_bgbg_1 << 16) | row_bgbg_1; + row_grgr_2 = *((uint16_t *) (rowptr_grgr_2 + x - 1)); + row_grgr_2 = (row_grgr_2 << 16) | row_grgr_2; + } else { // get 4 neighboring rows + row_grgr_0 = *((uint32_t *) (rowptr_grgr_0 + x - 1)); + row_bgbg_1 = *((uint32_t *) (rowptr_bgbg_1 + x - 1)); + row_grgr_2 = *((uint32_t *) (rowptr_grgr_2 + x - 1)); + } + + int r_pixels_0, g_pixels_0, b_pixels_0; + + switch (src->pixfmt) { + case PIXFORMAT_BAYER_BGGR: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16(__UHADD8(row_02, __PKHTB(row_02, row_02, 16))); + g_pixels_0 = __UXTB16(__UHADD8(row_1g, __PKHTB(row_1g, row_02, 8))); + b_pixels_0 = __UXTB16_RORn(__UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)), 8); + #else + + int r0 = ((row_grgr_0 & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int r2 = (((row_grgr_0 >> 16) & 0xFF) + ((row_grgr_2 >> 16) & 0xFF)) >> 1; + r_pixels_0 = (r2 << 16) | ((r0 + r2) >> 1); + + int g0 = (row_grgr_0 >> 8) & 0xFF; + int g1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 8) & 0xFF; + g_pixels_0 = (row_bgbg_1 & 0xFF0000) | ((((g0 + g2) >> 1) + g1) >> 1); + + int b1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + b_pixels_0 = (b1 << 16) | ((row_bgbg_1 >> 8) & 0xFF); + + #endif + break; + } + case PIXFORMAT_BAYER_GBRG: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16_RORn(__UHADD8(row_02, __PKHBT(row_02, row_02, 16)), 8); + g_pixels_0 = __UXTB16_RORn(__UHADD8(row_1g, __PKHBT(row_1g, row_02, 8)), 8); + b_pixels_0 = __UXTB16(__UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16))); + #else + + int r0 = (((row_grgr_0 >> 8) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int r2 = (((row_grgr_0 >> 24) & 0xFF) + ((row_grgr_2 >> 24) & 0xFF)) >> 1; + r_pixels_0 = r0 | (((r0 + r2) >> 1) << 16); + + int g0 = (row_grgr_0 >> 16) & 0xFF; + int g1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 16) & 0xFF; + g_pixels_0 = ((row_bgbg_1 >> 8) & 0xFF) | (((((g0 + g2) >> 1) + g1) >> 1) << 16); + + int b1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + b_pixels_0 = b1 | (row_bgbg_1 & 0xFF0000); + + #endif + break; + } + case PIXFORMAT_BAYER_GRBG: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16(__UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16))); + g_pixels_0 = __UXTB16_RORn(__UHADD8(row_1g, __PKHBT(row_1g, row_02, 8)), 8); + b_pixels_0 = __UXTB16_RORn(__UHADD8(row_02, __PKHBT(row_02, row_02, 16)), 8); + #else + + int r1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + r_pixels_0 = r1 | (row_bgbg_1 & 0xFF0000); + + int g0 = (row_grgr_0 >> 16) & 0xFF; + int g1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 16) & 0xFF; + g_pixels_0 = ((row_bgbg_1 >> 8) & 0xFF) | (((((g0 + g2) >> 1) + g1) >> 1) << 16); + + int b0 = (((row_grgr_0 >> 8) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int b2 = (((row_grgr_0 >> 24) & 0xFF) + ((row_grgr_2 >> 24) & 0xFF)) >> 1; + b_pixels_0 = b0 | (((b0 + b2) >> 1) << 16); + + #endif + break; + } + case PIXFORMAT_BAYER_RGGB: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16_RORn(__UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)), 8); + g_pixels_0 = __UXTB16(__UHADD8(row_1g, __PKHTB(row_1g, row_02, 8))); + b_pixels_0 = __UXTB16(__UHADD8(row_02, __PKHTB(row_02, row_02, 16))); + #else + + int r1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + r_pixels_0 = (r1 << 16) | ((row_bgbg_1 >> 8) & 0xFF); + + int g0 = (row_grgr_0 >> 8) & 0xFF; + int g1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 8) & 0xFF; + g_pixels_0 = (row_bgbg_1 & 0xFF0000) | ((((g0 + g2) >> 1) + g1) >> 1); + + int b0 = ((row_grgr_0 & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int b2 = (((row_grgr_0 >> 16) & 0xFF) + ((row_grgr_2 >> 16) & 0xFF)) >> 1; + b_pixels_0 = (b2 << 16) | ((b0 + b2) >> 1); + + #endif + break; + } + default: { + r_pixels_0 = 0; + g_pixels_0 = 0; + b_pixels_0 = 0; + break; + } + } + + switch (pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *dst_row_ptr_32 = (uint32_t *) dst_row_ptr; + int y0 = ((r_pixels_0 * 38) + (g_pixels_0 * 75) + (b_pixels_0 * 15)) >> 7; + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr_32, i, (y0 >> 7)); + + if (x != w_limit) { + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr_32, i + 1, (y0 >> 23)); + } + + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *dst_row_ptr_8 = (uint8_t *) dst_row_ptr; + int y0 = ((r_pixels_0 * 38) + (g_pixels_0 * 75) + (b_pixels_0 * 15)) >> 7; + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr_8, i, y0); + + if (x != w_limit) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr_8, i + 1, y0 >> 16); + } + + break; + } + case PIXFORMAT_RGB565: { + uint16_t *dst_row_ptr_16 = (uint16_t *) dst_row_ptr; + int rgb565_0 = ((r_pixels_0 << 8) & 0xf800f800) | + ((g_pixels_0 << 3) & 0x07e007e0) | + ((b_pixels_0 >> 3) & 0x001f001f); + + if (x == w_limit) { // just put bottom + IMAGE_PUT_RGB565_PIXEL_FAST(dst_row_ptr_16, i, rgb565_0); + } else { // put both + *((uint32_t *) (dst_row_ptr_16 + i)) = rgb565_0; + } + + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *dst_row_ptr_24 = (pixel24_t *) dst_row_ptr; + int rgb888_0 = COLOR_R8_G8_B8_TO_RGB888(r_pixels_0, g_pixels_0, b_pixels_0); + + if (x == w_limit) { // just put bottom + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr_24, i, rgb888_0); + } else { // put both + + *((pixel24_t *) (dst_row_ptr_24 + i)) = pixel32224(rgb888_0); + } + + break; + } + default: { + break; + } + } + } + } else { // odd + for (int x = x_start, i = 0; x < x_end; x += 2, i += 2) { + uint32_t row_bgbg_1, row_grgr_2, row_bgbg_3; + + // keep pixels in bounds + if (x == 0) { + if (src_w >= 4) { + row_bgbg_1 = *((uint32_t *) rowptr_bgbg_1); + row_grgr_2 = *((uint32_t *) rowptr_grgr_2); + row_bgbg_3 = *((uint32_t *) rowptr_bgbg_3); + } else if (src_w >= 3) { + row_bgbg_1 = *((uint16_t *) rowptr_bgbg_1) | (*(rowptr_bgbg_1 + 2) << 16); + row_grgr_2 = *((uint16_t *) rowptr_grgr_2) | (*(rowptr_grgr_2 + 2) << 16); + row_bgbg_3 = *((uint16_t *) rowptr_bgbg_3) | (*(rowptr_bgbg_3 + 2) << 16); + } else if (src_w >= 2) { + row_bgbg_1 = *((uint16_t *) rowptr_bgbg_1); + row_bgbg_1 = (row_bgbg_1 << 16) | row_bgbg_1; + row_grgr_2 = *((uint16_t *) rowptr_grgr_2); + row_grgr_2 = (row_grgr_2 << 16) | row_grgr_2; + row_bgbg_3 = *((uint16_t *) rowptr_bgbg_3); + row_bgbg_3 = (row_bgbg_3 << 16) | row_bgbg_3; + } else { + row_bgbg_1 = *(rowptr_bgbg_1) * 0x01010101; + row_grgr_2 = *(rowptr_grgr_2) * 0x01010101; + row_bgbg_3 = *(rowptr_bgbg_3) * 0x01010101; + } + // The starting point needs to be offset by 1. The below patterns are actually + // rgrg, gbgb, rgrg, and gbgb. So, shift left and backfill the missing border pixel. + row_bgbg_1 = (row_bgbg_1 << 8) | __UXTB_RORn(row_bgbg_1, 8); + row_grgr_2 = (row_grgr_2 << 8) | __UXTB_RORn(row_grgr_2, 8); + row_bgbg_3 = (row_bgbg_3 << 8) | __UXTB_RORn(row_bgbg_3, 8); + } else if (x == w_limit_m_1) { + row_bgbg_1 = *((uint32_t *) (rowptr_bgbg_1 + x - 2)); + row_bgbg_1 = (row_bgbg_1 >> 8) | ((row_bgbg_1 << 8) & 0xff000000); + row_grgr_2 = *((uint32_t *) (rowptr_grgr_2 + x - 2)); + row_grgr_2 = (row_grgr_2 >> 8) | ((row_grgr_2 << 8) & 0xff000000); + row_bgbg_3 = *((uint32_t *) (rowptr_bgbg_3 + x - 2)); + row_bgbg_3 = (row_bgbg_3 >> 8) | ((row_bgbg_1 << 8) & 0xff000000); + } else if (x >= w_limit) { + row_bgbg_1 = *((uint16_t *) (rowptr_bgbg_1 + x - 1)); + row_bgbg_1 = (row_bgbg_1 << 16) | row_bgbg_1; + row_grgr_2 = *((uint16_t *) (rowptr_grgr_2 + x - 1)); + row_grgr_2 = (row_grgr_2 << 16) | row_grgr_2; + row_bgbg_3 = *((uint16_t *) (rowptr_bgbg_3 + x - 1)); + row_bgbg_3 = (row_bgbg_3 << 16) | row_bgbg_3; + } else { // get 4 neighboring rows + row_bgbg_1 = *((uint32_t *) (rowptr_bgbg_1 + x - 1)); + row_grgr_2 = *((uint32_t *) (rowptr_grgr_2 + x - 1)); + row_bgbg_3 = *((uint32_t *) (rowptr_bgbg_3 + x - 1)); + } + + int r_pixels_1, g_pixels_1, b_pixels_1; + + switch (src->pixfmt) { + case PIXFORMAT_BAYER_BGGR: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16(__UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16))); + g_pixels_1 = __UXTB16_RORn(__UHADD8(row_2g, __PKHBT(row_2g, row_13, 8)), 8); + b_pixels_1 = __UXTB16_RORn(__UHADD8(row_13, __PKHBT(row_13, row_13, 16)), 8); + #else + + int r2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + r_pixels_1 = (row_grgr_2 & 0xFF0000) | r2; + + int g1 = (row_bgbg_1 >> 16) & 0xFF; + int g2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 16) & 0xFF; + g_pixels_1 = (((((g1 + g3) >> 1) + g2) >> 1) << 16) | ((row_grgr_2 >> 8) & 0xFF); + + int b1 = (((row_bgbg_1 >> 8) & 0xFF) + ((row_bgbg_3 >> 8) & 0xFF)) >> 1; + int b3 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_3 >> 24) & 0xFF)) >> 1; + b_pixels_1 = (((b1 + b3) >> 1) << 16) | b1; + + #endif + break; + } + case PIXFORMAT_BAYER_GBRG: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16_RORn(__UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)), 8); + g_pixels_1 = __UXTB16(__UHADD8(row_2g, __PKHTB(row_2g, row_13, 8))); + b_pixels_1 = __UXTB16(__UHADD8(row_13, __PKHTB(row_13, row_13, 16))); + #else + + int r2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + r_pixels_1 = ((row_grgr_2 >> 8) & 0xFF) | (r2 << 16); + + int g1 = (row_bgbg_1 >> 8) & 0xFF; + int g2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 8) & 0xFF; + g_pixels_1 = ((((g1 + g3) >> 1) + g2) >> 1) | (row_grgr_2 & 0xFF0000); + + int b1 = ((row_bgbg_1 & 0xFF) + (row_bgbg_3 & 0xFF)) >> 1; + int b3 = (((row_bgbg_1 >> 16) & 0xFF) + ((row_bgbg_3 >> 16) & 0xFF)) >> 1; + b_pixels_1 = ((b1 + b3) >> 1) | (b3 << 16); + + #endif + break; + } + case PIXFORMAT_BAYER_GRBG: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16(__UHADD8(row_13, __PKHTB(row_13, row_13, 16))); + g_pixels_1 = __UXTB16(__UHADD8(row_2g, __PKHTB(row_2g, row_13, 8))); + b_pixels_1 = __UXTB16_RORn(__UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)), 8); + #else + + int r1 = ((row_bgbg_1 & 0xFF) + (row_bgbg_3 & 0xFF)) >> 1; + int r3 = (((row_bgbg_1 >> 16) & 0xFF) + ((row_bgbg_3 >> 16) & 0xFF)) >> 1; + r_pixels_1 = ((r1 + r3) >> 1) | (r3 << 16); + + int g1 = (row_bgbg_1 >> 8) & 0xFF; + int g2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 8) & 0xFF; + g_pixels_1 = ((((g1 + g3) >> 1) + g2) >> 1) | (row_grgr_2 & 0xFF0000); + + int b2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + b_pixels_1 = ((row_grgr_2 >> 8) & 0xFF) | (b2 << 16); + + #endif + break; + } + case PIXFORMAT_BAYER_RGGB: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16_RORn(__UHADD8(row_13, __PKHBT(row_13, row_13, 16)), 8); + g_pixels_1 = __UXTB16_RORn(__UHADD8(row_2g, __PKHBT(row_2g, row_13, 8)), 8); + b_pixels_1 = __UXTB16(__UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16))); + #else + + int r1 = (((row_bgbg_1 >> 8) & 0xFF) + ((row_bgbg_3 >> 8) & 0xFF)) >> 1; + int r3 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_3 >> 24) & 0xFF)) >> 1; + r_pixels_1 = (((r1 + r3) >> 1) << 16) | r1; + + int g1 = (row_bgbg_1 >> 16) & 0xFF; + int g2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 16) & 0xFF; + g_pixels_1 = (((((g1 + g3) >> 1) + g2) >> 1) << 16) | ((row_grgr_2 >> 8) & 0xFF); + + int b2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + b_pixels_1 = (row_grgr_2 & 0xFF0000) | b2; + + #endif + break; + } + default: { + r_pixels_1 = 0; + g_pixels_1 = 0; + b_pixels_1 = 0; + break; + } + } + + switch (pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *dst_row_ptr_32 = (uint32_t *) dst_row_ptr; + int y1 = ((r_pixels_1 * 38) + (g_pixels_1 * 75) + (b_pixels_1 * 15)) >> 7; + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr_32, i, (y1 >> 7)); + + if (x != w_limit) { + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr_32, i + 1, (y1 >> 23)); + } + + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *dst_row_ptr_8 = (uint8_t *) dst_row_ptr; + int y1 = ((r_pixels_1 * 38) + (g_pixels_1 * 75) + (b_pixels_1 * 15)) >> 7; + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr_8, i, y1); + + if (x != w_limit) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr_8, i + 1, y1 >> 16); + } + + break; + } + case PIXFORMAT_RGB565: { + uint16_t *dst_row_ptr_16 = (uint16_t *) dst_row_ptr; + int rgb565_1 = ((r_pixels_1 << 8) & 0xf800f800) | + ((g_pixels_1 << 3) & 0x07e007e0) | + ((b_pixels_1 >> 3) & 0x001f001f); + + if (x == w_limit) { // just put bottom + IMAGE_PUT_RGB565_PIXEL_FAST(dst_row_ptr_16, i, rgb565_1); + } else { // put both + *((uint32_t *) (dst_row_ptr_16 + i)) = rgb565_1; + } + + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *dst_row_ptr_24 = (pixel24_t *) dst_row_ptr; + int rgb888_1 = COLOR_R8_G8_B8_TO_RGB888(r_pixels_1, g_pixels_1, b_pixels_1); + + if (x == w_limit) { // just put bottom + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr_24, i, rgb888_1); + } else { // put both + *((pixel24_t *) (dst_row_ptr_24 + i)) = pixel32224(rgb888_1); + } + + break; + } + default: { + break; + } + } + } + } +} + +// Does no bounds checking on the destination. Destination must be mutable. +void imlib_debayer_image(image_t *dst, image_t *src) +{ + int src_w = src->w, w_limit = src_w - 1, w_limit_m_1 = w_limit - 1; + int src_h = src->h, h_limit = src_h - 1, h_limit_m_1 = h_limit - 1; + + // If the image is an odd height this will go for the last loop and we drop the last row. + for (int y = 0; y < src_h; y += 2) { + void *row_ptr_e = NULL, *row_ptr_o = NULL; + + switch (dst->pixfmt) { + case PIXFORMAT_BINARY: { + row_ptr_e = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, y); + row_ptr_o = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, y + 1); + break; + } + case PIXFORMAT_GRAYSCALE: { + row_ptr_e = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(dst, y); + row_ptr_o = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(dst, y + 1); + break; + } + case PIXFORMAT_RGB565: { + row_ptr_e = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, y); + row_ptr_o = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, y + 1); + break; + } + case PIXFORMAT_RGB888: { + row_ptr_e = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(dst, y); + row_ptr_o = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(dst, y + 1); + break; + } + } + + uint8_t *rowptr_grgr_0, *rowptr_bgbg_1, *rowptr_grgr_2, *rowptr_bgbg_3; + + // keep row pointers in bounds + if (y == 0) { + rowptr_bgbg_1 = src->data; + rowptr_grgr_2 = rowptr_bgbg_1 + ((src_h >= 2) ? src_w : 0); + rowptr_bgbg_3 = rowptr_bgbg_1 + ((src_h >= 3) ? (src_w * 2) : 0); + rowptr_grgr_0 = rowptr_grgr_2; + } else if (y == h_limit_m_1) { + rowptr_grgr_0 = src->data + ((y - 1) * src_w); + rowptr_bgbg_1 = rowptr_grgr_0 + src_w; + rowptr_grgr_2 = rowptr_bgbg_1 + src_w; + rowptr_bgbg_3 = rowptr_bgbg_1; + } else if (y >= h_limit) { + rowptr_grgr_0 = src->data + ((y - 1) * src_w); + rowptr_bgbg_1 = rowptr_grgr_0 + src_w; + rowptr_grgr_2 = rowptr_grgr_0; + rowptr_bgbg_3 = rowptr_bgbg_1; + } else { // get 4 neighboring rows + rowptr_grgr_0 = src->data + ((y - 1) * src_w); + rowptr_bgbg_1 = rowptr_grgr_0 + src_w; + rowptr_grgr_2 = rowptr_bgbg_1 + src_w; + rowptr_bgbg_3 = rowptr_grgr_2 + src_w; + } + + // If the image is an odd width this will go for the last loop and we drop the last column. + for (int x = 0; x < src_w; x += 2) { + uint32_t row_grgr_0, row_bgbg_1, row_grgr_2, row_bgbg_3; + + // keep pixels in bounds + if (x == 0) { + if (src_w >= 4) { + row_grgr_0 = *((uint32_t *) rowptr_grgr_0); + row_bgbg_1 = *((uint32_t *) rowptr_bgbg_1); + row_grgr_2 = *((uint32_t *) rowptr_grgr_2); + row_bgbg_3 = *((uint32_t *) rowptr_bgbg_3); + } else if (src_w >= 3) { + row_grgr_0 = *((uint16_t *) rowptr_grgr_0) | (*(rowptr_grgr_0 + 2) << 16); + row_bgbg_1 = *((uint16_t *) rowptr_bgbg_1) | (*(rowptr_bgbg_1 + 2) << 16); + row_grgr_2 = *((uint16_t *) rowptr_grgr_2) | (*(rowptr_grgr_2 + 2) << 16); + row_bgbg_3 = *((uint16_t *) rowptr_bgbg_3) | (*(rowptr_bgbg_3 + 2) << 16); + } else if (src_w >= 2) { + row_grgr_0 = *((uint16_t *) rowptr_grgr_0); + row_grgr_0 = (row_grgr_0 << 16) | row_grgr_0; + row_bgbg_1 = *((uint16_t *) rowptr_bgbg_1); + row_bgbg_1 = (row_bgbg_1 << 16) | row_bgbg_1; + row_grgr_2 = *((uint16_t *) rowptr_grgr_2); + row_grgr_2 = (row_grgr_2 << 16) | row_grgr_2; + row_bgbg_3 = *((uint16_t *) rowptr_bgbg_3); + row_bgbg_3 = (row_bgbg_3 << 16) | row_bgbg_3; + } else { + row_grgr_0 = *(rowptr_grgr_0) * 0x01010101; + row_bgbg_1 = *(rowptr_bgbg_1) * 0x01010101; + row_grgr_2 = *(rowptr_grgr_2) * 0x01010101; + row_bgbg_3 = *(rowptr_bgbg_3) * 0x01010101; + } + // The starting point needs to be offset by 1. The below patterns are actually + // rgrg, gbgb, rgrg, and gbgb. So, shift left and backfill the missing border pixel. + row_grgr_0 = (row_grgr_0 << 8) | __UXTB_RORn(row_grgr_0, 8); + row_bgbg_1 = (row_bgbg_1 << 8) | __UXTB_RORn(row_bgbg_1, 8); + row_grgr_2 = (row_grgr_2 << 8) | __UXTB_RORn(row_grgr_2, 8); + row_bgbg_3 = (row_bgbg_3 << 8) | __UXTB_RORn(row_bgbg_3, 8); + } else if (x == w_limit_m_1) { + row_grgr_0 = *((uint32_t *) (rowptr_grgr_0 + x - 2)); + row_grgr_0 = (row_grgr_0 >> 8) | ((row_grgr_0 << 8) & 0xff000000); + row_bgbg_1 = *((uint32_t *) (rowptr_bgbg_1 + x - 2)); + row_bgbg_1 = (row_bgbg_1 >> 8) | ((row_bgbg_1 << 8) & 0xff000000); + row_grgr_2 = *((uint32_t *) (rowptr_grgr_2 + x - 2)); + row_grgr_2 = (row_grgr_2 >> 8) | ((row_grgr_2 << 8) & 0xff000000); + row_bgbg_3 = *((uint32_t *) (rowptr_bgbg_3 + x - 2)); + row_bgbg_3 = (row_bgbg_3 >> 8) | ((row_bgbg_1 << 8) & 0xff000000); + } else if (x >= w_limit) { + row_grgr_0 = *((uint16_t *) (rowptr_grgr_0 + x - 1)); + row_grgr_0 = (row_grgr_0 << 16) | row_grgr_0; + row_bgbg_1 = *((uint16_t *) (rowptr_bgbg_1 + x - 1)); + row_bgbg_1 = (row_bgbg_1 << 16) | row_bgbg_1; + row_grgr_2 = *((uint16_t *) (rowptr_grgr_2 + x - 1)); + row_grgr_2 = (row_grgr_2 << 16) | row_grgr_2; + row_bgbg_3 = *((uint16_t *) (rowptr_bgbg_3 + x - 1)); + row_bgbg_3 = (row_bgbg_3 << 16) | row_bgbg_3; + } else { // get 4 neighboring rows + row_grgr_0 = *((uint32_t *) (rowptr_grgr_0 + x - 1)); + row_bgbg_1 = *((uint32_t *) (rowptr_bgbg_1 + x - 1)); + row_grgr_2 = *((uint32_t *) (rowptr_grgr_2 + x - 1)); + row_bgbg_3 = *((uint32_t *) (rowptr_bgbg_3 + x - 1)); + } + + int r_pixels_0, g_pixels_0, b_pixels_0; + + switch (src->pixfmt) { + case PIXFORMAT_BAYER_BGGR: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16(__UHADD8(row_02, __PKHTB(row_02, row_02, 16))); + g_pixels_0 = __UXTB16(__UHADD8(row_1g, __PKHTB(row_1g, row_02, 8))); + b_pixels_0 = __UXTB16_RORn(__UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)), 8); + #else + + int r0 = ((row_grgr_0 & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int r2 = (((row_grgr_0 >> 16) & 0xFF) + ((row_grgr_2 >> 16) & 0xFF)) >> 1; + r_pixels_0 = (r2 << 16) | ((r0 + r2) >> 1); + + int g0 = (row_grgr_0 >> 8) & 0xFF; + int g1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 8) & 0xFF; + g_pixels_0 = (row_bgbg_1 & 0xFF0000) | ((((g0 + g2) >> 1) + g1) >> 1); + + int b1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + b_pixels_0 = (b1 << 16) | ((row_bgbg_1 >> 8) & 0xFF); + + #endif + break; + } + case PIXFORMAT_BAYER_GBRG: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16_RORn(__UHADD8(row_02, __PKHBT(row_02, row_02, 16)), 8); + g_pixels_0 = __UXTB16_RORn(__UHADD8(row_1g, __PKHBT(row_1g, row_02, 8)), 8); + b_pixels_0 = __UXTB16(__UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16))); + #else + + int r0 = (((row_grgr_0 >> 8) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int r2 = (((row_grgr_0 >> 24) & 0xFF) + ((row_grgr_2 >> 24) & 0xFF)) >> 1; + r_pixels_0 = r0 | (((r0 + r2) >> 1) << 16); + + int g0 = (row_grgr_0 >> 16) & 0xFF; + int g1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 16) & 0xFF; + g_pixels_0 = ((row_bgbg_1 >> 8) & 0xFF) | (((((g0 + g2) >> 1) + g1) >> 1) << 16); + + int b1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + b_pixels_0 = b1 | (row_bgbg_1 & 0xFF0000); + + #endif + break; + } + case PIXFORMAT_BAYER_GRBG: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16(__UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16))); + g_pixels_0 = __UXTB16_RORn(__UHADD8(row_1g, __PKHBT(row_1g, row_02, 8)), 8); + b_pixels_0 = __UXTB16_RORn(__UHADD8(row_02, __PKHBT(row_02, row_02, 16)), 8); + #else + + int r1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + r_pixels_0 = r1 | (row_bgbg_1 & 0xFF0000); + + int g0 = (row_grgr_0 >> 16) & 0xFF; + int g1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 16) & 0xFF; + g_pixels_0 = ((row_bgbg_1 >> 8) & 0xFF) | (((((g0 + g2) >> 1) + g1) >> 1) << 16); + + int b0 = (((row_grgr_0 >> 8) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int b2 = (((row_grgr_0 >> 24) & 0xFF) + ((row_grgr_2 >> 24) & 0xFF)) >> 1; + b_pixels_0 = b0 | (((b0 + b2) >> 1) << 16); + + #endif + break; + } + case PIXFORMAT_BAYER_RGGB: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16_RORn(__UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)), 8); + g_pixels_0 = __UXTB16(__UHADD8(row_1g, __PKHTB(row_1g, row_02, 8))); + b_pixels_0 = __UXTB16(__UHADD8(row_02, __PKHTB(row_02, row_02, 16))); + #else + + int r1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + r_pixels_0 = (r1 << 16) | ((row_bgbg_1 >> 8) & 0xFF); + + int g0 = (row_grgr_0 >> 8) & 0xFF; + int g1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 8) & 0xFF; + g_pixels_0 = (row_bgbg_1 & 0xFF0000) | ((((g0 + g2) >> 1) + g1) >> 1); + + int b0 = ((row_grgr_0 & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int b2 = (((row_grgr_0 >> 16) & 0xFF) + ((row_grgr_2 >> 16) & 0xFF)) >> 1; + b_pixels_0 = (b2 << 16) | ((b0 + b2) >> 1); + + #endif + break; + } + default: { + r_pixels_0 = 0; + g_pixels_0 = 0; + b_pixels_0 = 0; + break; + } + } + + switch (dst->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *row_ptr_e_32 = (uint32_t *) row_ptr_e; + int y0 = ((r_pixels_0 * 38) + (g_pixels_0 * 75) + (b_pixels_0 * 15)) >> 7; + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr_e_32, x, (y0 >> 7)); + + if (x != w_limit) { + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr_e_32, x + 1, (y0 >> 23)); + } + + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *row_ptr_e_8 = (uint8_t *) row_ptr_e; + int y0 = ((r_pixels_0 * 38) + (g_pixels_0 * 75) + (b_pixels_0 * 15)) >> 7; + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr_e_8, x, y0); + + if (x != w_limit) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr_e_8, x + 1, y0 >> 16); + } + + break; + } + case PIXFORMAT_RGB565: { + uint16_t *row_ptr_e_16 = (uint16_t *) row_ptr_e; + int rgb565_0 = ((r_pixels_0 << 8) & 0xf800f800) | + ((g_pixels_0 << 3) & 0x07e007e0) | + ((b_pixels_0 >> 3) & 0x001f001f); + + if (x == w_limit) { // just put bottom + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr_e_16, x, rgb565_0); + } else { // put both + *((uint32_t *) (row_ptr_e_16 + x)) = rgb565_0; + } + + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *row_ptr_e_24 = (pixel24_t *) row_ptr_e; + int rgb888_0 = COLOR_R8_G8_B8_TO_RGB888(r_pixels_0, g_pixels_0, b_pixels_0); + + if (x == w_limit) { // just put bottom + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr_e_24, x, rgb888_0); + } else { // put both + *((pixel24_t *) (row_ptr_e_24 + x)) = pixel32224(rgb888_0); + } + + break; + } + } + + if (y == h_limit) { + continue; + } + + int r_pixels_1, g_pixels_1, b_pixels_1; + + switch (src->pixfmt) { + case PIXFORMAT_BAYER_BGGR: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16(__UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16))); + g_pixels_1 = __UXTB16_RORn(__UHADD8(row_2g, __PKHBT(row_2g, row_13, 8)), 8); + b_pixels_1 = __UXTB16_RORn(__UHADD8(row_13, __PKHBT(row_13, row_13, 16)), 8); + #else + + int r2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + r_pixels_1 = (row_grgr_2 & 0xFF0000) | r2; + + int g1 = (row_bgbg_1 >> 16) & 0xFF; + int g2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 16) & 0xFF; + g_pixels_1 = (((((g1 + g3) >> 1) + g2) >> 1) << 16) | ((row_grgr_2 >> 8) & 0xFF); + + int b1 = (((row_bgbg_1 >> 8) & 0xFF) + ((row_bgbg_3 >> 8) & 0xFF)) >> 1; + int b3 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_3 >> 24) & 0xFF)) >> 1; + b_pixels_1 = (((b1 + b3) >> 1) << 16) | b1; + + #endif + break; + } + case PIXFORMAT_BAYER_GBRG: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16_RORn(__UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)), 8); + g_pixels_1 = __UXTB16(__UHADD8(row_2g, __PKHTB(row_2g, row_13, 8))); + b_pixels_1 = __UXTB16(__UHADD8(row_13, __PKHTB(row_13, row_13, 16))); + #else + + int r2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + r_pixels_1 = ((row_grgr_2 >> 8) & 0xFF) | (r2 << 16); + + int g1 = (row_bgbg_1 >> 8) & 0xFF; + int g2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 8) & 0xFF; + g_pixels_1 = ((((g1 + g3) >> 1) + g2) >> 1) | (row_grgr_2 & 0xFF0000); + + int b1 = ((row_bgbg_1 & 0xFF) + (row_bgbg_3 & 0xFF)) >> 1; + int b3 = (((row_bgbg_1 >> 16) & 0xFF) + ((row_bgbg_3 >> 16) & 0xFF)) >> 1; + b_pixels_1 = ((b1 + b3) >> 1) | (b3 << 16); + + #endif + break; + } + case PIXFORMAT_BAYER_GRBG: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16(__UHADD8(row_13, __PKHTB(row_13, row_13, 16))); + g_pixels_1 = __UXTB16(__UHADD8(row_2g, __PKHTB(row_2g, row_13, 8))); + b_pixels_1 = __UXTB16_RORn(__UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)), 8); + #else + + int r1 = ((row_bgbg_1 & 0xFF) + (row_bgbg_3 & 0xFF)) >> 1; + int r3 = (((row_bgbg_1 >> 16) & 0xFF) + ((row_bgbg_3 >> 16) & 0xFF)) >> 1; + r_pixels_1 = ((r1 + r3) >> 1) | (r3 << 16); + + int g1 = (row_bgbg_1 >> 8) & 0xFF; + int g2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 8) & 0xFF; + g_pixels_1 = ((((g1 + g3) >> 1) + g2) >> 1) | (row_grgr_2 & 0xFF0000); + + int b2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + b_pixels_1 = ((row_grgr_2 >> 8) & 0xFF) | (b2 << 16); + + #endif + break; + } + case PIXFORMAT_BAYER_RGGB: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16_RORn(__UHADD8(row_13, __PKHBT(row_13, row_13, 16)), 8); + g_pixels_1 = __UXTB16_RORn(__UHADD8(row_2g, __PKHBT(row_2g, row_13, 8)), 8); + b_pixels_1 = __UXTB16(__UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16))); + #else + + int r1 = (((row_bgbg_1 >> 8) & 0xFF) + ((row_bgbg_3 >> 8) & 0xFF)) >> 1; + int r3 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_3 >> 24) & 0xFF)) >> 1; + r_pixels_1 = (((r1 + r3) >> 1) << 16) | r1; + + int g1 = (row_bgbg_1 >> 16) & 0xFF; + int g2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 16) & 0xFF; + g_pixels_1 = (((((g1 + g3) >> 1) + g2) >> 1) << 16) | ((row_grgr_2 >> 8) & 0xFF); + + int b2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + b_pixels_1 = (row_grgr_2 & 0xFF0000) | b2; + + #endif + break; + } + default: { + r_pixels_1 = 0; + g_pixels_1 = 0; + b_pixels_1 = 0; + break; + } + } + + switch (dst->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *row_ptr_o_32 = (uint32_t *) row_ptr_o; + int y1 = ((r_pixels_1 * 38) + (g_pixels_1 * 75) + (b_pixels_1 * 15)) >> 7; + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr_o_32, x, (y1 >> 7)); + + if (x != w_limit) { + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr_o_32, x + 1, (y1 >> 23)); + } + + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *row_ptr_o_8 = (uint8_t *) row_ptr_o; + int y1 = ((r_pixels_1 * 38) + (g_pixels_1 * 75) + (b_pixels_1 * 15)) >> 7; + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr_o_8, x, y1); + + if (x != w_limit) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr_o_8, x + 1, y1 >> 16); + } + + break; + } + case PIXFORMAT_RGB565: { + uint16_t *row_ptr_o_16 = (uint16_t *) row_ptr_o; + int rgb565_1 = ((r_pixels_1 << 8) & 0xf800f800) | + ((g_pixels_1 << 3) & 0x07e007e0) | + ((b_pixels_1 >> 3) & 0x001f001f); + + if (x == w_limit) { // just put bottom + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr_o_16, x, rgb565_1); + } else { // put both + *((uint32_t *) (row_ptr_o_16 + x)) = rgb565_1; + } + + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *row_ptr_o_24 = (pixel24_t *) row_ptr_o; + int rgb888_1 = COLOR_R8_G8_B8_TO_RGB888(r_pixels_1, g_pixels_1, b_pixels_1); + + if (x == w_limit) { // just put bottom + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr_o_24, x, rgb888_1); + } else { // put both + *((pixel24_t *) (row_ptr_o_24 + x)) = pixel32224(rgb888_1); + } + + break; + } + } + } + } +} diff --git a/github_source/minicv2/src/binary.c b/github_source/minicv2/src/binary.c new file mode 100644 index 0000000..d6e7f99 --- /dev/null +++ b/github_source/minicv2/src/binary.c @@ -0,0 +1,1179 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Binary image operations. + */ +#include "imlib.h" + +#ifdef IMLIB_ENABLE_BINARY_OPS +void imlib_binary(image_t *out, image_t *img, list_t *thresholds, bool invert, bool zero, image_t *mask) +{ + image_t bmp; + bmp.w = img->w; + bmp.h = img->h; + bmp.pixfmt = PIXFORMAT_BINARY; + bmp.data = fb_alloc0(image_size(&bmp), FB_ALLOC_NO_HINT); + + for (list_lnk_t *it = iterator_start_from_head(thresholds); it; it = iterator_next(it)) { + color_thresholds_list_lnk_data_t lnk_data; + iterator_get(thresholds, it, &lnk_data); + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *old_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(old_row_ptr, x), &lnk_data, invert)) { + IMAGE_SET_BINARY_PIXEL_FAST(bmp_row_ptr, x); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *old_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(old_row_ptr, x), &lnk_data, invert)) { + IMAGE_SET_BINARY_PIXEL_FAST(bmp_row_ptr, x); + } + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *old_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(old_row_ptr, x), &lnk_data, invert)) { + IMAGE_SET_BINARY_PIXEL_FAST(bmp_row_ptr, x); + } + } + } + break; + } + case PIXFORMAT_RGB888: { + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *old_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (COLOR_THRESHOLD_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(old_row_ptr, x), &lnk_data, invert)) { + IMAGE_SET_BINARY_PIXEL_FAST(bmp_row_ptr, x); + } + } + } + break; + } + default: { + break; + } + } + } + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + if (!zero) { + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *old_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = ((!mask) || image_get_mask_pixel(mask, x, y)) + ? IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x) + : IMAGE_GET_BINARY_PIXEL_FAST(old_row_ptr, x); + IMAGE_PUT_BINARY_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } else { + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *old_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(old_row_ptr, x); + if (((!mask) || image_get_mask_pixel(mask, x, y)) + && IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) pixel = 0; + IMAGE_PUT_BINARY_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + if (out->pixfmt == PIXFORMAT_BINARY) { + if (!zero) { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *old_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = ((!mask) || image_get_mask_pixel(mask, x, y)) + ? IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x) + : COLOR_GRAYSCALE_TO_BINARY(IMAGE_GET_GRAYSCALE_PIXEL_FAST(old_row_ptr, x)); + IMAGE_PUT_BINARY_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } else { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *old_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = COLOR_GRAYSCALE_TO_BINARY(IMAGE_GET_GRAYSCALE_PIXEL_FAST(old_row_ptr, x)); + if (((!mask) || image_get_mask_pixel(mask, x, y)) + && IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) pixel = 0; + IMAGE_PUT_BINARY_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } + } else { + if (!zero) { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *old_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + uint8_t *out_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = ((!mask) || image_get_mask_pixel(mask, x, y)) + ? COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) + : IMAGE_GET_GRAYSCALE_PIXEL_FAST(old_row_ptr, x); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } else { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *old_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + uint8_t *out_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(old_row_ptr, x); + if (((!mask) || image_get_mask_pixel(mask, x, y)) + && IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) pixel = 0; + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } + } + break; + } + case PIXFORMAT_RGB565: { + if (out->pixfmt == PIXFORMAT_BINARY) { + if (!zero) { + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *old_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = ((!mask) || image_get_mask_pixel(mask, x, y)) + ? IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x) + : COLOR_RGB565_TO_BINARY(IMAGE_GET_RGB565_PIXEL_FAST(old_row_ptr, x)); + IMAGE_PUT_BINARY_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } else { + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *old_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = COLOR_RGB565_TO_BINARY(IMAGE_GET_RGB565_PIXEL_FAST(old_row_ptr, x)); + if (((!mask) || image_get_mask_pixel(mask, x, y)) + && IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) pixel = 0; + IMAGE_PUT_BINARY_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } + } else { + if (!zero) { + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *old_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + uint16_t *out_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = ((!mask) || image_get_mask_pixel(mask, x, y)) + ? COLOR_BINARY_TO_RGB565(IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) + : IMAGE_GET_RGB565_PIXEL_FAST(old_row_ptr, x); + IMAGE_PUT_RGB565_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } else { + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *old_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + uint16_t *out_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(old_row_ptr, x); + if (((!mask) || image_get_mask_pixel(mask, x, y)) + && IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) pixel = 0; + IMAGE_PUT_RGB565_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } + } + break; + } + case PIXFORMAT_RGB888: { + if (out->pixfmt == PIXFORMAT_BINARY) { + if (!zero) { + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *old_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = ((!mask) || image_get_mask_pixel(mask, x, y)) + ? IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x) + : COLOR_RGB888_TO_BINARY(IMAGE_GET_RGB888_PIXEL_FAST(old_row_ptr, x)); + IMAGE_PUT_BINARY_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } else { + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *old_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = COLOR_RGB888_TO_BINARY(IMAGE_GET_RGB888_PIXEL_FAST(old_row_ptr, x)); + if (((!mask) || image_get_mask_pixel(mask, x, y)) + && IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) pixel = 0; + IMAGE_PUT_BINARY_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } + } else { + if (!zero) { + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *old_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + pixel24_t *out_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = ((!mask) || image_get_mask_pixel(mask, x, y)) + ? COLOR_BINARY_TO_RGB888(IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) + : IMAGE_GET_RGB888_PIXEL_FAST(old_row_ptr, x); + IMAGE_PUT_RGB888_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } else { + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *old_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + pixel24_t *out_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(out, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(old_row_ptr, x); + if (((!mask) || image_get_mask_pixel(mask, x, y)) + && IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) pixel = 0; + IMAGE_PUT_RGB888_PIXEL_FAST(out_row_ptr, x, pixel); + } + } + } + } + break; + } + default: { + break; + } + } + + fb_free(bmp.data); +} + +void imlib_invert(image_t *img) +{ + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + for (uint32_t *start = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, 0), + *end = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, img->h); + start < end; start++) { + *start = ~*start; + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (uint8_t *start = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, 0), + *end = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, img->h); + start < end; start++) { + *start = ~*start; + } + break; + } + case PIXFORMAT_RGB565: { + for (uint16_t *start = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, 0), + *end = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, img->h); + start < end; start++) { + *start = ~*start; + } + break; + } + case PIXFORMAT_RGB888: { + for (pixel24_t *start = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, 0), + *end = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, img->h); + start < end; start++) { + *start = pixel32224((~ pixel24232(*start))); + } + break; + } + default: { + break; + } + } +} + +static void imlib_b_and_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + image_t *mask = (image_t *) data; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_BINARY_LINE_LEN(img); i < j; i++) { + data[i] &= ((uint32_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, + (IMAGE_GET_BINARY_PIXEL_FAST(data, i) + & IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_GRAYSCALE_LINE_LEN(img); i < j; i++) { + data[i] &= ((uint8_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, + (IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i) + & IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_RGB565_LINE_LEN(img); i < j; i++) { + data[i] &= ((uint16_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, + (IMAGE_GET_RGB565_PIXEL_FAST(data, i) + & IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_RGB888_LINE_LEN(img); i < j; i++) { + data[i] = pixel32224(pixel24232(((pixel24_t *) other)[i]) & pixel24232(data[i])); + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, + (IMAGE_GET_RGB888_PIXEL_FAST(data, i) + & IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i))); + } + } + } + break; + } + default: { + break; + } + } +} + +void imlib_b_and(image_t *img, const char *path, image_t *other, int scalar, image_t *mask) +{ + imlib_image_operation(img, path, other, scalar, imlib_b_and_line_op, mask); +} + +static void imlib_b_nand_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + image_t *mask = (image_t *) data; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_BINARY_LINE_LEN(img); i < j; i++) { + data[i] &= ~((uint32_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, + (IMAGE_GET_BINARY_PIXEL_FAST(data, i) + & ~IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_GRAYSCALE_LINE_LEN(img); i < j; i++) { + data[i] &= ~((uint8_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, + (IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i) + & ~IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_RGB565_LINE_LEN(img); i < j; i++) { + data[i] &= ~((uint16_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, + (IMAGE_GET_RGB565_PIXEL_FAST(data, i) + & ~IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_RGB888_LINE_LEN(img); i < j; i++) { + data[i] = pixel32224((~ pixel24232(((pixel24_t *) other)[i])) & pixel24232(data[i])); + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, + (IMAGE_GET_RGB888_PIXEL_FAST(data, i) + & ~IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i))); + } + } + } + break; + } + default: { + break; + } + } +} + +void imlib_b_nand(image_t *img, const char *path, image_t *other, int scalar, image_t *mask) +{ + imlib_image_operation(img, path, other, scalar, imlib_b_nand_line_op, mask); +} + +static void imlib_b_or_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + image_t *mask = (image_t *) data; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_BINARY_LINE_LEN(img); i < j; i++) { + data[i] |= ((uint32_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, + (IMAGE_GET_BINARY_PIXEL_FAST(data, i) + | IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_GRAYSCALE_LINE_LEN(img); i < j; i++) { + data[i] |= ((uint8_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, + (IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i) + | IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_RGB565_LINE_LEN(img); i < j; i++) { + data[i] |= ((uint16_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, + (IMAGE_GET_RGB565_PIXEL_FAST(data, i) + | IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_RGB565_LINE_LEN(img); i < j; i++) { + data[i] = pixel32224(pixel24232(((pixel24_t *) other)[i]) | pixel24232(data[i])); + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, + (IMAGE_GET_RGB888_PIXEL_FAST(data, i) + | IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i))); + } + } + } + break; + } + default: { + break; + } + } +} + +void imlib_b_or(image_t *img, const char *path, image_t *other, int scalar, image_t *mask) +{ + imlib_image_operation(img, path, other, scalar, imlib_b_or_line_op, mask); +} + +static void imlib_b_nor_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + image_t *mask = (image_t *) data; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_BINARY_LINE_LEN(img); i < j; i++) { + data[i] |= ~((uint32_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, + (IMAGE_GET_BINARY_PIXEL_FAST(data, i) + | ~IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_GRAYSCALE_LINE_LEN(img); i < j; i++) { + data[i] |= ~((uint8_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, + (IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i) + | ~IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_RGB565_LINE_LEN(img); i < j; i++) { + data[i] |= ~((uint16_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, + (IMAGE_GET_RGB565_PIXEL_FAST(data, i) + | ~IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_RGB565_LINE_LEN(img); i < j; i++) { + data[i] = pixel32224(((~pixel24232(((pixel24_t *) other)[i])) | pixel24232(data[i]))); + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, + (IMAGE_GET_RGB888_PIXEL_FAST(data, i) + | ~IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i))); + } + } + } + break; + } + default: { + break; + } + } +} + +void imlib_b_nor(image_t *img, const char *path, image_t *other, int scalar, image_t *mask) +{ + imlib_image_operation(img, path, other, scalar,imlib_b_nor_line_op, mask); +} + +static void imlib_b_xor_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + image_t *mask = (image_t *) data; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_BINARY_LINE_LEN(img); i < j; i++) { + data[i] ^= ((uint32_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, + (IMAGE_GET_BINARY_PIXEL_FAST(data, i) + ^ IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_GRAYSCALE_LINE_LEN(img); i < j; i++) { + data[i] ^= ((uint8_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, + (IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i) + ^ IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_RGB565_LINE_LEN(img); i < j; i++) { + data[i] ^= ((uint16_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, + (IMAGE_GET_RGB565_PIXEL_FAST(data, i) + ^ IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_RGB888_LINE_LEN(img); i < j; i++) { + data[i] = pixel32224((pixel24232(data[i]) ^ pixel24232(((pixel24_t *) other)[i]))); + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, + (IMAGE_GET_RGB888_PIXEL_FAST(data, i) + ^ IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i))); + } + } + } + break; + } + default: { + break; + } + } +} + +void imlib_b_xor(image_t *img, const char *path, image_t *other, int scalar, image_t *mask) +{ + imlib_image_operation(img, path, other, scalar, imlib_b_xor_line_op, mask); +} + +static void imlib_b_xnor_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + image_t *mask = (image_t *) data; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_BINARY_LINE_LEN(img); i < j; i++) { + data[i] ^= ~((uint32_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, + (IMAGE_GET_BINARY_PIXEL_FAST(data, i) + ^ ~IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_GRAYSCALE_LINE_LEN(img); i < j; i++) { + data[i] ^= ~((uint8_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, + (IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i) + ^ ~IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_RGB565_LINE_LEN(img); i < j; i++) { + data[i] ^= ~((uint16_t *) other)[i]; + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, + (IMAGE_GET_RGB565_PIXEL_FAST(data, i) + ^ ~IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i))); + } + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + + if(!mask) { + for (int i = 0, j = IMAGE_RGB888_LINE_LEN(img); i < j; i++) { + data[i] = pixel32224((pixel24232(data[i]) ^ (~pixel24232(((pixel24_t *) other)[i])))); + } + } else { + for (int i = 0, j = img->w; i < j; i++) { + if (image_get_mask_pixel(mask, i, line)) { + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, + (IMAGE_GET_RGB888_PIXEL_FAST(data, i) + ^ ~IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i))); + } + } + } + break; + } + default: { + break; + } + } +} + +void imlib_b_xnor(image_t *img, const char *path, image_t *other, int scalar, image_t *mask) +{ + imlib_image_operation(img, path, other, scalar, imlib_b_xnor_line_op, mask); +} + +static void imlib_erode_dilate(image_t *img, int ksize, int threshold, int e_or_d, image_t *mask) +{ + int brows = ksize + 1; + image_t buf; + buf.w = img->w; + buf.h = brows; + buf.pixfmt = img->pixfmt; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + int acc = 0; + + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + + if (mask && (!image_get_mask_pixel(mask, x, y))) { + continue; // Short circuit. + } + if (x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { // faster + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img,y+j); + // subtract old left column and add new right column + acc -= IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr,x-ksize-1); + acc += IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr,x+ksize); + } + } else { // slower (checks boundaries per pixel) + acc = e_or_d ? 0 : -1; // Don't count center pixel... + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + acc += IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + } + } + } + + if (!e_or_d) { + // Preserve original pixel value... or clear it. + if (acc < threshold) IMAGE_CLEAR_BINARY_PIXEL_FAST(buf_row_ptr, x); + } else { + // Preserve original pixel value... or set it. + if (acc > threshold) IMAGE_SET_BINARY_PIXEL_FAST(buf_row_ptr, x); + } + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + case PIXFORMAT_GRAYSCALE: { + buf.data = fb_alloc(IMAGE_GRAYSCALE_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint8_t *buf_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)); + int acc = 0; + + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, pixel); + + if (mask && (!image_get_mask_pixel(mask, x, y))) { + continue; // Short circuit. + } + + if (x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { // faster + for (int j = -ksize; j <= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img,y+j); + // subtract old left edge and add new right edge to sum + acc -= (IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr,x-ksize-1) > 0); + acc += (IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr,x+ksize) > 0); + } // for j + } else { // slower way which checks boundaries per pixel + acc = e_or_d ? 0 : -1; // Don't count center pixel... + for (int j = -ksize; j <= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + acc += (IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1)))) > 0; + } // for k + } // for j + } + + if (!e_or_d) { + // Preserve original pixel value... or clear it. + if (acc < threshold) IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, + COLOR_GRAYSCALE_BINARY_MIN); + } else { + // Preserve original pixel value... or set it. + if (acc > threshold) IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, + COLOR_GRAYSCALE_BINARY_MAX); + } + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + case PIXFORMAT_RGB565: { + buf.data = fb_alloc(IMAGE_RGB565_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint16_t *buf_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)); + int acc = 0; + + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, pixel); + + if (mask && (!image_get_mask_pixel(mask, x, y))) { + continue; // Short circuit. + } + + if (x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { // faster + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img,y+j); + // subtract old left column and add new right column + acc -= IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr,x-ksize-1) > 0; + acc += IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr,x+ksize) > 0; + } + } else { // need to check boundary conditions for each pixel + acc = e_or_d ? 0 : -1; // Don't count center pixel... + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + acc += (IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1)))) > 0; + } + } + } + + if (!e_or_d) { + // Preserve original pixel value... or clear it. + if (acc < threshold) IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, + COLOR_RGB565_BINARY_MIN); + } else { + // Preserve original pixel value... or set it. + if (acc > threshold) IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, + COLOR_RGB565_BINARY_MAX); + } + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + case PIXFORMAT_RGB888: { + buf.data = fb_alloc(IMAGE_RGB888_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel24_t *buf_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)); + int acc = 0; + + for (int x = 0, xx = img->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, pixel); + + if (mask && (!image_get_mask_pixel(mask, x, y))) { + continue; // Short circuit. + } + + if (x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { // faster + for (int j = -ksize; j <= ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img,y+j); + // subtract old left column and add new right column + acc -= IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr,x-ksize-1) > 0; + acc += IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr,x+ksize) > 0; + } + } else { // need to check boundary conditions for each pixel + acc = e_or_d ? 0 : -1; // Don't count center pixel... + for (int j = -ksize; j <= ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + acc += (IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1)))) > 0; + } + } + } + + if (!e_or_d) { + // Preserve original pixel value... or clear it. + if (acc < threshold) IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, + COLOR_RGB888_BINARY_MIN); + } else { + // Preserve original pixel value... or set it. + if (acc > threshold) IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, + COLOR_RGB888_BINARY_MAX); + } + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + default: { + break; + } + } +} + +void imlib_erode(image_t *img, int ksize, int threshold, image_t *mask) +{ + // Threshold should be equal to (((ksize*2)+1)*((ksize*2)+1))-1 + // for normal operation. E.g. for ksize==3 -> threshold==8 + // Basically you're adjusting the number of data that + // must be set in the kernel (besides the center) for the output to be 1. + // Erode normally requires all data to be 1. + imlib_erode_dilate(img, ksize, threshold, 0, mask); +} + +void imlib_dilate(image_t *img, int ksize, int threshold, image_t *mask) +{ + // Threshold should be equal to 0 + // for normal operation. E.g. for ksize==3 -> threshold==0 + // Basically you're adjusting the number of data that + // must be set in the kernel (besides the center) for the output to be 1. + // Dilate normally requires one pixel to be 1. + imlib_erode_dilate(img, ksize, threshold, 1, mask); +} + +void imlib_open(image_t *img, int ksize, int threshold, image_t *mask) +{ + imlib_erode(img, ksize, (((ksize*2)+1)*((ksize*2)+1))-1 - threshold, mask); + imlib_dilate(img, ksize, 0 + threshold, mask); +} + +void imlib_close(image_t *img, int ksize, int threshold, image_t *mask) +{ + imlib_dilate(img, ksize, 0 + threshold, mask); + imlib_erode(img, ksize, (((ksize*2)+1)*((ksize*2)+1))-1 - threshold, mask); +} + +void imlib_top_hat(image_t *img, int ksize, int threshold, image_t *mask) +{ + image_t temp; + temp.w = img->w; + temp.h = img->h; + temp.pixfmt = img->pixfmt; + temp.data = fb_alloc(image_size(img), FB_ALLOC_NO_HINT); + memcpy(temp.data, img->data, image_size(img)); + imlib_open(&temp, ksize, threshold, mask); + imlib_difference(img, NULL, &temp, 0, mask); + fb_free(temp.data); +} + +void imlib_black_hat(image_t *img, int ksize, int threshold, image_t *mask) +{ + image_t temp; + temp.w = img->w; + temp.h = img->h; + temp.pixfmt = img->pixfmt; + temp.data = fb_alloc(image_size(img), FB_ALLOC_NO_HINT); + memcpy(temp.data, img->data, image_size(img)); + imlib_close(&temp, ksize, threshold, mask); + imlib_difference(img, NULL, &temp, 0, mask); + fb_free(temp.data); +} +#endif diff --git a/github_source/minicv2/src/blob.c b/github_source/minicv2/src/blob.c new file mode 100644 index 0000000..f856824 --- /dev/null +++ b/github_source/minicv2/src/blob.c @@ -0,0 +1,1810 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Blob detection code. + */ +#include "imlib.h" + +typedef struct xylr { + int16_t x, y, l, r, t_l, b_l; +} +xylr_t; + +static float sign(float x) +{ + return x / fabsf(x); +} + +static int sum_m_to_n(int m, int n) +{ + return ((n * (n + 1)) - (m * (m - 1))) / 2; +} + +static int sum_2_m_to_n(int m, int n) +{ + return ((n * (n + 1) * ((2 * n) + 1)) - (m * (m - 1) * ((2 * m) - 1))) / 6; +} + +static int cumulative_moving_average(int avg, int x, int n) +{ + return (x + (n * avg)) / (n + 1); +} + +static void bin_up(uint16_t *hist, uint16_t size, unsigned int max_size, uint16_t **new_hist, uint16_t *new_size) +{ + int start = -1; + + for (int i = 0; i < size; i++) { + if (hist[i]) { + start = i; + break; + } + } + + if (start != -1) { + int end = start; + + for (int i = start + 1; i < size; i++) { + if (!hist[i]) { + break; + } + end = i; + } + + int bin_count = end - start + 1; // >= 1 + *new_size = IM_MIN(max_size, bin_count); + *new_hist = xalloc0((*new_size) * sizeof(uint16_t)); + float div_value = (*new_size) / ((float) bin_count); // Reversed so we can multiply below. + + for (int i = 0; i < bin_count; i++) { + (*new_hist)[fast_floorf(i*div_value)] += hist[start+i]; + } + } +} + +static void merge_bins(int b_dst_start, int b_dst_end, uint16_t **b_dst_hist, uint16_t *b_dst_hist_len, + int b_src_start, int b_src_end, uint16_t **b_src_hist, uint16_t *b_src_hist_len, + unsigned int max_size) +{ + int start = IM_MIN(b_dst_start, b_src_start); + int end = IM_MAX(b_dst_end, b_src_end); + + int bin_count = end - start + 1; // >= 1 + uint16_t new_size = IM_MIN(max_size, bin_count); + uint16_t *new_hist = xalloc0(new_size * sizeof(uint16_t)); + float div_value = new_size / ((float) bin_count); // Reversed so we can multiply below. + + int b_dst_bin_count = b_dst_end - b_dst_start + 1; // >= 1 + uint16_t b_dst_new_size = IM_MIN((*b_dst_hist_len), b_dst_bin_count); + float b_dst_div_value = b_dst_new_size / ((float) b_dst_bin_count); // Reversed so we can multiply below. + + int b_src_bin_count = b_src_end - b_src_start + 1; // >= 1 + uint16_t b_src_new_size = IM_MIN((*b_src_hist_len), b_src_bin_count); + float b_src_div_value = b_src_new_size / ((float) b_src_bin_count); // Reversed so we can multiply below. + + for(int i = 0; i < bin_count; i++) { + if ((b_dst_start <= (i + start)) && ((i + start) <= b_dst_end)) { + int index = fast_floorf((i + start - b_dst_start) * b_dst_div_value); + new_hist[fast_floorf(i*div_value)] += (*b_dst_hist)[index]; + (*b_dst_hist)[index] = 0; // prevent from adding again... + } + if ((b_src_start <= (i + start)) && ((i + start) <= b_src_end)) { + int index = fast_floorf((i + start - b_src_start) * b_src_div_value); + new_hist[fast_floorf(i*div_value)] += (*b_src_hist)[index]; + (*b_src_hist)[index] = 0; // prevent from adding again... + } + } + + xfree(*b_dst_hist); + xfree(*b_src_hist); + + *b_dst_hist_len = new_size; + (*b_dst_hist) = new_hist; + *b_src_hist_len = 0; + (*b_src_hist) = NULL; +} + +static float calc_roundness(float blob_a, float blob_b, float blob_c) +{ + float roundness_div = fast_sqrtf((blob_b * blob_b) + ((blob_a - blob_c) * (blob_a - blob_c))); + float roundness_sin = IM_DIV(blob_b, roundness_div); + float roundness_cos = IM_DIV(blob_a - blob_c, roundness_div); + float roundness_add = (blob_a + blob_c) / 2; + float roundness_cos_mul = (blob_a - blob_c) / 2; + float roundness_sin_mul = blob_b / 2; + + float roundness_0 = roundness_add + (roundness_cos * roundness_cos_mul) + (roundness_sin * roundness_sin_mul); + float roundness_1 = roundness_add + (roundness_cos * roundness_cos_mul) - (roundness_sin * roundness_sin_mul); + float roundness_2 = roundness_add - (roundness_cos * roundness_cos_mul) + (roundness_sin * roundness_sin_mul); + float roundness_3 = roundness_add - (roundness_cos * roundness_cos_mul) - (roundness_sin * roundness_sin_mul); + + float roundness_max = IM_MAX(roundness_0, IM_MAX(roundness_1, IM_MAX(roundness_2, roundness_3))); + float roundness_min = IM_MIN(roundness_0, IM_MIN(roundness_1, IM_MIN(roundness_2, roundness_3))); + + return IM_DIV(roundness_min, roundness_max); +} + +void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, + list_t *thresholds, bool invert, unsigned int area_threshold, unsigned int pixels_threshold, + bool merge, int margin, + bool (*threshold_cb)(void*,find_blobs_list_lnk_data_t*), void *threshold_cb_arg, + bool (*merge_cb)(void*,find_blobs_list_lnk_data_t*,find_blobs_list_lnk_data_t*), void *merge_cb_arg, + unsigned int x_hist_bins_max, unsigned int y_hist_bins_max) +{ + // Same size as the image so we don't have to translate. + image_t bmp; + bmp.w = ptr->w; + bmp.h = ptr->h; + bmp.pixfmt = PIXFORMAT_BINARY; + bmp.data = fb_alloc0(image_size(&bmp), FB_ALLOC_NO_HINT); + + uint16_t *x_hist_bins = NULL; + if (x_hist_bins_max) x_hist_bins = fb_alloc(ptr->w * sizeof(uint16_t), FB_ALLOC_NO_HINT); + + uint16_t *y_hist_bins = NULL; + if (y_hist_bins_max) y_hist_bins = fb_alloc(ptr->h * sizeof(uint16_t), FB_ALLOC_NO_HINT); + + lifo_t lifo; + size_t lifo_len; + lifo_alloc_all(&lifo, &lifo_len, sizeof(xylr_t)); + + imlib_list_init(out, sizeof(find_blobs_list_lnk_data_t)); + + size_t code = 0; + for (list_lnk_t *it = iterator_start_from_head(thresholds); it; it = iterator_next(it)) { + color_thresholds_list_lnk_data_t lnk_data; + iterator_get(thresholds, it, &lnk_data); + + switch (ptr->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = roi->y, yy = roi->y + roi->h, y_max = yy - 1; y < yy; y += y_stride) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w, x_max = xx - 1; x < xx; x += x_stride) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) + && COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { + int old_x = x; + int old_y = y; + + float corners_acc[FIND_BLOBS_CORNERS_RESOLUTION]; + point_t corners[FIND_BLOBS_CORNERS_RESOLUTION]; + int corners_n[FIND_BLOBS_CORNERS_RESOLUTION]; + // These values are initialized to their maximum before we minimize. + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + corners[i].x = IM_MAX(IM_MIN(x_max * sign(cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), x_max), 0); + corners[i].y = IM_MAX(IM_MIN(y_max * sign(sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), y_max), 0); + corners_acc[i] = (corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (corners[i].y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + corners_n[i] = 1; + } + + int blob_pixels = 0; + int blob_perimeter = 0; + int blob_cx = 0; + int blob_cy = 0; + long long blob_a = 0; + long long blob_b = 0; + long long blob_c = 0; + + if (x_hist_bins) memset(x_hist_bins, 0, ptr->w * sizeof(uint16_t)); + if (y_hist_bins) memset(y_hist_bins, 0, ptr->h * sizeof(uint16_t)); + + // Scanline Flood Fill Algorithm // + + for(;;) { + int left = x, right = x; + uint32_t *row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y); + uint32_t *bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + + while ((left > roi->x) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, left - 1)) + && COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, left - 1), &lnk_data, invert)) { + left--; + } + + while ((right < (roi->x + roi->w - 1)) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, right + 1)) + && COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, right + 1), &lnk_data, invert)) { + right++; + } + + for (int i = left; i <= right; i++) { + IMAGE_SET_BINARY_PIXEL_FAST(bmp_row, i); + } + + int sum = sum_m_to_n(left, right); + int sum_2 = sum_2_m_to_n(left, right); + int cnt = right - left + 1; + int avg = sum / cnt; + + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + int x_new = (cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] > 0) ? left : + ((cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] == 0) ? avg : + right); + float z = (x_new * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + if (z < corners_acc[i]) { + corners_acc[i] = z; + corners[i].x = x_new; + corners[i].y = y; + corners_n[i] = 1; + } else if (z == corners_acc[i]) { + corners[i].x = cumulative_moving_average(corners[i].x, x_new, corners_n[i]); + corners[i].y = cumulative_moving_average(corners[i].y, y, corners_n[i]); + corners_n[i] += 1; + } + } + + blob_pixels += cnt; + blob_perimeter += 2; + blob_cx += sum; + blob_cy += y * cnt; + blob_a += sum_2; + blob_b += y * sum; + blob_c += y * y * cnt; + + if (y_hist_bins) y_hist_bins[y] += cnt; + if (x_hist_bins) for (int i = left; i <= right; i++) x_hist_bins[i] += 1; + + int top_left = left; + int bot_left = left; + bool break_out = false; + for(;;) { + if (lifo_size(&lifo) < lifo_len) { + + if (y > roi->y) { + row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y - 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y - 1); + + bool recurse = false; + for (int i = top_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; + lifo_enqueue(&lifo, &context); + x = i; + y = y - 1; + recurse = true; + break; + } + + blob_perimeter += (!ok) && (i != left) && (i != right); + } + if (recurse) { + break; + } + } else { + blob_perimeter += right - left + 1; + } + + if (y < (roi->y + roi->h - 1)) { + row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y + 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y + 1); + + bool recurse = false; + for (int i = bot_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... + lifo_enqueue(&lifo, &context); + x = i; + y = y + 1; + recurse = true; + break; + } + + blob_perimeter += (!ok) && (i != left) && (i != right); + } + if (recurse) { + break; + } + } else { + blob_perimeter += right - left + 1; + } + } else { + blob_perimeter += (right - left + 1) * 2; + } + + if (!lifo_size(&lifo)) { + break_out = true; + break; + } + + xylr_t context; + lifo_dequeue(&lifo, &context); + x = context.x; + y = context.y; + left = context.l; + right = context.r; + top_left = context.t_l; + bot_left = context.b_l; + } + + if (break_out) { + break; + } + } + + rectangle_t rect; + rect.x = corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x; // l + rect.y = corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y; // t + rect.w = corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x - corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x + 1; // r - l + 1 + rect.h = corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y - corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y + 1; // b - t + 1 + + if (((rect.w * rect.h) >= area_threshold) && (blob_pixels >= pixels_threshold)) { + + // http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf + // https://www.strchr.com/standard_deviation_in_one_pass + // + // a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx) + // b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my) + // c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my) + // + // blob_a = sigma(x*x) + // blob_b = sigma(x*y) + // blob_c = sigma(y*y) + // blob_cx = sigma(x) + // blob_cy = sigma(y) + // blob_pixels = sigma() + + float b_mx = blob_cx / ((float) blob_pixels); + float b_my = blob_cy / ((float) blob_pixels); + int mx = fast_roundf(b_mx); // x centroid + int my = fast_roundf(b_my); // y centroid + int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx); + int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my); + int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my); + + find_blobs_list_lnk_data_t lnk_blob; + memcpy(lnk_blob.corners, corners, FIND_BLOBS_CORNERS_RESOLUTION * sizeof(point_t)); + memcpy(&lnk_blob.rect, &rect, sizeof(rectangle_t)); + lnk_blob.pixels = blob_pixels; + lnk_blob.perimeter = blob_perimeter; + lnk_blob.code = 1 << code; + lnk_blob.count = 1; + lnk_blob.centroid_x = b_mx; + lnk_blob.centroid_y = b_my; + lnk_blob.rotation = (small_blob_a != small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 0.0f; + lnk_blob.roundness = calc_roundness(small_blob_a, small_blob_b, small_blob_c); + lnk_blob.x_hist_bins_count = 0; + lnk_blob.x_hist_bins = NULL; + lnk_blob.y_hist_bins_count = 0; + lnk_blob.y_hist_bins = NULL; + // These store the current average accumulation. + lnk_blob.centroid_x_acc = lnk_blob.centroid_x * lnk_blob.pixels; + lnk_blob.centroid_y_acc = lnk_blob.centroid_y * lnk_blob.pixels; + lnk_blob.rotation_acc_x = cosf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.rotation_acc_y = sinf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.roundness_acc = lnk_blob.roundness * lnk_blob.pixels; + + if (x_hist_bins) { + bin_up(x_hist_bins, ptr->w, x_hist_bins_max, &lnk_blob.x_hist_bins, &lnk_blob.x_hist_bins_count); + } + + if (y_hist_bins) { + bin_up(y_hist_bins, ptr->h, y_hist_bins_max, &lnk_blob.y_hist_bins, &lnk_blob.y_hist_bins_count); + } + + bool add_to_list = threshold_cb_arg == NULL; + if (!add_to_list) { + // Protect ourselves from caught exceptions in the callback + // code from freeing our fb_alloc() stack. + fb_alloc_mark(); + fb_alloc_mark_permanent(); + add_to_list = threshold_cb(threshold_cb_arg, &lnk_blob); + fb_alloc_free_till_mark_past_mark_permanent(); + } + + if (add_to_list) { + list_push_back(out, &lnk_blob); + } else { + if (lnk_blob.x_hist_bins) xfree(lnk_blob.x_hist_bins); + if (lnk_blob.y_hist_bins) xfree(lnk_blob.y_hist_bins); + } + } + + x = old_x; + y = old_y; + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = roi->y, yy = roi->y + roi->h, y_max = yy - 1; y < yy; y += y_stride) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w, x_max = xx - 1; x < xx; x += x_stride) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) + && COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { + int old_x = x; + int old_y = y; + + float corners_acc[FIND_BLOBS_CORNERS_RESOLUTION]; + point_t corners[FIND_BLOBS_CORNERS_RESOLUTION]; + int corners_n[FIND_BLOBS_CORNERS_RESOLUTION]; + // These values are initialized to their maximum before we minimize. + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + corners[i].x = IM_MAX(IM_MIN(x_max * sign(cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), x_max), 0); + corners[i].y = IM_MAX(IM_MIN(y_max * sign(sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), y_max), 0); + corners_acc[i] = (corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (corners[i].y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + corners_n[i] = 1; + } + + int blob_pixels = 0; + int blob_perimeter = 0; + int blob_cx = 0; + int blob_cy = 0; + long long blob_a = 0; + long long blob_b = 0; + long long blob_c = 0; + + if (x_hist_bins) memset(x_hist_bins, 0, ptr->w * sizeof(uint16_t)); + if (y_hist_bins) memset(y_hist_bins, 0, ptr->h * sizeof(uint16_t)); + + // Scanline Flood Fill Algorithm // + + for(;;) { + int left = x, right = x; + uint8_t *row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y); + uint32_t *bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + + while ((left > roi->x) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, left - 1)) + && COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, left - 1), &lnk_data, invert)) { + left--; + } + + while ((right < (roi->x + roi->w - 1)) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, right + 1)) + && COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, right + 1), &lnk_data, invert)) { + right++; + } + + for (int i = left; i <= right; i++) { + IMAGE_SET_BINARY_PIXEL_FAST(bmp_row, i); + } + + int sum = sum_m_to_n(left, right); + int sum_2 = sum_2_m_to_n(left, right); + int cnt = right - left + 1; + int avg = sum / cnt; + + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + int x_new = (cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] > 0) ? left : + ((cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] == 0) ? avg : + right); + float z = (x_new * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + if (z < corners_acc[i]) { + corners_acc[i] = z; + corners[i].x = x_new; + corners[i].y = y; + corners_n[i] = 1; + } else if (z == corners_acc[i]) { + corners[i].x = cumulative_moving_average(corners[i].x, x_new, corners_n[i]); + corners[i].y = cumulative_moving_average(corners[i].y, y, corners_n[i]); + corners_n[i] += 1; + } + } + + blob_pixels += cnt; + blob_perimeter += 2; + blob_cx += sum; + blob_cy += y * cnt; + blob_a += sum_2; + blob_b += y * sum; + blob_c += y * y * cnt; + + if (y_hist_bins) y_hist_bins[y] += cnt; + if (x_hist_bins) for (int i = left; i <= right; i++) x_hist_bins[i] += 1; + + int top_left = left; + int bot_left = left; + bool break_out = false; + for(;;) { + if (lifo_size(&lifo) < lifo_len) { + + if (y > roi->y) { + row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y - 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y - 1); + + bool recurse = false; + for (int i = top_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; + lifo_enqueue(&lifo, &context); + x = i; + y = y - 1; + recurse = true; + break; + } + + blob_perimeter += (!ok) && (i != left) && (i != right); + } + if (recurse) { + break; + } + } else { + blob_perimeter += right - left + 1; + } + + if (y < (roi->y + roi->h - 1)) { + row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y + 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y + 1); + + bool recurse = false; + for (int i = bot_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... + lifo_enqueue(&lifo, &context); + x = i; + y = y + 1; + recurse = true; + break; + } + + blob_perimeter += (!ok) && (i != left) && (i != right); + } + if (recurse) { + break; + } + } else { + blob_perimeter += right - left + 1; + } + } else { + blob_perimeter += (right - left + 1) * 2; + } + + if (!lifo_size(&lifo)) { + break_out = true; + break; + } + + xylr_t context; + lifo_dequeue(&lifo, &context); + x = context.x; + y = context.y; + left = context.l; + right = context.r; + top_left = context.t_l; + bot_left = context.b_l; + } + + if (break_out) { + break; + } + } + + rectangle_t rect; + rect.x = corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x; // l + rect.y = corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y; // t + rect.w = corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x - corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x + 1; // r - l + 1 + rect.h = corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y - corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y + 1; // b - t + 1 + + if (((rect.w * rect.h) >= area_threshold) && (blob_pixels >= pixels_threshold)) { + + // http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf + // https://www.strchr.com/standard_deviation_in_one_pass + // + // a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx) + // b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my) + // c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my) + // + // blob_a = sigma(x*x) + // blob_b = sigma(x*y) + // blob_c = sigma(y*y) + // blob_cx = sigma(x) + // blob_cy = sigma(y) + // blob_pixels = sigma() + + float b_mx = blob_cx / ((float) blob_pixels); + float b_my = blob_cy / ((float) blob_pixels); + int mx = fast_roundf(b_mx); // x centroid + int my = fast_roundf(b_my); // y centroid + int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx); + int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my); + int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my); + + find_blobs_list_lnk_data_t lnk_blob; + memcpy(lnk_blob.corners, corners, FIND_BLOBS_CORNERS_RESOLUTION * sizeof(point_t)); + memcpy(&lnk_blob.rect, &rect, sizeof(rectangle_t)); + lnk_blob.pixels = blob_pixels; + lnk_blob.perimeter = blob_perimeter; + lnk_blob.code = 1 << code; + lnk_blob.count = 1; + lnk_blob.centroid_x = b_mx; + lnk_blob.centroid_y = b_my; + lnk_blob.rotation = (small_blob_a != small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 0.0f; + lnk_blob.roundness = calc_roundness(small_blob_a, small_blob_b, small_blob_c); + lnk_blob.x_hist_bins_count = 0; + lnk_blob.x_hist_bins = NULL; + lnk_blob.y_hist_bins_count = 0; + lnk_blob.y_hist_bins = NULL; + // These store the current average accumulation. + lnk_blob.centroid_x_acc = lnk_blob.centroid_x * lnk_blob.pixels; + lnk_blob.centroid_y_acc = lnk_blob.centroid_y * lnk_blob.pixels; + lnk_blob.rotation_acc_x = cosf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.rotation_acc_y = sinf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.roundness_acc = lnk_blob.roundness * lnk_blob.pixels; + + if (x_hist_bins) { + bin_up(x_hist_bins, ptr->w, x_hist_bins_max, &lnk_blob.x_hist_bins, &lnk_blob.x_hist_bins_count); + } + + if (y_hist_bins) { + bin_up(y_hist_bins, ptr->h, y_hist_bins_max, &lnk_blob.y_hist_bins, &lnk_blob.y_hist_bins_count); + } + + bool add_to_list = threshold_cb_arg == NULL; + if (!add_to_list) { + // Protect ourselves from caught exceptions in the callback + // code from freeing our fb_alloc() stack. + fb_alloc_mark(); + fb_alloc_mark_permanent(); + add_to_list = threshold_cb(threshold_cb_arg, &lnk_blob); + fb_alloc_free_till_mark_past_mark_permanent(); + } + + if (add_to_list) { + list_push_back(out, &lnk_blob); + } else { + if (lnk_blob.x_hist_bins) xfree(lnk_blob.x_hist_bins); + if (lnk_blob.y_hist_bins) xfree(lnk_blob.y_hist_bins); + } + } + + x = old_x; + y = old_y; + } + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = roi->y, yy = roi->y + roi->h, y_max = yy - 1; y < yy; y += y_stride) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w, x_max = xx - 1; x < xx; x += x_stride) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) + && COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { + int old_x = x; + int old_y = y; + + float corners_acc[FIND_BLOBS_CORNERS_RESOLUTION]; + point_t corners[FIND_BLOBS_CORNERS_RESOLUTION]; + int corners_n[FIND_BLOBS_CORNERS_RESOLUTION]; + // Ensures that maximum goes all the way to the edge of the image. + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + corners[i].x = IM_MAX(IM_MIN(x_max * sign(cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), x_max), 0); + corners[i].y = IM_MAX(IM_MIN(y_max * sign(sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), y_max), 0); + corners_acc[i] = (corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (corners[i].y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + corners_n[i] = 1; + } + + int blob_pixels = 0; + int blob_perimeter = 0; + int blob_cx = 0; + int blob_cy = 0; + long long blob_a = 0; + long long blob_b = 0; + long long blob_c = 0; + + if (x_hist_bins) memset(x_hist_bins, 0, ptr->w * sizeof(uint16_t)); + if (y_hist_bins) memset(y_hist_bins, 0, ptr->h * sizeof(uint16_t)); + + // Scanline Flood Fill Algorithm // + + for(;;) { + int left = x, right = x; + uint16_t *row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y); + uint32_t *bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + + while ((left > roi->x) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, left - 1)) + && COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, left - 1), &lnk_data, invert)) { + left--; + } + + while ((right < (roi->x + roi->w - 1)) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, right + 1)) + && COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, right + 1), &lnk_data, invert)) { + right++; + } + + for (int i = left; i <= right; i++) { + IMAGE_SET_BINARY_PIXEL_FAST(bmp_row, i); + } + + int sum = sum_m_to_n(left, right); + int sum_2 = sum_2_m_to_n(left, right); + int cnt = right - left + 1; + int avg = sum / cnt; + + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + int x_new = (cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] > 0) ? left : + ((cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] == 0) ? avg : + right); + float z = (x_new * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + if (z < corners_acc[i]) { + corners_acc[i] = z; + corners[i].x = x_new; + corners[i].y = y; + corners_n[i] = 1; + } else if (z == corners_acc[i]) { + corners[i].x = cumulative_moving_average(corners[i].x, x_new, corners_n[i]); + corners[i].y = cumulative_moving_average(corners[i].y, y, corners_n[i]); + corners_n[i] += 1; + } + } + + blob_pixels += cnt; + blob_perimeter += 2; + blob_cx += sum; + blob_cy += y * cnt; + blob_a += sum_2; + blob_b += y * sum; + blob_c += y * y * cnt; + + if (y_hist_bins) y_hist_bins[y] += cnt; + if (x_hist_bins) for (int i = left; i <= right; i++) x_hist_bins[i] += 1; + + int top_left = left; + int bot_left = left; + bool break_out = false; + for(;;) { + if (lifo_size(&lifo) < lifo_len) { + + if (y > roi->y) { + row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y - 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y - 1); + + bool recurse = false; + for (int i = top_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; + lifo_enqueue(&lifo, &context); + x = i; + y = y - 1; + recurse = true; + break; + } + + blob_perimeter += (!ok) && (i != left) && (i != right); + } + if (recurse) { + break; + } + } else { + blob_perimeter += right - left + 1; + } + + if (y < (roi->y + roi->h - 1)) { + row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y + 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y + 1); + + bool recurse = false; + for (int i = bot_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... + lifo_enqueue(&lifo, &context); + x = i; + y = y + 1; + recurse = true; + break; + } + + blob_perimeter += (!ok) && (i != left) && (i != right); + } + if (recurse) { + break; + } + } else { + blob_perimeter += right - left + 1; + } + } else { + blob_perimeter += (right - left + 1) * 2; + } + + if (!lifo_size(&lifo)) { + break_out = true; + break; + } + + xylr_t context; + lifo_dequeue(&lifo, &context); + x = context.x; + y = context.y; + left = context.l; + right = context.r; + top_left = context.t_l; + bot_left = context.b_l; + } + + if (break_out) { + break; + } + } + + rectangle_t rect; + rect.x = corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x; // l + rect.y = corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y; // t + rect.w = corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x - corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x + 1; // r - l + 1 + rect.h = corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y - corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y + 1; // b - t + 1 + + if (((rect.w * rect.h) >= area_threshold) && (blob_pixels >= pixels_threshold)) { + + // http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf + // https://www.strchr.com/standard_deviation_in_one_pass + // + // a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx) + // b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my) + // c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my) + // + // blob_a = sigma(x*x) + // blob_b = sigma(x*y) + // blob_c = sigma(y*y) + // blob_cx = sigma(x) + // blob_cy = sigma(y) + // blob_pixels = sigma() + + float b_mx = blob_cx / ((float) blob_pixels); + float b_my = blob_cy / ((float) blob_pixels); + int mx = fast_roundf(b_mx); // x centroid + int my = fast_roundf(b_my); // y centroid + int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx); + int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my); + int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my); + + find_blobs_list_lnk_data_t lnk_blob; + memcpy(lnk_blob.corners, corners, FIND_BLOBS_CORNERS_RESOLUTION * sizeof(point_t)); + memcpy(&lnk_blob.rect, &rect, sizeof(rectangle_t)); + lnk_blob.pixels = blob_pixels; + lnk_blob.perimeter = blob_perimeter; + lnk_blob.code = 1 << code; + lnk_blob.count = 1; + lnk_blob.centroid_x = b_mx; + lnk_blob.centroid_y = b_my; + lnk_blob.rotation = (small_blob_a != small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 0.0f; + lnk_blob.roundness = calc_roundness(small_blob_a, small_blob_b, small_blob_c); + lnk_blob.x_hist_bins_count = 0; + lnk_blob.x_hist_bins = NULL; + lnk_blob.y_hist_bins_count = 0; + lnk_blob.y_hist_bins = NULL; + // These store the current average accumulation. + lnk_blob.centroid_x_acc = lnk_blob.centroid_x * lnk_blob.pixels; + lnk_blob.centroid_y_acc = lnk_blob.centroid_y * lnk_blob.pixels; + lnk_blob.rotation_acc_x = cosf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.rotation_acc_y = sinf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.roundness_acc = lnk_blob.roundness * lnk_blob.pixels; + + if (x_hist_bins) { + bin_up(x_hist_bins, ptr->w, x_hist_bins_max, &lnk_blob.x_hist_bins, &lnk_blob.x_hist_bins_count); + } + + if (y_hist_bins) { + bin_up(y_hist_bins, ptr->h, y_hist_bins_max, &lnk_blob.y_hist_bins, &lnk_blob.y_hist_bins_count); + } + + bool add_to_list = threshold_cb_arg == NULL; + if (!add_to_list) { + // Protect ourselves from caught exceptions in the callback + // code from freeing our fb_alloc() stack. + fb_alloc_mark(); + fb_alloc_mark_permanent(); + add_to_list = threshold_cb(threshold_cb_arg, &lnk_blob); + fb_alloc_free_till_mark_past_mark_permanent(); + } + + if (add_to_list) { + list_push_back(out, &lnk_blob); + } else { + if (lnk_blob.x_hist_bins) xfree(lnk_blob.x_hist_bins); + if (lnk_blob.y_hist_bins) xfree(lnk_blob.y_hist_bins); + } + } + + x = old_x; + y = old_y; + } + } + } + break; + } + case PIXFORMAT_RGB888: { + for (int y = roi->y, yy = roi->y + roi->h, y_max = yy - 1; y < yy; y += y_stride) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y); + uint32_t *bmp_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w, x_max = xx - 1; x < xx; x += x_stride) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row_ptr, x)) + && COLOR_THRESHOLD_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { + int old_x = x; + int old_y = y; + + float corners_acc[FIND_BLOBS_CORNERS_RESOLUTION]; + point_t corners[FIND_BLOBS_CORNERS_RESOLUTION]; + int corners_n[FIND_BLOBS_CORNERS_RESOLUTION]; + // Ensures that maximum goes all the way to the edge of the image. + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + corners[i].x = IM_MAX(IM_MIN(x_max * sign(cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), x_max), 0); + corners[i].y = IM_MAX(IM_MIN(y_max * sign(sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]), y_max), 0); + corners_acc[i] = (corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (corners[i].y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + corners_n[i] = 1; + } + + int blob_pixels = 0; + int blob_perimeter = 0; + int blob_cx = 0; + int blob_cy = 0; + long long blob_a = 0; + long long blob_b = 0; + long long blob_c = 0; + + if (x_hist_bins) memset(x_hist_bins, 0, ptr->w * sizeof(uint16_t)); + if (y_hist_bins) memset(y_hist_bins, 0, ptr->h * sizeof(uint16_t)); + + // Scanline Flood Fill Algorithm // + + for(;;) { + int left = x, right = x; + pixel24_t *row = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y); + uint32_t *bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y); + + while ((left > roi->x) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, left - 1)) + && COLOR_THRESHOLD_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, left - 1), &lnk_data, invert)) { + left--; + } + + while ((right < (roi->x + roi->w - 1)) + && (!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, right + 1)) + && COLOR_THRESHOLD_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, right + 1), &lnk_data, invert)) { + right++; + } + + for (int i = left; i <= right; i++) { + IMAGE_SET_BINARY_PIXEL_FAST(bmp_row, i); + } + + int sum = sum_m_to_n(left, right); + int sum_2 = sum_2_m_to_n(left, right); + int cnt = right - left + 1; + int avg = sum / cnt; + + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + int x_new = (cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] > 0) ? left : + ((cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i] == 0) ? avg : + right); + float z = (x_new * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + if (z < corners_acc[i]) { + corners_acc[i] = z; + corners[i].x = x_new; + corners[i].y = y; + corners_n[i] = 1; + } else if (z == corners_acc[i]) { + corners[i].x = cumulative_moving_average(corners[i].x, x_new, corners_n[i]); + corners[i].y = cumulative_moving_average(corners[i].y, y, corners_n[i]); + corners_n[i] += 1; + } + } + + blob_pixels += cnt; + blob_perimeter += 2; + blob_cx += sum; + blob_cy += y * cnt; + blob_a += sum_2; + blob_b += y * sum; + blob_c += y * y * cnt; + + if (y_hist_bins) y_hist_bins[y] += cnt; + if (x_hist_bins) for (int i = left; i <= right; i++) x_hist_bins[i] += 1; + + int top_left = left; + int bot_left = left; + bool break_out = false; + for(;;) { + if (lifo_size(&lifo) < lifo_len) { + + if (y > roi->y) { + row = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y - 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y - 1); + + bool recurse = false; + for (int i = top_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; + lifo_enqueue(&lifo, &context); + x = i; + y = y - 1; + recurse = true; + break; + } + + blob_perimeter += (!ok) && (i != left) && (i != right); + } + if (recurse) { + break; + } + } else { + blob_perimeter += right - left + 1; + } + + if (y < (roi->y + roi->h - 1)) { + row = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y + 1); + bmp_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&bmp, y + 1); + + bool recurse = false; + for (int i = bot_left; i <= right; i++) { + bool ok = true; // Does nothing if thresholding is skipped. + + if ((!IMAGE_GET_BINARY_PIXEL_FAST(bmp_row, i)) + && (ok = COLOR_THRESHOLD_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, i), &lnk_data, invert))) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... + lifo_enqueue(&lifo, &context); + x = i; + y = y + 1; + recurse = true; + break; + } + + blob_perimeter += (!ok) && (i != left) && (i != right); + } + if (recurse) { + break; + } + } else { + blob_perimeter += right - left + 1; + } + } else { + blob_perimeter += (right - left + 1) * 2; + } + + if (!lifo_size(&lifo)) { + break_out = true; + break; + } + + xylr_t context; + lifo_dequeue(&lifo, &context); + x = context.x; + y = context.y; + left = context.l; + right = context.r; + top_left = context.t_l; + bot_left = context.b_l; + } + + if (break_out) { + break; + } + } + + rectangle_t rect; + rect.x = corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x; // l + rect.y = corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y; // t + rect.w = corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x - corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x + 1; // r - l + 1 + rect.h = corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y - corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y + 1; // b - t + 1 + + if (((rect.w * rect.h) >= area_threshold) && (blob_pixels >= pixels_threshold)) { + + // http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf + // https://www.strchr.com/standard_deviation_in_one_pass + // + // a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx) + // b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my) + // c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my) + // + // blob_a = sigma(x*x) + // blob_b = sigma(x*y) + // blob_c = sigma(y*y) + // blob_cx = sigma(x) + // blob_cy = sigma(y) + // blob_pixels = sigma() + + float b_mx = blob_cx / ((float) blob_pixels); + float b_my = blob_cy / ((float) blob_pixels); + int mx = fast_roundf(b_mx); // x centroid + int my = fast_roundf(b_my); // y centroid + int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx); + int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my); + int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my); + + find_blobs_list_lnk_data_t lnk_blob; + memcpy(lnk_blob.corners, corners, FIND_BLOBS_CORNERS_RESOLUTION * sizeof(point_t)); + memcpy(&lnk_blob.rect, &rect, sizeof(rectangle_t)); + lnk_blob.pixels = blob_pixels; + lnk_blob.perimeter = blob_perimeter; + lnk_blob.code = 1 << code; + lnk_blob.count = 1; + lnk_blob.centroid_x = b_mx; + lnk_blob.centroid_y = b_my; + lnk_blob.rotation = (small_blob_a != small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 0.0f; + lnk_blob.roundness = calc_roundness(small_blob_a, small_blob_b, small_blob_c); + lnk_blob.x_hist_bins_count = 0; + lnk_blob.x_hist_bins = NULL; + lnk_blob.y_hist_bins_count = 0; + lnk_blob.y_hist_bins = NULL; + // These store the current average accumulation. + lnk_blob.centroid_x_acc = lnk_blob.centroid_x * lnk_blob.pixels; + lnk_blob.centroid_y_acc = lnk_blob.centroid_y * lnk_blob.pixels; + lnk_blob.rotation_acc_x = cosf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.rotation_acc_y = sinf(lnk_blob.rotation) * lnk_blob.pixels; + lnk_blob.roundness_acc = lnk_blob.roundness * lnk_blob.pixels; + + if (x_hist_bins) { + bin_up(x_hist_bins, ptr->w, x_hist_bins_max, &lnk_blob.x_hist_bins, &lnk_blob.x_hist_bins_count); + } + + if (y_hist_bins) { + bin_up(y_hist_bins, ptr->h, y_hist_bins_max, &lnk_blob.y_hist_bins, &lnk_blob.y_hist_bins_count); + } + + bool add_to_list = threshold_cb_arg == NULL; + if (!add_to_list) { + // Protect ourselves from caught exceptions in the callback + // code from freeing our fb_alloc() stack. + fb_alloc_mark(); + fb_alloc_mark_permanent(); + add_to_list = threshold_cb(threshold_cb_arg, &lnk_blob); + fb_alloc_free_till_mark_past_mark_permanent(); + } + + if (add_to_list) { + list_push_back(out, &lnk_blob); + } else { + if (lnk_blob.x_hist_bins) xfree(lnk_blob.x_hist_bins); + if (lnk_blob.y_hist_bins) xfree(lnk_blob.y_hist_bins); + } + } + + x = old_x; + y = old_y; + } + } + } + break; + } + default: { + break; + } + } + + code += 1; + } + + lifo_free(&lifo); + if (y_hist_bins) fb_free(y_hist_bins); + if (x_hist_bins) fb_free(x_hist_bins); + fb_free(bmp.data); // bitmap + + if (merge) { + for(;;) { + bool merge_occured = false; + + list_t out_temp; + imlib_list_init(&out_temp, sizeof(find_blobs_list_lnk_data_t)); + + while(list_size(out)) { + find_blobs_list_lnk_data_t lnk_blob; + list_pop_front(out, &lnk_blob); + + for (size_t k = 0, l = list_size(out); k < l; k++) { + find_blobs_list_lnk_data_t tmp_blob; + list_pop_front(out, &tmp_blob); + + rectangle_t temp; + temp.x = IM_MAX(IM_MIN(tmp_blob.rect.x - margin, INT16_MAX), INT16_MIN); + temp.y = IM_MAX(IM_MIN(tmp_blob.rect.y - margin, INT16_MAX), INT16_MIN); + temp.w = IM_MAX(IM_MIN(tmp_blob.rect.w + (margin * 2), INT16_MAX), 0); + temp.h = IM_MAX(IM_MIN(tmp_blob.rect.h + (margin * 2), INT16_MAX), 0); + + if (rectangle_overlap(&(lnk_blob.rect), &temp) + && ((merge_cb_arg == NULL) || merge_cb(merge_cb_arg, &lnk_blob, &tmp_blob))) { + // Have to merge these first before merging rects. + if (x_hist_bins_max) merge_bins(lnk_blob.rect.x, lnk_blob.rect.x + lnk_blob.rect.w - 1, &lnk_blob.x_hist_bins, &lnk_blob.x_hist_bins_count, + tmp_blob.rect.x, tmp_blob.rect.x + tmp_blob.rect.w - 1, &tmp_blob.x_hist_bins, &tmp_blob.x_hist_bins_count, + x_hist_bins_max); + if (y_hist_bins_max) merge_bins(lnk_blob.rect.y, lnk_blob.rect.y + lnk_blob.rect.h - 1, &lnk_blob.y_hist_bins, &lnk_blob.y_hist_bins_count, + tmp_blob.rect.y, tmp_blob.rect.y + tmp_blob.rect.h - 1, &tmp_blob.y_hist_bins, &tmp_blob.y_hist_bins_count, + y_hist_bins_max); + // Merge corners... + for (int i = 0; i < FIND_BLOBS_CORNERS_RESOLUTION; i++) { + float z_dst = (lnk_blob.corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (lnk_blob.corners[i].y * sin_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + float z_src = (tmp_blob.corners[i].x * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]) + + (tmp_blob.corners[i].y * cos_table[FIND_BLOBS_ANGLE_RESOLUTION*i]); + if (z_src < z_dst) { + lnk_blob.corners[i].x = tmp_blob.corners[i].x; + lnk_blob.corners[i].y = tmp_blob.corners[i].y; + } + } + // Merge rects... + rectangle_united(&(lnk_blob.rect), &(tmp_blob.rect)); + // Merge counters... + lnk_blob.pixels += tmp_blob.pixels; // won't overflow + lnk_blob.perimeter += tmp_blob.perimeter; // won't overflow + lnk_blob.code |= tmp_blob.code; // won't overflow + lnk_blob.count += tmp_blob.count; // won't overflow + // Merge accumulators... + lnk_blob.centroid_x_acc += tmp_blob.centroid_x_acc; + lnk_blob.centroid_y_acc += tmp_blob.centroid_y_acc; + lnk_blob.rotation_acc_x += tmp_blob.rotation_acc_x; + lnk_blob.rotation_acc_y += tmp_blob.rotation_acc_y; + lnk_blob.roundness_acc += tmp_blob.roundness_acc; + // Compute current values... + lnk_blob.centroid_x = lnk_blob.centroid_x_acc / lnk_blob.pixels; + lnk_blob.centroid_y = lnk_blob.centroid_y_acc / lnk_blob.pixels; + lnk_blob.rotation = fast_atan2f(lnk_blob.rotation_acc_y / lnk_blob.pixels, + lnk_blob.rotation_acc_x / lnk_blob.pixels); + lnk_blob.roundness = lnk_blob.roundness_acc / lnk_blob.pixels; + merge_occured = true; + } else { + list_push_back(out, &tmp_blob); + } + } + + list_push_back(&out_temp, &lnk_blob); + } + + list_copy(out, &out_temp); + + if (!merge_occured) { + break; + } + } + } +} + +void imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, + int seed_threshold, int floating_threshold, + flood_fill_call_back_t cb, void *data) +{ + lifo_t lifo; + size_t lifo_len; + lifo_alloc_all(&lifo, &lifo_len, sizeof(xylr_t)); + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + for(int seed_pixel = IMAGE_GET_BINARY_PIXEL(img, x, y);;) { + int left = x, right = x; + uint32_t *row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y); + + while ((left > 0) + && (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, left - 1)) + && COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, left - 1), seed_pixel, seed_threshold) + && COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, left - 1), + IMAGE_GET_BINARY_PIXEL_FAST(row, left), floating_threshold)) { + left--; + } + + while ((right < (img->w - 1)) + && (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, right + 1)) + && COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, right + 1), seed_pixel, seed_threshold) + && COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, right + 1), + IMAGE_GET_BINARY_PIXEL_FAST(row, right), floating_threshold)) { + right++; + } + + for (int i = left; i <= right; i++) { + IMAGE_SET_BINARY_PIXEL_FAST(out_row, i); + } + + int top_left = left; + int bot_left = left; + bool break_out = false; + for(;;) { + if (lifo_size(&lifo) < lifo_len) { + uint32_t *old_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + + if (y > 0) { + row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y - 1); + out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y - 1); + + bool recurse = false; + for (int i = top_left; i <= right; i++) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) + && COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), seed_pixel, seed_threshold) + && COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), + IMAGE_GET_BINARY_PIXEL_FAST(old_row, i), floating_threshold)) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; + lifo_enqueue(&lifo, &context); + x = i; + y = y - 1; + recurse = true; + break; + } + } + if (recurse) { + break; + } + } + + if (y < (img->h - 1)) { + row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y + 1); + out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y + 1); + + bool recurse = false; + for (int i = bot_left; i <= right; i++) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) + && COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), seed_pixel, seed_threshold) + && COLOR_BOUND_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row, i), + IMAGE_GET_BINARY_PIXEL_FAST(old_row, i), floating_threshold)) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... + lifo_enqueue(&lifo, &context); + x = i; + y = y + 1; + recurse = true; + break; + } + } + if (recurse) { + break; + } + } + } + + if (cb) cb(img, y, left, right, data); + + if (!lifo_size(&lifo)) { + break_out = true; + break; + } + + xylr_t context; + lifo_dequeue(&lifo, &context); + x = context.x; + y = context.y; + left = context.l; + right = context.r; + top_left = context.t_l; + bot_left = context.b_l; + } + + if (break_out) { + break; + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for(int seed_pixel = IMAGE_GET_GRAYSCALE_PIXEL(img, x, y);;) { + int left = x, right = x; + uint8_t *row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint32_t *out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y); + + while ((left > 0) + && (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, left - 1)) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, left - 1), seed_pixel, seed_threshold) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, left - 1), + IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, left), floating_threshold)) { + left--; + } + + while ((right < (img->w - 1)) + && (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, right + 1)) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, right + 1), seed_pixel, seed_threshold) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, right + 1), + IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, right), floating_threshold)) { + right++; + } + + for (int i = left; i <= right; i++) { + IMAGE_SET_BINARY_PIXEL_FAST(out_row, i); + } + + int top_left = left; + int bot_left = left; + bool break_out = false; + for(;;) { + if (lifo_size(&lifo) < lifo_len) { + uint8_t *old_row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + + if (y > 0) { + row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y - 1); + out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y - 1); + + bool recurse = false; + for (int i = top_left; i <= right; i++) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), seed_pixel, seed_threshold) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), + IMAGE_GET_GRAYSCALE_PIXEL_FAST(old_row, i), floating_threshold)) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; + lifo_enqueue(&lifo, &context); + x = i; + y = y - 1; + recurse = true; + break; + } + } + if (recurse) { + break; + } + } + + if (y < (img->h - 1)) { + row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y + 1); + out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y + 1); + + bool recurse = false; + for (int i = bot_left; i <= right; i++) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), seed_pixel, seed_threshold) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), + IMAGE_GET_GRAYSCALE_PIXEL_FAST(old_row, i), floating_threshold)) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... + lifo_enqueue(&lifo, &context); + x = i; + y = y + 1; + recurse = true; + break; + } + } + if (recurse) { + break; + } + } + } + + if (cb) cb(img, y, left, right, data); + + if (!lifo_size(&lifo)) { + break_out = true; + break; + } + + xylr_t context; + lifo_dequeue(&lifo, &context); + x = context.x; + y = context.y; + left = context.l; + right = context.r; + top_left = context.t_l; + bot_left = context.b_l; + } + + if (break_out) { + break; + } + } + break; + } + case PIXFORMAT_RGB565: { + for(int seed_pixel = IMAGE_GET_RGB565_PIXEL(img, x, y);;) { + int left = x, right = x; + uint16_t *row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint32_t *out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y); + + while ((left > 0) + && (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, left - 1)) + && COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, left - 1), seed_pixel, seed_threshold) + && COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, left - 1), + IMAGE_GET_RGB565_PIXEL_FAST(row, left), floating_threshold)) { + left--; + } + + while ((right < (img->w - 1)) + && (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, right + 1)) + && COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, right + 1), seed_pixel, seed_threshold) + && COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, right + 1), + IMAGE_GET_RGB565_PIXEL_FAST(row, right), floating_threshold)) { + right++; + } + + for (int i = left; i <= right; i++) { + IMAGE_SET_BINARY_PIXEL_FAST(out_row, i); + } + + int top_left = left; + int bot_left = left; + bool break_out = false; + for(;;) { + if (lifo_size(&lifo) < lifo_len) { + uint16_t *old_row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + + if (y > 0) { + row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y - 1); + out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y - 1); + + bool recurse = false; + for (int i = top_left; i <= right; i++) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) + && COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), seed_pixel, seed_threshold) + && COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), + IMAGE_GET_RGB565_PIXEL_FAST(old_row, i), floating_threshold)) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; + lifo_enqueue(&lifo, &context); + x = i; + y = y - 1; + recurse = true; + break; + } + } + if (recurse) { + break; + } + } + + if (y < (img->h - 1)) { + row = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y + 1); + out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y + 1); + + bool recurse = false; + for (int i = bot_left; i <= right; i++) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) + && COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), seed_pixel, seed_threshold) + && COLOR_BOUND_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row, i), + IMAGE_GET_RGB565_PIXEL_FAST(old_row, i), floating_threshold)) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... + lifo_enqueue(&lifo, &context); + x = i; + y = y + 1; + recurse = true; + break; + } + } + if (recurse) { + break; + } + } + } + + if (cb) cb(img, y, left, right, data); + + if (!lifo_size(&lifo)) { + break_out = true; + break; + } + + xylr_t context; + lifo_dequeue(&lifo, &context); + x = context.x; + y = context.y; + left = context.l; + right = context.r; + top_left = context.t_l; + bot_left = context.b_l; + } + + if (break_out) { + break; + } + } + break; + } + case PIXFORMAT_RGB888: { + for(int seed_pixel = IMAGE_GET_RGB888_PIXEL(img, x, y);;) { + int left = x, right = x; + pixel24_t *row = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + uint32_t *out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y); + + while ((left > 0) + && (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, left - 1)) + && COLOR_BOUND_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, left - 1), seed_pixel, seed_threshold) + && COLOR_BOUND_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, left - 1), + IMAGE_GET_RGB888_PIXEL_FAST(row, left), floating_threshold)) { + left--; + } + + while ((right < (img->w - 1)) + && (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, right + 1)) + && COLOR_BOUND_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, right + 1), seed_pixel, seed_threshold) + && COLOR_BOUND_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, right + 1), + IMAGE_GET_RGB888_PIXEL_FAST(row, right), floating_threshold)) { + right++; + } + + for (int i = left; i <= right; i++) { + IMAGE_SET_BINARY_PIXEL_FAST(out_row, i); + } + + int top_left = left; + int bot_left = left; + bool break_out = false; + for(;;) { + if (lifo_size(&lifo) < lifo_len) { + pixel24_t *old_row = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + + if (y > 0) { + row = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y - 1); + out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y - 1); + + bool recurse = false; + for (int i = top_left; i <= right; i++) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) + && COLOR_BOUND_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, i), seed_pixel, seed_threshold) + && COLOR_BOUND_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, i), + IMAGE_GET_RGB888_PIXEL_FAST(old_row, i), floating_threshold)) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = i + 1; // Don't test the same pixel again... + context.b_l = bot_left; + lifo_enqueue(&lifo, &context); + x = i; + y = y - 1; + recurse = true; + break; + } + } + if (recurse) { + break; + } + } + + if (y < (img->h - 1)) { + row = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y + 1); + out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y + 1); + + bool recurse = false; + for (int i = bot_left; i <= right; i++) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) + && COLOR_BOUND_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, i), seed_pixel, seed_threshold) + && COLOR_BOUND_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row, i), + IMAGE_GET_RGB888_PIXEL_FAST(old_row, i), floating_threshold)) { + xylr_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + context.t_l = top_left; + context.b_l = i + 1; // Don't test the same pixel again... + lifo_enqueue(&lifo, &context); + x = i; + y = y + 1; + recurse = true; + break; + } + } + if (recurse) { + break; + } + } + } + + if (cb) cb(img, y, left, right, data); + + if (!lifo_size(&lifo)) { + break_out = true; + break; + } + + xylr_t context; + lifo_dequeue(&lifo, &context); + x = context.x; + y = context.y; + left = context.l; + right = context.r; + top_left = context.t_l; + bot_left = context.b_l; + } + + if (break_out) { + break; + } + } + break; + } + default: { + break; + } + } + + lifo_free(&lifo); +} diff --git a/github_source/minicv2/src/bmp.c b/github_source/minicv2/src/bmp.c new file mode 100644 index 0000000..2b94804 --- /dev/null +++ b/github_source/minicv2/src/bmp.c @@ -0,0 +1,391 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * BMP reader/writer. + */ + +#include "imlib.h" +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + +#include +// #include "py/obj.h" +// #include "py/runtime.h" + +#include "xalloc.h" +#include "ff_wrapper.h" + +// This function inits the geometry values of an image (opens file). +bool bmp_read_geometry(FIL *fp, image_t *img, const char *path, bmp_read_settings_t *rs) +{ + int is_ps_image = 0; + read_byte_expect(fp, 'B'); + read_byte_expect(fp, 'M'); + + uint32_t file_size; + read_long(fp, &file_size); + read_word_ignore(fp); + read_word_ignore(fp); + + uint32_t header_size; + read_long(fp, &header_size); + if (file_size <= header_size) ff_file_corrupted(fp); + + uint32_t data_size = file_size - header_size; + if (data_size % 4) + { + uint32_t tmp_uint32 = data_size - 2; + if(tmp_uint32 % 4 == 0) + { + is_ps_image = 1; + } + else + { + ff_file_corrupted(fp); + } + } + + uint32_t header_type; + read_long(fp, &header_type); + if ((header_type != 40) // BITMAPINFOHEADER + && (header_type != 52) // BITMAPV2INFOHEADER + && (header_type != 56) // BITMAPV3INFOHEADER + && (header_type != 108) // BITMAPV4HEADER + && (header_type != 124)) ff_unsupported_format(fp); // BITMAPV5HEADER + read_long(fp, (uint32_t*) &rs->bmp_w); + read_long(fp, (uint32_t*) &rs->bmp_h); + if ((rs->bmp_w == 0) || (rs->bmp_h == 0)) ff_file_corrupted(fp); + img->w = abs(rs->bmp_w); + img->h = abs(rs->bmp_h); + + read_word_expect(fp, 1); + read_word(fp, &rs->bmp_bpp); + if ((rs->bmp_bpp != 8) && (rs->bmp_bpp != 16) && (rs->bmp_bpp != 24)) { + ff_unsupported_format(fp); + } + // img->pixfmt = (rs->bmp_bpp == 8) ? PIXFORMAT_GRAYSCALE : PIXFORMAT_RGB565; + switch (rs->bmp_bpp) + { + case 8: + img->pixfmt = PIXFORMAT_GRAYSCALE; + break; + case 16: + img->pixfmt = PIXFORMAT_RGB565; + break; + case 24: + img->pixfmt = PIXFORMAT_RGB888; + break; + } + read_long(fp, &rs->bmp_fmt); + if ((rs->bmp_fmt != 0) && (rs->bmp_fmt != 3)) ff_unsupported_format(fp); + + read_long_ignore(fp); + read_long_ignore(fp); + read_long_ignore(fp); + read_long_ignore(fp); + read_long_ignore(fp); + + if (rs->bmp_bpp == 8) { + if (rs->bmp_fmt != 0) ff_unsupported_format(fp); + if (header_type >= 52) { // Skip past the remaining BITMAPV2INFOHEADER bytes. + for (int i = 0; i < 3; i++) read_long_ignore(fp); + } + if (header_type >= 56) { // Skip past the remaining BITMAPV3INFOHEADER bytes. + for (int i = 0; i < 1; i++) read_long_ignore(fp); + } + if (header_type >= 108) { // Skip past the remaining BITMAPV4HEADER bytes. + for (int i = 0; i < 13; i++) read_long_ignore(fp); + } + if (header_type >= 124) { // Skip past the remaining BITMAPV5HEADER bytes. + for (int i = 0; i < 4; i++) read_long_ignore(fp); + } + // Color Table (1024 bytes) + for (int i = 0; i < 256; i++) { + read_long_expect(fp, ((i) << 16) | ((i) << 8) | i); + } + } else if (rs->bmp_bpp == 16) { + if (rs->bmp_fmt != 3) ff_unsupported_format(fp); + // Bit Masks (12 bytes) + read_long_expect(fp, 0x1F << 11); + read_long_expect(fp, 0x3F << 5); + read_long_expect(fp, 0x1F); + if (header_type >= 56) { // Skip past the remaining BITMAPV3INFOHEADER bytes. + for (int i = 0; i < 1; i++) read_long_ignore(fp); + } + if (header_type >= 108) { // Skip past the remaining BITMAPV4HEADER bytes. + for (int i = 0; i < 13; i++) read_long_ignore(fp); + } + if (header_type >= 124) { // Skip past the remaining BITMAPV5HEADER bytes. + for (int i = 0; i < 4; i++) read_long_ignore(fp); + } + } else if (rs->bmp_bpp == 24) { + if (rs->bmp_fmt == 3) { + // Bit Masks (12 bytes) + read_long_expect(fp, 0xFF << 16); + read_long_expect(fp, 0xFF << 8); + read_long_expect(fp, 0xFF); + } else if (header_type >= 52) { // Skip past the remaining BITMAPV2INFOHEADER bytes. + for (int i = 0; i < 3; i++) read_long_ignore(fp); + } + if (header_type >= 56) { // Skip past the remaining BITMAPV3INFOHEADER bytes. + for (int i = 0; i < 1; i++) read_long_ignore(fp); + } + if (header_type >= 108) { // Skip past the remaining BITMAPV4HEADER bytes. + for (int i = 0; i < 13; i++) read_long_ignore(fp); + } + if (header_type >= 124) { // Skip past the remaining BITMAPV5HEADER bytes. + for (int i = 0; i < 4; i++) read_long_ignore(fp); + } + } + + rs->bmp_row_bytes = (((img->w * rs->bmp_bpp) + 31) / 32) * 4; + if(is_ps_image) + { + if ((data_size - 2) != (rs->bmp_row_bytes * img->h)) ff_file_corrupted(fp); + } + else + { + if (data_size != (rs->bmp_row_bytes * img->h)) ff_file_corrupted(fp); + } + return (rs->bmp_h >= 0); +} + +// This function reads the pixel values of an image. +void bmp_read_pixels(FIL *fp, image_t *img, int n_lines, bmp_read_settings_t *rs) +{ + if (rs->bmp_bpp == 8) { + if ((rs->bmp_h < 0) && (rs->bmp_w >= 0) && (img->w == rs->bmp_row_bytes)) { + read_data(fp, img->pixels, n_lines * img->w); + } else { + for (int i = 0; i < n_lines; i++) { + for (int j = 0; j < rs->bmp_row_bytes; j++) { + uint8_t pixel; + read_byte(fp, &pixel); + if (j < img->w) { + if (rs->bmp_h < 0) { // vertical flip (BMP file perspective) + if (rs->bmp_w < 0) { // horizontal flip (BMP file perspective) + IM_SET_GS_PIXEL(img, (img->w-j-1), i, pixel); + } else { + IM_SET_GS_PIXEL(img, j, i, pixel); + } + } else { + if (rs->bmp_w < 0) { + IM_SET_GS_PIXEL(img, (img->w-j-1), (img->h-i-1), pixel); + } else { + IM_SET_GS_PIXEL(img, j, (img->h-i-1), pixel); + } + } + } + } + } + } + } else if (rs->bmp_bpp == 16) { + for (int i = 0; i < n_lines; i++) { + for (int j = 0, jj = rs->bmp_row_bytes / 2; j < jj; j++) { + uint16_t pixel; + read_word(fp, &pixel); + if (j < img->w) { + if (rs->bmp_h < 0) { // vertical flip (BMP file perspective) + if (rs->bmp_w < 0) { // horizontal flip (BMP file perspective) + IM_SET_RGB565_PIXEL(img, (img->w-j-1), i, pixel); + } else { + IM_SET_RGB565_PIXEL(img, j, i, pixel); + } + } else { + if (rs->bmp_w < 0) { + IM_SET_RGB565_PIXEL(img, (img->w-j-1), (img->h-i-1), pixel); + } else { + IM_SET_RGB565_PIXEL(img, j, (img->h-i-1), pixel); + } + } + } + } + } + } else if (rs->bmp_bpp == 24) { + for (int i = 0; i < n_lines; i++) { + for (int j = 0, jj = rs->bmp_row_bytes / 3; j < jj; j++) { + uint8_t b, g, r; + read_byte(fp, &b); + read_byte(fp, &g); + read_byte(fp, &r); + int pixel = COLOR_R8_G8_B8_TO_RGB888(r, g, b); + if (j < img->w) { + if (rs->bmp_h < 0) { // vertical flip + if (rs->bmp_w < 0) { // horizontal flip + IM_SET_RGB888_PIXEL(img, (img->w-j-1), i, pixel); + } else { + IM_SET_RGB888_PIXEL(img, j, i, pixel); + } + } else { + if (rs->bmp_w < 0) { + IM_SET_RGB888_PIXEL(img, (img->w-j-1), (img->h-i-1), pixel); + } else { + IM_SET_RGB888_PIXEL(img, j, (img->h-i-1), pixel); + } + } + } + } + for (int j = 0, jj = rs->bmp_row_bytes % 3; j < jj; j++) { + read_byte_ignore(fp); + } + } + } +} + +void bmp_read(image_t *img, const char *path) +{ + FIL fp; + bmp_read_settings_t rs; + file_read_open(&fp, path); + // file_buffer_on(&fp); + bmp_read_geometry(&fp, img, path, &rs); + if(img->is_data_alloc){ + img->pixels = xrealloc(img->pixels, img->w * img->h * img->bpp); + }else{ + img->pixels = xalloc(img->w * img->h * img->bpp); + img->is_data_alloc = true; + } + bmp_read_pixels(&fp, img, img->h, &rs); + // file_buffer_off(&fp); + file_close(&fp); +} + +void bmp_write_subimg(image_t *img, const char *path, rectangle_t *r) +{ + rectangle_t rect; + if(!r) + { + rectangle_t tmp_rect; + rectangle_init(&tmp_rect, 0, 0, img->w, img->h); + if (!rectangle_subimg(img, &tmp_rect, &rect)) { + ERR_PRINT("OSError: No intersection!"); + } + } + else + { + if (!rectangle_subimg(img, r, &rect)) { + ERR_PRINT("OSError: No intersection!"); + } + } + FIL fp; + file_write_open(&fp, path); + // file_buffer_on(&fp); + if (IM_IS_GS(img)) { + const int row_bytes = (((rect.w * 8) + 31) / 32) * 4; + const int data_size = (row_bytes * rect.h); + const int waste = (row_bytes / sizeof(uint8_t)) - rect.w; + // File Header (14 bytes) + write_byte(&fp, 'B'); + write_byte(&fp, 'M'); + write_long(&fp, 14 + 40 + 1024 + data_size); + write_word(&fp, 0); + write_word(&fp, 0); + write_long(&fp, 14 + 40 + 1024); + // Info Header (40 bytes) + write_long(&fp, 40); + write_long(&fp, rect.w); + write_long(&fp, -rect.h); // store the image flipped (correctly) + write_word(&fp, 1); + write_word(&fp, 8); + write_long(&fp, 0); + write_long(&fp, data_size); + write_long(&fp, 0); + write_long(&fp, 0); + write_long(&fp, 0); + write_long(&fp, 0); + // Color Table (1024 bytes) + for (int i = 0; i < 256; i++) { + write_long(&fp, ((i) << 16) | ((i) << 8) | i); + } + if ((rect.x == 0) && (rect.w == img->w) && (img->w == row_bytes)) { + write_data(&fp, // Super Fast - Zoom, Zoom! + img->pixels + (rect.y * img->w), + rect.w * rect.h); + } else { + for (int i = 0; i < rect.h; i++) { + write_data(&fp, img->pixels+((rect.y+i)*img->w)+rect.x, rect.w); + for (int j = 0; j < waste; j++) { + write_byte(&fp, 0); + } + } + } + } else if(IM_IS_RGB565(img)) { + const int row_bytes = (((rect.w * 16) + 31) / 32) * 4; + const int data_size = (row_bytes * rect.h); + const int waste = (row_bytes / sizeof(uint16_t)) - rect.w; + // File Header (14 bytes) + write_byte(&fp, 'B'); //表示文件类型 + write_byte(&fp, 'M'); + write_long(&fp, 14 + 40 + 12 + data_size); //表示文件的大小 + write_word(&fp, 0); //保留位,必须设置为0; + write_word(&fp, 0); //保留位,必须设置为0; + write_long(&fp, 14 + 40 + 12); //4字节的偏移,表示从文件头到位图数据的偏移 + // Info Header (40 bytes) + write_long(&fp, 40); //信息头的大小,即40; + write_long(&fp, rect.w); //以像素为单位说明图像的宽度; + write_long(&fp, -rect.h); // store the image flipped (correctly) //以像素为单位说明图像的高度,同时如果为正,说明位图倒立 + write_word(&fp, 1); //为目标设备说明颜色平面数,总被设置为1; + write_word(&fp, 16); //说明比特数/像素数,值有1、2、4、8、16、24、32; + write_long(&fp, 3); //说明图像的压缩类型,最常用的就是0(BI_RGB),表示不压缩; + write_long(&fp, data_size); //说明位图数据的大小,当用BI_RGB格式时,可以设置为0 + write_long(&fp, 0); //表示水平分辨率,单位是像素/米,有符号整数; + write_long(&fp, 0); //表示垂直分辨率,单位是像素/米,有符号整数; + write_long(&fp, 0); //说明位图使用的调色板中的颜色索引数,为0说明使用所有; + write_long(&fp, 0); //说明对图像显示有重要影响的颜色索引数,为0说明都重要; + // Bit Masks (12 bytes) + write_long(&fp, 0x1F << 11); //调色板 + write_long(&fp, 0x3F << 5); + write_long(&fp, 0x1F); + for (int i = 0; i < rect.h; i++) { + for (int j = 0; j < rect.w; j++) { + write_word(&fp, IM_GET_RGB565_PIXEL(img, (rect.x + j), (rect.y + i))); + } + for (int j = 0; j < waste; j++) { + write_word(&fp, 0); + } + } + }else if(IM_IS_RGB888(img)) { + const int row_bytes = (((rect.w * 24) + 31) / 32) * 4; + const int data_size = (row_bytes * rect.h); + const int waste = row_bytes - rect.w * 24; + // File Header (14 bytes) + write_byte(&fp, 'B'); //0x42 + write_byte(&fp, 'M'); //0x4d + write_long(&fp, 14 + 40 + data_size); + write_word(&fp, 0); + write_word(&fp, 0); + write_long(&fp, 14 + 40); + // Info Header (40 bytes) + write_long(&fp, 40); + write_long(&fp, rect.w); + write_long(&fp, -rect.h); // store the image flipped (correctly) + write_word(&fp, 1); + write_word(&fp, 24); + write_long(&fp, 0); //说明图像的压缩类型,最常用的就是0(BI_RGB),表示不压缩; + write_long(&fp, data_size); + write_long(&fp, 0); + write_long(&fp, 0); + write_long(&fp, 0); + write_long(&fp, 0); + for (int i = 0; i < rect.h; i++) { + for (int j = 0; j < rect.w; j++) { + pixel24_t pixel_tmp = IM_GET_RGB888_PIXEL_(img, (rect.x + j), (rect.y + i)); + int tmp_r = pixel_tmp.red; + pixel_tmp.red = pixel_tmp.blue; + pixel_tmp.blue = tmp_r; + write_data(&fp, &pixel_tmp, 3); + } + for (int j = 0; j < waste; j++) { + write_byte(&fp, 0); + } + } + } + // file_buffer_off(&fp); + + file_close(&fp); +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO diff --git a/github_source/minicv2/src/clahe.c b/github_source/minicv2/src/clahe.c new file mode 100644 index 0000000..d65e298 --- /dev/null +++ b/github_source/minicv2/src/clahe.c @@ -0,0 +1,450 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Contrast Limited Adaptive Histogram Equalization. + */ +#include "imlib.h" +#define BYTE_IMAGE + +/* + * ANSI C code from the article + * "Contrast Limited Adaptive Histogram Equalization" + * by Karel Zuiderveld, karel@cv.ruu.nl + * in "Graphics Gems IV", Academic Press, 1994 + * + * + * These functions implement Contrast Limited Adaptive Histogram Equalization. + * The main routine (CLAHE) expects an input image that is stored contiguously in + * memory; the CLAHE output image overwrites the original input image and has the + * same minimum and maximum values (which must be provided by the user). + * This implementation assumes that the X- and Y image resolutions are an integer + * multiple of the X- and Y sizes of the contextual regions. A check on various other + * error conditions is performed. + * + * #define the symbol BYTE_IMAGE to make this implementation suitable for + * 8-bit images. The maximum number of contextual regions can be redefined + * by changing uiMAX_REG_X and/or uiMAX_REG_Y; the use of more than 256 + * contextual regions is not recommended. + * + * The code is ANSI-C and is also C++ compliant. + * + * Author: Karel Zuiderveld, Computer Vision Research Group, + * Utrecht, The Netherlands (karel@cv.ruu.nl) + */ + +#ifdef BYTE_IMAGE +typedef unsigned char kz_pixel_t; /* for 8 bit-per-pixel images */ +#define uiNR_OF_GREY (256) +#else +typedef unsigned short kz_pixel_t; /* for 12 bit-per-pixel images (default) */ +# define uiNR_OF_GREY (4096) +#endif + +/******** Prototype of CLAHE function. Put this in a separate include file. *****/ +int CLAHE(kz_pixel_t* pImage, unsigned int uiXRes, unsigned int uiYRes, kz_pixel_t Min, + kz_pixel_t Max, unsigned int uiNrX, unsigned int uiNrY, + unsigned int uiNrBins, float fCliplimit); + +/*********************** Local prototypes ************************/ +static void ClipHistogram (unsigned long*, unsigned int, unsigned long); +static void MakeHistogram (kz_pixel_t*, unsigned int, unsigned int, unsigned int, + unsigned long*, unsigned int, kz_pixel_t*); +static void MapHistogram (unsigned long*, kz_pixel_t, kz_pixel_t, + unsigned int, unsigned long); +static void MakeLut (kz_pixel_t*, kz_pixel_t, kz_pixel_t, unsigned int); +static void Interpolate (kz_pixel_t*, int, unsigned long*, unsigned long*, + unsigned long*, unsigned long*, unsigned int, unsigned int, kz_pixel_t*); + +/************** Start of actual code **************/ +const unsigned int uiMAX_REG_X = 16; /* max. # contextual regions in x-direction */ +const unsigned int uiMAX_REG_Y = 16; /* max. # contextual regions in y-direction */ + +/************************** main function CLAHE ******************/ +int CLAHE (kz_pixel_t* pImage, unsigned int uiXRes, unsigned int uiYRes, + kz_pixel_t Min, kz_pixel_t Max, unsigned int uiNrX, unsigned int uiNrY, + unsigned int uiNrBins, float fCliplimit) +/* pImage - Pointer to the input/output image + * uiXRes - Image resolution in the X direction + * uiYRes - Image resolution in the Y direction + * Min - Minimum greyvalue of input image (also becomes minimum of output image) + * Max - Maximum greyvalue of input image (also becomes maximum of output image) + * uiNrX - Number of contextial regions in the X direction (min 2, max uiMAX_REG_X) + * uiNrY - Number of contextial regions in the Y direction (min 2, max uiMAX_REG_Y) + * uiNrBins - Number of greybins for histogram ("dynamic range") + * float fCliplimit - Normalized cliplimit (higher values give more contrast) + * The number of "effective" greylevels in the output image is set by uiNrBins; selecting + * a small value (eg. 128) speeds up processing and still produce an output image of + * good quality. The output image will have the same minimum and maximum value as the input + * image. A clip limit smaller than 1 results in standard (non-contrast limited) AHE. + */ +{ + unsigned int uiX, uiY; /* counters */ + unsigned int uiXSize, uiYSize, uiSubX, uiSubY; /* size of context. reg. and subimages */ + unsigned int uiXL, uiXR, uiYU, uiYB; /* auxiliary variables interpolation routine */ + unsigned long ulClipLimit, ulNrPixels;/* clip limit and region pixel count */ + kz_pixel_t* pImPointer; /* pointer to image */ + kz_pixel_t aLUT[uiNR_OF_GREY]; /* lookup table used for scaling of input image */ + unsigned long* pulHist, *pulMapArray; /* pointer to histogram and mappings*/ + unsigned long* pulLU, *pulLB, *pulRU, *pulRB; /* auxiliary pointers interpolation */ + + if (uiNrX > uiMAX_REG_X) return -1; /* # of regions x-direction too large */ + if (uiNrY > uiMAX_REG_Y) return -2; /* # of regions y-direction too large */ + if (uiXRes % uiNrX) return -3; /* x-resolution no multiple of uiNrX */ + if (uiYRes % uiNrY) return -4; /* y-resolution no multiple of uiNrY */ + if (Max >= uiNR_OF_GREY) return -5; /* maximum too large */ + if (Min >= Max) return -6; /* minimum equal or larger than maximum */ + if (uiNrX < 2 || uiNrY < 2) return -7;/* at least 4 contextual regions required */ + if (fCliplimit == 1.0) return 0; /* is OK, immediately returns original image. */ + if (uiNrBins == 0) uiNrBins = 128; /* default value when not specified */ + + pulMapArray=(unsigned long *)xalloc(sizeof(unsigned long)*uiNrX*uiNrY*uiNrBins); + if (pulMapArray == 0) return -8; /* Not enough memory! (try reducing uiNrBins) */ + + uiXSize = uiXRes/uiNrX; uiYSize = uiYRes/uiNrY; /* Actual size of contextual regions */ + ulNrPixels = (unsigned long)uiXSize * (unsigned long)uiYSize; + + if(fCliplimit > 0.0) { /* Calculate actual cliplimit */ + ulClipLimit = (unsigned long) (fCliplimit * (uiXSize * uiYSize) / uiNrBins); + ulClipLimit = (ulClipLimit < 1UL) ? 1UL : ulClipLimit; + } + else ulClipLimit = 1UL<<14; /* Large value, do not clip (AHE) */ + MakeLut(aLUT, Min, Max, uiNrBins); /* Make lookup table for mapping of greyvalues */ + /* Calculate greylevel mappings for each contextual region */ + for (uiY = 0, pImPointer = pImage; uiY < uiNrY; uiY++) { + for (uiX = 0; uiX < uiNrX; uiX++, pImPointer += uiXSize) { + pulHist = &pulMapArray[uiNrBins * (uiY * uiNrX + uiX)]; + MakeHistogram(pImPointer,uiXRes,uiXSize,uiYSize,pulHist,uiNrBins,aLUT); + ClipHistogram(pulHist, uiNrBins, ulClipLimit); + MapHistogram(pulHist, Min, Max, uiNrBins, ulNrPixels); + } + pImPointer += (uiYSize - 1) * uiXRes; /* skip lines, set pointer */ + } + + /* Interpolate greylevel mappings to get CLAHE image */ + for (pImPointer = pImage, uiY = 0; uiY <= uiNrY; uiY++) { + if (uiY == 0) { /* special case: top row */ + uiSubY = uiYSize >> 1; uiYU = 0; uiYB = 0; + } + else { + if (uiY == uiNrY) { /* special case: bottom row */ + uiSubY = (uiYSize+1) >> 1; uiYU = uiNrY-1; uiYB = uiYU; + } + else { /* default values */ + uiSubY = uiYSize; uiYU = uiY - 1; uiYB = uiYU + 1; + } + } + for (uiX = 0; uiX <= uiNrX; uiX++) { + if (uiX == 0) { /* special case: left column */ + uiSubX = uiXSize >> 1; uiXL = 0; uiXR = 0; + } + else { + if (uiX == uiNrX) { /* special case: right column */ + uiSubX = (uiXSize+1) >> 1; uiXL = uiNrX - 1; uiXR = uiXL; + } + else { /* default values */ + uiSubX = uiXSize; uiXL = uiX - 1; uiXR = uiXL + 1; + } + } + + pulLU = &pulMapArray[uiNrBins * (uiYU * uiNrX + uiXL)]; + pulRU = &pulMapArray[uiNrBins * (uiYU * uiNrX + uiXR)]; + pulLB = &pulMapArray[uiNrBins * (uiYB * uiNrX + uiXL)]; + pulRB = &pulMapArray[uiNrBins * (uiYB * uiNrX + uiXR)]; + Interpolate(pImPointer,uiXRes,pulLU,pulRU,pulLB,pulRB,uiSubX,uiSubY,aLUT); + pImPointer += uiSubX; /* set pointer on next matrix */ + } + pImPointer += (uiSubY - 1) * uiXRes; + } + xfree(pulMapArray); /* free space for histograms */ + return 0; /* return status OK */ +} + +void ClipHistogram (unsigned long* pulHistogram, unsigned int + uiNrGreylevels, unsigned long ulClipLimit) +/* This function performs clipping of the histogram and redistribution of bins. + * The histogram is clipped and the number of excess pixels is counted. Afterwards + * the excess pixels are equally redistributed across the whole histogram (providing + * the bin count is smaller than the cliplimit). + */ +{ + unsigned long* pulBinPointer, *pulEndPointer, *pulHisto; + unsigned long ulNrExcess, ulUpper, ulBinIncr, ulStepSize, i; + long lBinExcess; + + ulNrExcess = 0; pulBinPointer = pulHistogram; + for (i = 0; i < uiNrGreylevels; i++) { /* calculate total number of excess pixels */ + lBinExcess = (long) pulBinPointer[i] - (long) ulClipLimit; + if (lBinExcess > 0) ulNrExcess += lBinExcess; /* excess in current bin */ + }; + + /* Second part: clip histogram and redistribute excess pixels in each bin */ + ulBinIncr = ulNrExcess / uiNrGreylevels; /* average binincrement */ + ulUpper = ulClipLimit - ulBinIncr; /* Bins larger than ulUpper set to cliplimit */ + + for (i = 0; i < uiNrGreylevels; i++) { + if (pulHistogram[i] > ulClipLimit) pulHistogram[i] = ulClipLimit; /* clip bin */ + else { + if (pulHistogram[i] > ulUpper) { /* high bin count */ + ulNrExcess -= pulHistogram[i] - ulUpper; pulHistogram[i]=ulClipLimit; + } + else { /* low bin count */ + ulNrExcess -= ulBinIncr; pulHistogram[i] += ulBinIncr; + } + } + } + + while (ulNrExcess) { /* Redistribute remaining excess */ + pulEndPointer = &pulHistogram[uiNrGreylevels]; pulHisto = pulHistogram; + + while (ulNrExcess && pulHisto < pulEndPointer) { + ulStepSize = uiNrGreylevels / ulNrExcess; + if (ulStepSize < 1) ulStepSize = 1; /* stepsize at least 1 */ + for (pulBinPointer=pulHisto; pulBinPointer < pulEndPointer && ulNrExcess; + pulBinPointer += ulStepSize) { + if (*pulBinPointer < ulClipLimit) { + (*pulBinPointer)++; ulNrExcess--; /* reduce excess */ + } + } + pulHisto++; /* restart redistributing on other bin location */ + } + } +} + +void MakeHistogram (kz_pixel_t* pImage, unsigned int uiXRes, + unsigned int uiSizeX, unsigned int uiSizeY, + unsigned long* pulHistogram, + unsigned int uiNrGreylevels, kz_pixel_t* pLookupTable) +/* This function classifies the greylevels present in the array image into + * a greylevel histogram. The pLookupTable specifies the relationship + * between the greyvalue of the pixel (typically between 0 and 4095) and + * the corresponding bin in the histogram (usually containing only 128 bins). + */ +{ + kz_pixel_t* pImagePointer; + unsigned int i; + + for (i = 0; i < uiNrGreylevels; i++) pulHistogram[i] = 0L; /* clear histogram */ + + for (i = 0; i < uiSizeY; i++) { + pImagePointer = &pImage[uiSizeX]; + while (pImage < pImagePointer) pulHistogram[pLookupTable[*pImage++]]++; + pImagePointer += uiXRes; + pImage = &pImagePointer[-(int)uiSizeX]; /* go to bdeginning of next row */ + } +} + +void MapHistogram (unsigned long* pulHistogram, kz_pixel_t Min, kz_pixel_t Max, + unsigned int uiNrGreylevels, unsigned long ulNrOfPixels) +/* This function calculates the equalized lookup table (mapping) by + * cumulating the input histogram. Note: lookup table is rescaled in range [Min..Max]. + */ +{ + unsigned int i; unsigned long ulSum = 0; + const float fScale = ((float)(Max - Min)) / ulNrOfPixels; + const unsigned long ulMin = (unsigned long) Min; + + for (i = 0; i < uiNrGreylevels; i++) { + ulSum += pulHistogram[i]; pulHistogram[i]=(unsigned long)(ulMin+ulSum*fScale); + if (pulHistogram[i] > Max) pulHistogram[i] = Max; + } +} + +void MakeLut (kz_pixel_t * pLUT, kz_pixel_t Min, kz_pixel_t Max, unsigned int uiNrBins) +/* To speed up histogram clipping, the input image [Min,Max] is scaled down to + * [0,uiNrBins-1]. This function calculates the LUT. + */ +{ + int i; + const kz_pixel_t BinSize = (kz_pixel_t) (1 + (Max - Min) / uiNrBins); + + for (i = Min; i <= Max; i++) pLUT[i] = (i - Min) / BinSize; +} + +void Interpolate (kz_pixel_t * pImage, int uiXRes, unsigned long * pulMapLU, + unsigned long * pulMapRU, unsigned long * pulMapLB, unsigned long * pulMapRB, + unsigned int uiXSize, unsigned int uiYSize, kz_pixel_t * pLUT) +/* pImage - pointer to input/output image + * uiXRes - resolution of image in x-direction + * pulMap* - mappings of greylevels from histograms + * uiXSize - uiXSize of image submatrix + * uiYSize - uiYSize of image submatrix + * pLUT - lookup table containing mapping greyvalues to bins + * This function calculates the new greylevel assignments of pixels within a submatrix + * of the image with size uiXSize and uiYSize. This is done by a bilinear interpolation + * between four different mappings in order to eliminate boundary artifacts. + * It uses a division; since division is often an expensive operation, I added code to + * perform a logical shift instead when feasible. + */ +{ + const unsigned int uiIncr = uiXRes-uiXSize; /* Pointer increment after processing row */ + kz_pixel_t GreyValue; unsigned int uiNum = uiXSize*uiYSize; /* Normalization factor */ + + unsigned int uiXCoef, uiYCoef, uiXInvCoef, uiYInvCoef, uiShift = 0; + + if (uiNum & (uiNum - 1)) /* If uiNum is not a power of two, use division */ + for (uiYCoef = 0, uiYInvCoef = uiYSize; uiYCoef < uiYSize; + uiYCoef++, uiYInvCoef--,pImage+=uiIncr) { + for (uiXCoef = 0, uiXInvCoef = uiXSize; uiXCoef < uiXSize; + uiXCoef++, uiXInvCoef--) { + GreyValue = pLUT[*pImage]; /* get histogram bin value */ + *pImage++ = (kz_pixel_t ) ((uiYInvCoef * (uiXInvCoef*pulMapLU[GreyValue] + + uiXCoef * pulMapRU[GreyValue]) + + uiYCoef * (uiXInvCoef * pulMapLB[GreyValue] + + uiXCoef * pulMapRB[GreyValue])) / uiNum); + } + } + else { /* avoid the division and use a right shift instead */ + while (uiNum >>= 1) uiShift++; /* Calculate 2log of uiNum */ + for (uiYCoef = 0, uiYInvCoef = uiYSize; uiYCoef < uiYSize; + uiYCoef++, uiYInvCoef--,pImage+=uiIncr) { + for (uiXCoef = 0, uiXInvCoef = uiXSize; uiXCoef < uiXSize; + uiXCoef++, uiXInvCoef--) { + GreyValue = pLUT[*pImage]; /* get histogram bin value */ + *pImage++ = (kz_pixel_t)((uiYInvCoef* (uiXInvCoef * pulMapLU[GreyValue] + + uiXCoef * pulMapRU[GreyValue]) + + uiYCoef * (uiXInvCoef * pulMapLB[GreyValue] + + uiXCoef * pulMapRB[GreyValue])) >> uiShift); + } + } + } +} + +void imlib_clahe_histeq(image_t *img, float clip_limit, image_t *mask) +{ + int xTileSize = IM_MAX(uiMAX_REG_X >> (10 - IM_MIN(IM_LOG2_32(img->w), 10)), 2); + int yTileSize = IM_MAX(uiMAX_REG_Y >> (10 - IM_MIN(IM_LOG2_32(img->h), 10)), 2); + int pImageW = img->w + ((img->w % xTileSize) ? (xTileSize - (img->w % xTileSize)) : 0); + int pImageH = img->h + ((img->h % yTileSize) ? (yTileSize - (img->h % yTileSize)) : 0); + int xOffset = (pImageW - img->w) / 2; + int yOffset = (pImageH - img->h) / 2; + + image_t temp; + temp.w = img->w; + temp.h = img->h; + temp.pixfmt = img->pixfmt; + temp.data = xalloc0(pImageW * pImageH * sizeof(kz_pixel_t)); + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset, + COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x))); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset, + IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)); + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset, + COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x))); + } + } + break; + } + case PIXFORMAT_RGB888: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset, + COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x))); + } + } + break; + } + default: { + break; + } + } + + CLAHE((kz_pixel_t *) temp.data, + pImageW, pImageH, + COLOR_GRAYSCALE_MIN, COLOR_GRAYSCALE_MAX, + xTileSize, yTileSize, + COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1, + clip_limit); + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, + COLOR_GRAYSCALE_TO_BINARY(IMAGE_GET_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset))); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, + IMAGE_GET_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset)); + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, + imlib_yuv_to_rgb(IMAGE_GET_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset), + COLOR_RGB565_TO_U(pixel), + COLOR_RGB565_TO_V(pixel))); + } + } + break; + } + case PIXFORMAT_RGB888: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *clahe_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&temp, y + yOffset); + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr, x, + imlib_yuv_to_rgb888(IMAGE_GET_GRAYSCALE_PIXEL_FAST(clahe_row_ptr, x + xOffset), + COLOR_RGB888_TO_U(pixel), + COLOR_RGB888_TO_V(pixel))); + } + } + break; + } + default: { + break; + } + } + + xfree(temp.data); +} diff --git a/github_source/minicv2/src/collections.c b/github_source/minicv2/src/collections.c new file mode 100644 index 0000000..84da627 --- /dev/null +++ b/github_source/minicv2/src/collections.c @@ -0,0 +1,535 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Common data structures. + */ +#include "imlib.h" +#define CHAR_BITS (sizeof(char) * 8) +#define CHAR_MASK (CHAR_BITS - 1) +#define CHAR_SHIFT IM_LOG2(CHAR_MASK) + +//////////// +// bitmap // +//////////// + +void bitmap_alloc(bitmap_t *ptr, size_t size) +{ + ptr->size = size; + ptr->data = (char *) fb_alloc0(((size + CHAR_MASK) >> CHAR_SHIFT) * sizeof(char), FB_ALLOC_NO_HINT); +} + +void bitmap_free(bitmap_t *ptr) +{ + if (ptr->data) { + fb_free(ptr->data); + } +} + +void bitmap_clear(bitmap_t *ptr) +{ + memset(ptr->data, 0, ((ptr->size + CHAR_MASK) >> CHAR_SHIFT) * sizeof(char)); +} + +void bitmap_bit_set(bitmap_t *ptr, size_t index) +{ + ptr->data[index >> CHAR_SHIFT] |= 1 << (index & CHAR_MASK); +} + +bool bitmap_bit_get(bitmap_t *ptr, size_t index) +{ + return (ptr->data[index >> CHAR_SHIFT] >> (index & CHAR_MASK)) & 1; +} + +////////// +// lifo // +////////// + +void lifo_alloc(lifo_t *ptr, size_t size, size_t data_len) +{ + ptr->len = 0; + ptr->size = size; + ptr->data_len = data_len; + ptr->data = (char *) fb_alloc(size * data_len, FB_ALLOC_NO_HINT); +} + +void lifo_alloc_all(lifo_t *ptr, size_t *size, size_t data_len) +{ + uint32_t tmp_size; + ptr->data = (char *) fb_alloc_all(&tmp_size, FB_ALLOC_NO_HINT); + ptr->data_len = data_len; + ptr->size = tmp_size / data_len; + ptr->len = 0; + *size = ptr->size; +} + +void lifo_free(lifo_t *ptr) +{ + if (ptr->data) { + fb_free(ptr->data); + } +} + +void lifo_clear(lifo_t *ptr) +{ + ptr->len = 0; +} + +size_t lifo_size(lifo_t *ptr) +{ + return ptr->len; +} + +bool lifo_is_not_empty(lifo_t *ptr) +{ + return ptr->len; +} + +bool lifo_is_not_full(lifo_t *ptr) +{ + return ptr->len != ptr->size; +} + +void lifo_enqueue(lifo_t *ptr, void *data) +{ + memcpy(ptr->data + (ptr->len * ptr->data_len), data, ptr->data_len); + + ptr->len += 1; +} + +void lifo_dequeue(lifo_t *ptr, void *data) +{ + if (data) { + memcpy(data, ptr->data + ((ptr->len - 1) * ptr->data_len), ptr->data_len); + } + + ptr->len -= 1; +} + +void lifo_poke(lifo_t *ptr, void *data) +{ + memcpy(ptr->data + (ptr->len * ptr->data_len), data, ptr->data_len); +} + +void lifo_peek(lifo_t *ptr, void *data) +{ + memcpy(data, ptr->data + ((ptr->len - 1) * ptr->data_len), ptr->data_len); +} + +////////// +// fifo // +////////// + +void fifo_alloc(fifo_t *ptr, size_t size, size_t data_len) +{ + ptr->head_ptr = 0; + ptr->tail_ptr = 0; + ptr->len = 0; + ptr->size = size; + ptr->data_len = data_len; + ptr->data = (char *) fb_alloc(size * data_len, FB_ALLOC_NO_HINT); +} + +void fifo_alloc_all(fifo_t *ptr, size_t *size, size_t data_len) +{ + uint32_t tmp_size; + ptr->data = (char *) fb_alloc_all(&tmp_size, FB_ALLOC_NO_HINT); + ptr->data_len = data_len; + ptr->size = tmp_size / data_len; + ptr->len = 0; + ptr->tail_ptr = 0; + ptr->head_ptr = 0; + *size = ptr->size; +} + +void fifo_free(fifo_t *ptr) +{ + if (ptr->data) { + fb_free(ptr->data); + } +} + +void fifo_clear(fifo_t *ptr) +{ + ptr->head_ptr = 0; + ptr->tail_ptr = 0; + ptr->len = 0; +} + +size_t fifo_size(fifo_t *ptr) +{ + return ptr->len; +} + +bool fifo_is_not_empty(fifo_t *ptr) +{ + return ptr->len; +} + +bool fifo_is_not_full(fifo_t *ptr) +{ + return ptr->len != ptr->size; +} + +void fifo_enqueue(fifo_t *ptr, void *data) +{ + memcpy(ptr->data + (ptr->head_ptr * ptr->data_len), data, ptr->data_len); + + size_t temp = ptr->head_ptr + 1; + + if (temp == ptr->size) { + temp = 0; + } + + ptr->head_ptr = temp; + ptr->len += 1; +} + +void fifo_dequeue(fifo_t *ptr, void *data) +{ + if (data) { + memcpy(data, ptr->data + (ptr->tail_ptr * ptr->data_len), ptr->data_len); + } + + size_t temp = ptr->tail_ptr + 1; + + if (temp == ptr->size) { + temp = 0; + } + + ptr->tail_ptr = temp; + ptr->len -= 1; +} + +void fifo_poke(fifo_t *ptr, void *data) +{ + memcpy(ptr->data + (ptr->head_ptr * ptr->data_len), data, ptr->data_len); +} + +void fifo_peek(fifo_t *ptr, void *data) +{ + memcpy(data, ptr->data + (ptr->tail_ptr * ptr->data_len), ptr->data_len); +} + +////////// +// list // +////////// + +void imlib_list_init(list_t *ptr, size_t data_len) +{ + ptr->head_ptr = NULL; + ptr->tail_ptr = NULL; + ptr->size = 0; + ptr->data_len = data_len; +} +void list_init(list_t *ptr, size_t data_len) +{ + ptr->head_ptr = NULL; + ptr->tail_ptr = NULL; + ptr->size = 0; + ptr->data_len = data_len; +} + +void list_copy(list_t *dst, list_t *src) +{ + memcpy(dst, src, sizeof(list_t)); +} + +void list_free(list_t *ptr) +{ + for (list_lnk_t *i = ptr->head_ptr; i; ) { + list_lnk_t *j = i->next_ptr; + xfree(i); + i = j; + } +} + +void list_clear(list_t *ptr) +{ + list_free(ptr); + + ptr->head_ptr = NULL; + ptr->tail_ptr = NULL; + ptr->size = 0; +} + +size_t list_size(list_t *ptr) +{ + return ptr->size; +} + +void list_push_front(list_t *ptr, void *data) +{ + list_lnk_t *tmp = (list_lnk_t *) xalloc(sizeof(list_lnk_t) + ptr->data_len); + memcpy(tmp->data, data, ptr->data_len); + + if (ptr->size++) { + tmp->next_ptr = ptr->head_ptr; + tmp->prev_ptr = NULL; + ptr->head_ptr->prev_ptr = tmp; + ptr->head_ptr = tmp; + } else { + tmp->next_ptr = NULL; + tmp->prev_ptr = NULL; + ptr->head_ptr = tmp; + ptr->tail_ptr = tmp; + } +} + +void list_push_back(list_t *ptr, void *data) +{ + list_lnk_t *tmp = (list_lnk_t *) xalloc(sizeof(list_lnk_t) + ptr->data_len); + memcpy(tmp->data, data, ptr->data_len); + + if (ptr->size++) { + tmp->next_ptr = NULL; + tmp->prev_ptr = ptr->tail_ptr; + ptr->tail_ptr->next_ptr = tmp; + ptr->tail_ptr = tmp; + } else { + tmp->next_ptr = NULL; + tmp->prev_ptr = NULL; + ptr->head_ptr = tmp; + ptr->tail_ptr = tmp; + } +} + +void list_pop_front(list_t *ptr, void *data) +{ + list_lnk_t *tmp = ptr->head_ptr; + + if (data) { + memcpy(data, tmp->data, ptr->data_len); + } + + if (tmp->next_ptr) { + tmp->next_ptr->prev_ptr = NULL; + } + ptr->head_ptr = tmp->next_ptr; + ptr->size -= 1; + xfree(tmp); +} + +void list_pop_back(list_t *ptr, void *data) +{ + list_lnk_t *tmp = ptr->tail_ptr; + + if (data) { + memcpy(data, tmp->data, ptr->data_len); + } + + tmp->prev_ptr->next_ptr = NULL; + ptr->tail_ptr = tmp->prev_ptr; + ptr->size -= 1; + xfree(tmp); +} + +void list_get_front(list_t *ptr, void *data) +{ + memcpy(data, ptr->head_ptr->data, ptr->data_len); +} + +void list_get_back(list_t *ptr, void *data) +{ + memcpy(data, ptr->tail_ptr->data, ptr->data_len); +} + +void list_set_front(list_t *ptr, void *data) +{ + memcpy(ptr->head_ptr->data, data, ptr->data_len); +} + +void list_set_back(list_t *ptr, void *data) +{ + memcpy(ptr->tail_ptr->data, data, ptr->data_len); +} + +void list_insert(list_t *ptr, void *data, size_t index) +{ + if (index == 0) { + list_push_front(ptr, data); + } else if (index >= ptr->size) { + list_push_back(ptr, data); + } else if (index < (ptr->size >> 1)) { + + list_lnk_t *i = ptr->head_ptr; + + while (index) { + i = i->next_ptr; + index -= 1; + } + + list_lnk_t *tmp = (list_lnk_t *) xalloc(sizeof(list_lnk_t) + ptr->data_len); + memcpy(tmp->data, data, ptr->data_len); + + tmp->next_ptr = i; + tmp->prev_ptr = i->prev_ptr; + i->prev_ptr->next_ptr = tmp; + i->prev_ptr = tmp; + ptr->size += 1; + + } else { + + list_lnk_t *i = ptr->tail_ptr; + index = ptr->size - index - 1; + + while (index) { + i = i->prev_ptr; + index -= 1; + } + + list_lnk_t *tmp = (list_lnk_t *) xalloc(sizeof(list_lnk_t) + ptr->data_len); + memcpy(tmp->data, data, ptr->data_len); + + tmp->next_ptr = i; + tmp->prev_ptr = i->prev_ptr; + i->prev_ptr->next_ptr = tmp; + i->prev_ptr = tmp; + ptr->size += 1; + } +} + +void list_remove(list_t *ptr, void *data, size_t index) +{ + if (index == 0) { + list_pop_front(ptr, data); + } else if (index >= (ptr->size - 1)) { + list_pop_back(ptr, data); + } else if (index < (ptr->size >> 1)) { + + list_lnk_t *i = ptr->head_ptr; + + while (index) { + i = i->next_ptr; + index -= 1; + } + + if (data) { + memcpy(data, i->data, ptr->data_len); + } + + i->prev_ptr->next_ptr = i->next_ptr; + i->next_ptr->prev_ptr = i->prev_ptr; + ptr->size -= 1; + xfree(i); + + } else { + + list_lnk_t *i = ptr->tail_ptr; + index = ptr->size - index - 1; + + while (index) { + i = i->prev_ptr; + index -= 1; + } + + if (data) { + memcpy(data, i->data, ptr->data_len); + } + + i->prev_ptr->next_ptr = i->next_ptr; + i->next_ptr->prev_ptr = i->prev_ptr; + ptr->size -= 1; + xfree(i); + } +} + +void list_get(list_t *ptr, void *data, size_t index) +{ + if (index == 0) { + list_get_front(ptr, data); + } else if (index >= (ptr->size - 1)) { + list_get_back(ptr, data); + } else if (index < (ptr->size >> 1)) { + + list_lnk_t *i = ptr->head_ptr; + + while (index) { + i = i->next_ptr; + index -= 1; + } + + memcpy(data, i->data, ptr->data_len); + + } else { + + list_lnk_t *i = ptr->tail_ptr; + index = ptr->size - index - 1; + + while (index) { + i = i->prev_ptr; + index -= 1; + } + + memcpy(data, i->data, ptr->data_len); + } +} + +void list_set(list_t *ptr, void *data, size_t index) +{ + if (index == 0) { + list_set_front(ptr, data); + } else if (index >= (ptr->size - 1)) { + list_set_back(ptr, data); + } else if (index < (ptr->size >> 1)) { + + list_lnk_t *i = ptr->head_ptr; + + while (index) { + i = i->next_ptr; + index -= 1; + } + + memcpy(i->data, data, ptr->data_len); + + } else { + + list_lnk_t *i = ptr->tail_ptr; + index = ptr->size - index - 1; + + while (index) { + i = i->prev_ptr; + index -= 1; + } + + memcpy(i->data, data, ptr->data_len); + } +} + +////////////// +// iterator // +////////////// + +list_lnk_t *iterator_start_from_head(list_t *ptr) +{ + return ptr->head_ptr; +} + +list_lnk_t *iterator_start_from_tail(list_t *ptr) +{ + return ptr->tail_ptr; +} + +list_lnk_t *iterator_next(list_lnk_t *lnk) +{ + return lnk->next_ptr; +} + +list_lnk_t *iterator_prev(list_lnk_t *lnk) +{ + return lnk->prev_ptr; +} + +void iterator_get(list_t *ptr, list_lnk_t *lnk, void *data) +{ + memcpy(data, lnk->data, ptr->data_len); +} + +void iterator_set(list_t *ptr, list_lnk_t *lnk, void *data) +{ + memcpy(lnk->data, data, ptr->data_len); +} diff --git a/github_source/minicv2/src/dmtx.c b/github_source/minicv2/src/dmtx.c new file mode 100644 index 0000000..ebcbf2d --- /dev/null +++ b/github_source/minicv2/src/dmtx.c @@ -0,0 +1,6413 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Data Matrix Encoding/Decoding Library. + */ +#include +#include +#include "imlib.h" +#ifdef IMLIB_ENABLE_DATAMATRICES +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define perror(str) +#define fprintf(stream, format, ...) +#define free(ptr) ({ umm_free(ptr); }) +#define malloc(size) ({ void *_r = umm_malloc(size); if(!_r) fb_alloc_fail(); _r; }) +#define realloc(ptr, size) ({ void *_r = umm_realloc((ptr), (size)); if(!_r) fb_alloc_fail(); _r; }) +#define calloc(num, item_size) ({ void *_r = umm_calloc((num), (item_size)); if(!_r) fb_alloc_fail(); _r; }) +#define assert(expression) +#define sqrt(x) fast_sqrtf(x) +#define sqrtf(x) fast_sqrtf(x) +#define floor(x) fast_floorf(x) +#define floorf(x) fast_floorf(x) +#define ceil(x) fast_ceilf(x) +#define ceilf(x) fast_ceilf(x) +#define round(x) fast_roundf(x) +#define roundf(x) fast_roundf(x) +#define atan(x) fast_atanf(x) +#define atanf(x) fast_atanf(x) +#define atan2(y, x) fast_atan2f((y), (x)) +#define atan2f(y, x) fast_atan2f((y), (x)) +#define exp(x) fast_expf(x) +#define expf(x) fast_expf(x) +#define cbrt(x) fast_cbrtf(x) +#define cbrtf(x) fast_cbrtf(x) +#define fabs(x) fast_fabsf(x) +#define fabsf(x) fast_fabsf(x) +#define log(x) fast_log(x) +#define logf(x) fast_log(x) +#undef log2 +#define log2(x) fast_log2(x) +#undef log2f +#define log2f(x) fast_log2(x) +#define cos(x) cosf(x) +#define sin(x) sinf(x) +#define acos(x) acosf(x) +#define asin(x) asinf(x) + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtx.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2008, 2009 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file dmtx.h + * \brief Main libdmtx header + */ + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#ifndef M_PI_2 +#define M_PI_2 1.57079632679489661923 +#endif + +#define DmtxVersion "0.7.5" + +#define DmtxUndefined -1 + +#define DmtxPassFail unsigned int +#define DmtxPass 1 +#define DmtxFail 0 + +#define DmtxBoolean unsigned int +#define DmtxTrue 1 +#define DmtxFalse 0 + +#define DmtxFormatMatrix 0 +#define DmtxFormatMosaic 1 + +#define DmtxSymbolSquareCount 24 +#define DmtxSymbolRectCount 6 + +#define DmtxModuleOff 0x00 +#define DmtxModuleOnRed 0x01 +#define DmtxModuleOnGreen 0x02 +#define DmtxModuleOnBlue 0x04 +#define DmtxModuleOnRGB 0x07 /* OnRed | OnGreen | OnBlue */ +#define DmtxModuleOn 0x07 +#define DmtxModuleUnsure 0x08 +#define DmtxModuleAssigned 0x10 +#define DmtxModuleVisited 0x20 +#define DmtxModuleData 0x40 + +#define DMTX_CHECK_BOUNDS(l,i) (assert((i) >= 0 && (i) < (l)->length && (l)->length <= (l)->capacity)) + +typedef enum { + DmtxSchemeAutoFast = -2, + DmtxSchemeAutoBest = -1, + DmtxSchemeAscii = 0, + DmtxSchemeC40, + DmtxSchemeText, + DmtxSchemeX12, + DmtxSchemeEdifact, + DmtxSchemeBase256 +} DmtxScheme; + +typedef enum { + DmtxSymbolRectAuto = -3, + DmtxSymbolSquareAuto = -2, + DmtxSymbolShapeAuto = -1, + DmtxSymbol10x10 = 0, + DmtxSymbol12x12, + DmtxSymbol14x14, + DmtxSymbol16x16, + DmtxSymbol18x18, + DmtxSymbol20x20, + DmtxSymbol22x22, + DmtxSymbol24x24, + DmtxSymbol26x26, + DmtxSymbol32x32, + DmtxSymbol36x36, + DmtxSymbol40x40, + DmtxSymbol44x44, + DmtxSymbol48x48, + DmtxSymbol52x52, + DmtxSymbol64x64, + DmtxSymbol72x72, + DmtxSymbol80x80, + DmtxSymbol88x88, + DmtxSymbol96x96, + DmtxSymbol104x104, + DmtxSymbol120x120, + DmtxSymbol132x132, + DmtxSymbol144x144, + DmtxSymbol8x18, + DmtxSymbol8x32, + DmtxSymbol12x26, + DmtxSymbol12x36, + DmtxSymbol16x36, + DmtxSymbol16x48 +} DmtxSymbolSize; + +typedef enum { + DmtxDirNone = 0x00, + DmtxDirUp = 0x01 << 0, + DmtxDirLeft = 0x01 << 1, + DmtxDirDown = 0x01 << 2, + DmtxDirRight = 0x01 << 3, + DmtxDirHorizontal = DmtxDirLeft | DmtxDirRight, + DmtxDirVertical = DmtxDirUp | DmtxDirDown, + DmtxDirRightUp = DmtxDirRight | DmtxDirUp, + DmtxDirLeftDown = DmtxDirLeft | DmtxDirDown +} DmtxDirection; + +typedef enum { + DmtxSymAttribSymbolRows, + DmtxSymAttribSymbolCols, + DmtxSymAttribDataRegionRows, + DmtxSymAttribDataRegionCols, + DmtxSymAttribHorizDataRegions, + DmtxSymAttribVertDataRegions, + DmtxSymAttribMappingMatrixRows, + DmtxSymAttribMappingMatrixCols, + DmtxSymAttribInterleavedBlocks, + DmtxSymAttribBlockErrorWords, + DmtxSymAttribBlockMaxCorrectable, + DmtxSymAttribSymbolDataWords, + DmtxSymAttribSymbolErrorWords, + DmtxSymAttribSymbolMaxCorrectable +} DmtxSymAttribute; + +typedef enum { + /* Encoding properties */ + DmtxPropScheme = 100, + DmtxPropSizeRequest, + DmtxPropMarginSize, + DmtxPropModuleSize, + /* Decoding properties */ + DmtxPropEdgeMin = 200, + DmtxPropEdgeMax, + DmtxPropScanGap, + DmtxPropSquareDevn, + DmtxPropSymbolSize, + DmtxPropEdgeThresh, + /* Image properties */ + DmtxPropWidth = 300, + DmtxPropHeight, + DmtxPropPixelPacking, + DmtxPropBitsPerPixel, + DmtxPropBytesPerPixel, + DmtxPropRowPadBytes, + DmtxPropRowSizeBytes, + DmtxPropImageFlip, + DmtxPropChannelCount, + /* Image modifiers */ + DmtxPropXmin = 400, + DmtxPropXmax, + DmtxPropYmin, + DmtxPropYmax, + DmtxPropScale +} DmtxProperty; + +typedef enum { + /* Custom format */ + DmtxPackCustom = 100, + /* 1 bpp */ + DmtxPack1bppK = 200, + /* 8 bpp grayscale */ + DmtxPack8bppK = 300, + /* 16 bpp formats */ + DmtxPack16bppRGB = 400, + DmtxPack16bppRGBX, + DmtxPack16bppXRGB, + DmtxPack16bppBGR, + DmtxPack16bppBGRX, + DmtxPack16bppXBGR, + DmtxPack16bppYCbCr, + /* 24 bpp formats */ + DmtxPack24bppRGB = 500, + DmtxPack24bppBGR, + DmtxPack24bppYCbCr, + /* 32 bpp formats */ + DmtxPack32bppRGBX = 600, + DmtxPack32bppXRGB, + DmtxPack32bppBGRX, + DmtxPack32bppXBGR, + DmtxPack32bppCMYK +} DmtxPackOrder; + +typedef enum { + DmtxFlipNone = 0x00, + DmtxFlipX = 0x01 << 0, + DmtxFlipY = 0x01 << 1 +} DmtxFlip; + +typedef float DmtxMatrix3[3][3]; + +/** + * @struct DmtxPixelLoc + * @brief DmtxPixelLoc + */ +typedef struct DmtxPixelLoc_struct { + int X; + int Y; +} DmtxPixelLoc; + +/** + * @struct DmtxVector2 + * @brief DmtxVector2 + */ +typedef struct DmtxVector2_struct { + float X; + float Y; +} DmtxVector2; + +/** + * @struct DmtxRay2 + * @brief DmtxRay2 + */ +typedef struct DmtxRay2_struct { + float tMin; + float tMax; + DmtxVector2 p; + DmtxVector2 v; +} DmtxRay2; + +typedef unsigned char DmtxByte; + +/** + * @struct DmtxByteList + * @brief DmtxByteList + * Use signed int for length fields instead of size_t to play nicely with RS + * arithmetic + */ +typedef struct DmtxByteList_struct DmtxByteList; +struct DmtxByteList_struct +{ + int length; + int capacity; + DmtxByte *b; +}; + +/** + * @struct DmtxImage + * @brief DmtxImage + */ +typedef struct DmtxImage_struct { + int width; + int height; + int pixelPacking; + int bitsPerPixel; + int bytesPerPixel; + int rowPadBytes; + int rowSizeBytes; + int imageFlip; + int channelCount; + int channelStart[4]; + int bitsPerChannel[4]; + unsigned char *pxl; +} DmtxImage; + +/** + * @struct DmtxPointFlow + * @brief DmtxPointFlow + */ +typedef struct DmtxPointFlow_struct { + int plane; + int arrive; + int depart; + int mag; + DmtxPixelLoc loc; +} DmtxPointFlow; + +/** + * @struct DmtxBestLine + * @brief DmtxBestLine + */ +typedef struct DmtxBestLine_struct { + int angle; + int hOffset; + int mag; + int stepBeg; + int stepPos; + int stepNeg; + int distSq; + float devn; + DmtxPixelLoc locBeg; + DmtxPixelLoc locPos; + DmtxPixelLoc locNeg; +} DmtxBestLine; + +/** + * @struct DmtxRegion + * @brief DmtxRegion + */ +typedef struct DmtxRegion_struct { + + /* Trail blazing values */ + int jumpToPos; /* */ + int jumpToNeg; /* */ + int stepsTotal; /* */ + DmtxPixelLoc finalPos; /* */ + DmtxPixelLoc finalNeg; /* */ + DmtxPixelLoc boundMin; /* */ + DmtxPixelLoc boundMax; /* */ + DmtxPointFlow flowBegin; /* */ + + /* Orientation values */ + int polarity; /* */ + int stepR; + int stepT; + DmtxPixelLoc locR; /* remove if stepR works above */ + DmtxPixelLoc locT; /* remove if stepT works above */ + + /* Region fitting values */ + int leftKnown; /* known == 1; unknown == 0 */ + int leftAngle; /* hough angle of left edge */ + DmtxPixelLoc leftLoc; /* known (arbitrary) location on left edge */ + DmtxBestLine leftLine; /* */ + int bottomKnown; /* known == 1; unknown == 0 */ + int bottomAngle; /* hough angle of bottom edge */ + DmtxPixelLoc bottomLoc; /* known (arbitrary) location on bottom edge */ + DmtxBestLine bottomLine; /* */ + int topKnown; /* known == 1; unknown == 0 */ + int topAngle; /* hough angle of top edge */ + DmtxPixelLoc topLoc; /* known (arbitrary) location on top edge */ + int rightKnown; /* known == 1; unknown == 0 */ + int rightAngle; /* hough angle of right edge */ + DmtxPixelLoc rightLoc; /* known (arbitrary) location on right edge */ + + /* Region calibration values */ + int onColor; /* */ + int offColor; /* */ + int sizeIdx; /* Index of arrays that store Data Matrix constants */ + int symbolRows; /* Number of total rows in symbol including alignment patterns */ + int symbolCols; /* Number of total columns in symbol including alignment patterns */ + int mappingRows; /* Number of data rows in symbol */ + int mappingCols; /* Number of data columns in symbol */ + + /* Transform values */ + DmtxMatrix3 raw2fit; /* 3x3 transformation from raw image to fitted barcode grid */ + DmtxMatrix3 fit2raw; /* 3x3 transformation from fitted barcode grid to raw image */ +} DmtxRegion; + +/** + * @struct DmtxMessage + * @brief DmtxMessage + */ +typedef struct DmtxMessage_struct { + size_t arraySize; /* mappingRows * mappingCols */ + size_t codeSize; /* Size of encoded data (data words + error words) */ + size_t outputSize; /* Size of buffer used to hold decoded data */ + int outputIdx; /* Internal index used to store output progress */ + int padCount; + unsigned char *array; /* Pointer to internal representation of Data Matrix modules */ + unsigned char *code; /* Pointer to internal storage of code words (data and error) */ + unsigned char *output; /* Pointer to internal storage of decoded output */ +} DmtxMessage; + +/** + * @struct DmtxScanGrid + * @brief DmtxScanGrid + */ +typedef struct DmtxScanGrid_struct { + /* set once */ + int minExtent; /* Smallest cross size used in scan */ + int maxExtent; /* Size of bounding grid region (2^N - 1) */ + int xOffset; /* Offset to obtain image X coordinate */ + int yOffset; /* Offset to obtain image Y coordinate */ + int xMin; /* Minimum X in image coordinate system */ + int xMax; /* Maximum X in image coordinate system */ + int yMin; /* Minimum Y in image coordinate system */ + int yMax; /* Maximum Y in image coordinate system */ + + /* reset for each level */ + int total; /* Total number of crosses at this size */ + int extent; /* Length/width of cross in pixels */ + int jumpSize; /* Distance in pixels between cross centers */ + int pixelTotal; /* Total pixel count within an individual cross path */ + int startPos; /* X and Y coordinate of first cross center in pattern */ + + /* reset for each cross */ + int pixelCount; /* Progress (pixel count) within current cross pattern */ + int xCenter; /* X center of current cross pattern */ + int yCenter; /* Y center of current cross pattern */ +} DmtxScanGrid; + +/** + * @struct DmtxDecode + * @brief DmtxDecode + */ +typedef struct DmtxDecode_struct { + /* Options */ + int edgeMin; + int edgeMax; + int scanGap; + float squareDevn; + int sizeIdxExpected; + int edgeThresh; + + /* Image modifiers */ + int xMin; + int xMax; + int yMin; + int yMax; + int scale; + + /* Internals */ +/* int cacheComplete; */ + unsigned char *cache; + DmtxImage *image; + DmtxScanGrid grid; +} DmtxDecode; + +/* dmtxdecode.c */ +extern DmtxDecode *dmtxDecodeCreate(DmtxImage *img, int scale); +extern DmtxPassFail dmtxDecodeDestroy(DmtxDecode **dec); +extern DmtxPassFail dmtxDecodeSetProp(DmtxDecode *dec, int prop, int value); +extern int dmtxDecodeGetProp(DmtxDecode *dec, int prop); +extern /*@exposed@*/ unsigned char *dmtxDecodeGetCache(DmtxDecode *dec, int x, int y); +extern DmtxPassFail dmtxDecodeGetPixelValue(DmtxDecode *dec, int x, int y, int channel, /*@out@*/ int *value); +extern DmtxMessage *dmtxDecodeMatrixRegion(DmtxDecode *dec, DmtxRegion *reg, int fix); +extern DmtxMessage *dmtxDecodeMosaicRegion(DmtxDecode *dec, DmtxRegion *reg, int fix); + +/* dmtxregion.c */ +extern DmtxRegion *dmtxRegionCreate(DmtxRegion *reg); +extern DmtxPassFail dmtxRegionDestroy(DmtxRegion **reg); +extern DmtxRegion *dmtxRegionFindNext(DmtxDecode *dec, int max_iterations, int *current_iterations); +extern DmtxRegion *dmtxRegionScanPixel(DmtxDecode *dec, int x, int y); +extern DmtxPassFail dmtxRegionUpdateCorners(DmtxDecode *dec, DmtxRegion *reg, DmtxVector2 p00, + DmtxVector2 p10, DmtxVector2 p11, DmtxVector2 p01); +extern DmtxPassFail dmtxRegionUpdateXfrms(DmtxDecode *dec, DmtxRegion *reg); + +/* dmtxmessage.c */ +extern DmtxMessage *dmtxMessageCreate(int sizeIdx, int symbolFormat); +extern DmtxPassFail dmtxMessageDestroy(DmtxMessage **msg); + +/* dmtximage.c */ +extern DmtxImage *dmtxImageCreate(unsigned char *pxl, int width, int height, int pack); +extern DmtxPassFail dmtxImageDestroy(DmtxImage **img); +extern DmtxPassFail dmtxImageSetChannel(DmtxImage *img, int channelStart, int bitsPerChannel); +extern DmtxPassFail dmtxImageSetProp(DmtxImage *img, int prop, int value); +extern int dmtxImageGetProp(DmtxImage *img, int prop); +extern int dmtxImageGetByteOffset(DmtxImage *img, int x, int y); +extern DmtxPassFail dmtxImageGetPixelValue(DmtxImage *img, int x, int y, int channel, /*@out@*/ int *value); +extern DmtxPassFail dmtxImageSetPixelValue(DmtxImage *img, int x, int y, int channel, int value); +extern DmtxBoolean dmtxImageContainsInt(DmtxImage *img, int margin, int x, int y); +extern DmtxBoolean dmtxImageContainsFloat(DmtxImage *img, float x, float y); + +/* dmtxvector2.c */ +extern DmtxVector2 *dmtxVector2AddTo(DmtxVector2 *v1, const DmtxVector2 *v2); +extern DmtxVector2 *dmtxVector2Add(/*@out@*/ DmtxVector2 *vOut, const DmtxVector2 *v1, const DmtxVector2 *v2); +extern DmtxVector2 *dmtxVector2SubFrom(DmtxVector2 *v1, const DmtxVector2 *v2); +extern DmtxVector2 *dmtxVector2Sub(/*@out@*/ DmtxVector2 *vOut, const DmtxVector2 *v1, const DmtxVector2 *v2); +extern DmtxVector2 *dmtxVector2ScaleBy(DmtxVector2 *v, float s); +extern DmtxVector2 *dmtxVector2Scale(/*@out@*/ DmtxVector2 *vOut, const DmtxVector2 *v, float s); +extern float dmtxVector2Cross(const DmtxVector2 *v1, const DmtxVector2 *v2); +extern float dmtxVector2Norm(DmtxVector2 *v); +extern float dmtxVector2Dot(const DmtxVector2 *v1, const DmtxVector2 *v2); +extern float dmtxVector2Mag(const DmtxVector2 *v); +extern float dmtxDistanceFromRay2(const DmtxRay2 *r, const DmtxVector2 *q); +extern float dmtxDistanceAlongRay2(const DmtxRay2 *r, const DmtxVector2 *q); +extern DmtxPassFail dmtxRay2Intersect(/*@out@*/ DmtxVector2 *point, const DmtxRay2 *p0, const DmtxRay2 *p1); +extern DmtxPassFail dmtxPointAlongRay2(/*@out@*/ DmtxVector2 *point, const DmtxRay2 *r, float t); + +/* dmtxmatrix3.c */ +extern void dmtxMatrix3Copy(/*@out@*/ DmtxMatrix3 m0, DmtxMatrix3 m1); +extern void dmtxMatrix3Identity(/*@out@*/ DmtxMatrix3 m); +extern void dmtxMatrix3Translate(/*@out@*/ DmtxMatrix3 m, float tx, float ty); +extern void dmtxMatrix3Rotate(/*@out@*/ DmtxMatrix3 m, float angle); +extern void dmtxMatrix3Scale(/*@out@*/ DmtxMatrix3 m, float sx, float sy); +extern void dmtxMatrix3Shear(/*@out@*/ DmtxMatrix3 m, float shx, float shy); +extern void dmtxMatrix3LineSkewTop(/*@out@*/ DmtxMatrix3 m, float b0, float b1, float sz); +extern void dmtxMatrix3LineSkewTopInv(/*@out@*/ DmtxMatrix3 m, float b0, float b1, float sz); +extern void dmtxMatrix3LineSkewSide(/*@out@*/ DmtxMatrix3 m, float b0, float b1, float sz); +extern void dmtxMatrix3LineSkewSideInv(/*@out@*/ DmtxMatrix3 m, float b0, float b1, float sz); +extern void dmtxMatrix3Multiply(/*@out@*/ DmtxMatrix3 mOut, DmtxMatrix3 m0, DmtxMatrix3 m1); +extern void dmtxMatrix3MultiplyBy(DmtxMatrix3 m0, DmtxMatrix3 m1); +extern int dmtxMatrix3VMultiply(/*@out@*/ DmtxVector2 *vOut, DmtxVector2 *vIn, DmtxMatrix3 m); +extern int dmtxMatrix3VMultiplyBy(DmtxVector2 *v, DmtxMatrix3 m); +extern void dmtxMatrix3Print(DmtxMatrix3 m); + +/* dmtxsymbol.c */ +extern int dmtxSymbolModuleStatus(DmtxMessage *mapping, int sizeIdx, int row, int col); +extern int dmtxGetSymbolAttribute(int attribute, int sizeIdx); +extern int dmtxGetBlockDataSize(int sizeIdx, int blockIdx); + +/* dmtxbytelist.c */ +extern DmtxByteList dmtxByteListBuild(DmtxByte *storage, int capacity); +extern void dmtxByteListInit(DmtxByteList *list, int length, DmtxByte value, DmtxPassFail *passFail); +extern void dmtxByteListClear(DmtxByteList *list); +extern DmtxBoolean dmtxByteListHasCapacity(DmtxByteList *list); +extern void dmtxByteListCopy(DmtxByteList *dst, const DmtxByteList *src, DmtxPassFail *passFail); +extern void dmtxByteListPush(DmtxByteList *list, DmtxByte value, DmtxPassFail *passFail); +extern DmtxByte dmtxByteListPop(DmtxByteList *list, DmtxPassFail *passFail); +extern void dmtxByteListPrint(DmtxByteList *list, char *prefix); + +extern char *dmtxVersion(void); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtxstatic.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2008, 2009 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file dmtxstatic.h + * \brief Static header + */ + +#define DmtxAlmostZero 0.000001 +#define DmtxAlmostInfinity -1 + +#define DmtxValueC40Latch 230 +#define DmtxValueTextLatch 239 +#define DmtxValueX12Latch 238 +#define DmtxValueEdifactLatch 240 +#define DmtxValueBase256Latch 231 + +#define DmtxValueCTXUnlatch 254 +#define DmtxValueEdifactUnlatch 31 + +#define DmtxValueAsciiPad 129 +#define DmtxValueAsciiUpperShift 235 +#define DmtxValueCTXShift1 0 +#define DmtxValueCTXShift2 1 +#define DmtxValueCTXShift3 2 +#define DmtxValueFNC1 232 +#define DmtxValueStructuredAppend 233 +#define DmtxValue05Macro 236 +#define DmtxValue06Macro 237 +#define DmtxValueECI 241 + +#define DmtxC40TextBasicSet 0 +#define DmtxC40TextShift1 1 +#define DmtxC40TextShift2 2 +#define DmtxC40TextShift3 3 + +#define DmtxUnlatchExplicit 0 +#define DmtxUnlatchImplicit 1 + +#define DmtxChannelValid 0x00 +#define DmtxChannelUnsupportedChar 0x01 << 0 +#define DmtxChannelCannotUnlatch 0x01 << 1 + +#undef min +#define min(X,Y) (((X) < (Y)) ? (X) : (Y)) + +#undef max +#define max(X,Y) (((X) > (Y)) ? (X) : (Y)) + +typedef enum { + DmtxRangeGood, + DmtxRangeBad, + DmtxRangeEnd +} DmtxRange; + +typedef enum { + DmtxEdgeTop = 0x01 << 0, + DmtxEdgeBottom = 0x01 << 1, + DmtxEdgeLeft = 0x01 << 2, + DmtxEdgeRight = 0x01 << 3 +} DmtxEdge; + +typedef enum { + DmtxMaskBit8 = 0x01 << 0, + DmtxMaskBit7 = 0x01 << 1, + DmtxMaskBit6 = 0x01 << 2, + DmtxMaskBit5 = 0x01 << 3, + DmtxMaskBit4 = 0x01 << 4, + DmtxMaskBit3 = 0x01 << 5, + DmtxMaskBit2 = 0x01 << 6, + DmtxMaskBit1 = 0x01 << 7 +} DmtxMaskBit; + +/** + * @struct DmtxFollow + * @brief DmtxFollow + */ +typedef struct DmtxFollow_struct { + unsigned char *ptr; + unsigned char neighbor; + int step; + DmtxPixelLoc loc; +} DmtxFollow; + +/** + * @struct DmtxBresLine + * @brief DmtxBresLine + */ +typedef struct DmtxBresLine_struct { + int xStep; + int yStep; + int xDelta; + int yDelta; + int steep; + int xOut; + int yOut; + int travel; + int outward; + int error; + DmtxPixelLoc loc; + DmtxPixelLoc loc0; + DmtxPixelLoc loc1; +} DmtxBresLine; + +typedef struct C40TextState_struct { + int shift; + DmtxBoolean upperShift; +} C40TextState; + +/* dmtxregion.c */ +static float RightAngleTrueness(DmtxVector2 c0, DmtxVector2 c1, DmtxVector2 c2, float angle); +static DmtxPointFlow MatrixRegionSeekEdge(DmtxDecode *dec, DmtxPixelLoc loc0); +static DmtxPassFail MatrixRegionOrientation(DmtxDecode *dec, DmtxRegion *reg, DmtxPointFlow flowBegin); +static long DistanceSquared(DmtxPixelLoc a, DmtxPixelLoc b); +static int ReadModuleColor(DmtxDecode *dec, DmtxRegion *reg, int symbolRow, int symbolCol, int sizeIdx, int colorPlane); + +static DmtxPassFail MatrixRegionFindSize(DmtxDecode *dec, DmtxRegion *reg); +static int CountJumpTally(DmtxDecode *dec, DmtxRegion *reg, int xStart, int yStart, DmtxDirection dir); +static DmtxPointFlow GetPointFlow(DmtxDecode *dec, int colorPlane, DmtxPixelLoc loc, int arrive); +static DmtxPointFlow FindStrongestNeighbor(DmtxDecode *dec, DmtxPointFlow center, int sign); +static DmtxFollow FollowSeek(DmtxDecode *dec, DmtxRegion *reg, int seek); +static DmtxFollow FollowSeekLoc(DmtxDecode *dec, DmtxPixelLoc loc); +static DmtxFollow FollowStep(DmtxDecode *dec, DmtxRegion *reg, DmtxFollow followBeg, int sign); +static DmtxFollow FollowStep2(DmtxDecode *dec, DmtxFollow followBeg, int sign); +static DmtxPassFail TrailBlazeContinuous(DmtxDecode *dec, DmtxRegion *reg, DmtxPointFlow flowBegin, int maxDiagonal); +static int TrailBlazeGapped(DmtxDecode *dec, DmtxRegion *reg, DmtxBresLine line, int streamDir); +static int TrailClear(DmtxDecode *dec, DmtxRegion *reg, int clearMask); +static DmtxBestLine FindBestSolidLine(DmtxDecode *dec, DmtxRegion *reg, int step0, int step1, int streamDir, int houghAvoid); +static DmtxBestLine FindBestSolidLine2(DmtxDecode *dec, DmtxPixelLoc loc0, int tripSteps, int sign, int houghAvoid); +static DmtxPassFail FindTravelLimits(DmtxDecode *dec, DmtxRegion *reg, DmtxBestLine *line); +static DmtxPassFail MatrixRegionAlignCalibEdge(DmtxDecode *dec, DmtxRegion *reg, int whichEdge); +static DmtxBresLine BresLineInit(DmtxPixelLoc loc0, DmtxPixelLoc loc1, DmtxPixelLoc locInside); +static DmtxPassFail BresLineGetStep(DmtxBresLine line, DmtxPixelLoc target, int *travel, int *outward); +static DmtxPassFail BresLineStep(DmtxBresLine *line, int travel, int outward); +/*static void WriteDiagnosticImage(DmtxDecode *dec, DmtxRegion *reg, char *imagePath);*/ + +/* dmtxdecode.c */ +static void TallyModuleJumps(DmtxDecode *dec, DmtxRegion *reg, int tally[][24], int xOrigin, int yOrigin, int mapWidth, int mapHeight, DmtxDirection dir); +static DmtxPassFail PopulateArrayFromMatrix(DmtxDecode *dec, DmtxRegion *reg, DmtxMessage *msg); + +/* dmtxdecodescheme.c */ +static void DecodeDataStream(DmtxMessage *msg, int sizeIdx, unsigned char *outputStart); +static int GetEncodationScheme(unsigned char cw); +static void PushOutputWord(DmtxMessage *msg, int value); +static void PushOutputC40TextWord(DmtxMessage *msg, C40TextState *state, int value); +static void PushOutputMacroHeader(DmtxMessage *msg, int macroType); +static void PushOutputMacroTrailer(DmtxMessage *msg); +static unsigned char *DecodeSchemeAscii(DmtxMessage *msg, unsigned char *ptr, unsigned char *dataEnd); +static unsigned char *DecodeSchemeC40Text(DmtxMessage *msg, unsigned char *ptr, unsigned char *dataEnd, DmtxScheme encScheme); +static unsigned char *DecodeSchemeX12(DmtxMessage *msg, unsigned char *ptr, unsigned char *dataEnd); +static unsigned char *DecodeSchemeEdifact(DmtxMessage *msg, unsigned char *ptr, unsigned char *dataEnd); +static unsigned char *DecodeSchemeBase256(DmtxMessage *msg, unsigned char *ptr, unsigned char *dataEnd); + +/* dmtxplacemod.c */ +static int ModulePlacementEcc200(unsigned char *modules, unsigned char *codewords, int sizeIdx, int moduleOnColor); +static void PatternShapeStandard(unsigned char *modules, int mappingRows, int mappingCols, int row, int col, unsigned char *codeword, int moduleOnColor); +static void PatternShapeSpecial1(unsigned char *modules, int mappingRows, int mappingCols, unsigned char *codeword, int moduleOnColor); +static void PatternShapeSpecial2(unsigned char *modules, int mappingRows, int mappingCols, unsigned char *codeword, int moduleOnColor); +static void PatternShapeSpecial3(unsigned char *modules, int mappingRows, int mappingCols, unsigned char *codeword, int moduleOnColor); +static void PatternShapeSpecial4(unsigned char *modules, int mappingRows, int mappingCols, unsigned char *codeword, int moduleOnColor); +static void PlaceModule(unsigned char *modules, int mappingRows, int mappingCols, int row, int col, + unsigned char *codeword, int mask, int moduleOnColor); + +/* dmtxreedsol.c */ +static DmtxPassFail RsDecode(unsigned char *code, int sizeIdx, int fix); +static DmtxBoolean RsComputeSyndromes(DmtxByteList *syn, const DmtxByteList *rec, int blockErrorWords); +static DmtxBoolean RsFindErrorLocatorPoly(DmtxByteList *elp, const DmtxByteList *syn, int errorWordCount, int maxCorrectable); +static DmtxBoolean RsFindErrorLocations(DmtxByteList *loc, const DmtxByteList *elp); +static DmtxPassFail RsRepairErrors(DmtxByteList *rec, const DmtxByteList *loc, const DmtxByteList *elp, const DmtxByteList *syn); + +/* dmtxscangrid.c */ +static DmtxScanGrid InitScanGrid(DmtxDecode *dec); +static int PopGridLocation(DmtxScanGrid *grid, /*@out@*/ DmtxPixelLoc *locPtr); +static int GetGridCoordinates(DmtxScanGrid *grid, /*@out@*/ DmtxPixelLoc *locPtr); +static void SetDerivedFields(DmtxScanGrid *grid); + +/* dmtximage.c */ +static int GetBitsPerPixel(int pack); + +/* dmtxencodebase256.c */ +static unsigned char UnRandomize255State(unsigned char value, int idx); + +static const int dmtxNeighborNone = 8; +static const int dmtxPatternX[] = { -1, 0, 1, 1, 1, 0, -1, -1 }; +static const int dmtxPatternY[] = { -1, -1, -1, 0, 1, 1, 1, 0 }; +static const DmtxPointFlow dmtxBlankEdge = { 0, 0, 0, DmtxUndefined, { -1, -1 } }; + +/*@ +charint @*/ + +static int rHvX[] = + { 256, 256, 256, 256, 255, 255, 255, 254, 254, 253, 252, 251, 250, 249, 248, + 247, 246, 245, 243, 242, 241, 239, 237, 236, 234, 232, 230, 228, 226, 224, + 222, 219, 217, 215, 212, 210, 207, 204, 202, 199, 196, 193, 190, 187, 184, + 181, 178, 175, 171, 168, 165, 161, 158, 154, 150, 147, 143, 139, 136, 132, + 128, 124, 120, 116, 112, 108, 104, 100, 96, 92, 88, 83, 79, 75, 71, + 66, 62, 58, 53, 49, 44, 40, 36, 31, 27, 22, 18, 13, 9, 4, + 0, -4, -9, -13, -18, -22, -27, -31, -36, -40, -44, -49, -53, -58, -62, + -66, -71, -75, -79, -83, -88, -92, -96, -100, -104, -108, -112, -116, -120, -124, + -128, -132, -136, -139, -143, -147, -150, -154, -158, -161, -165, -168, -171, -175, -178, + -181, -184, -187, -190, -193, -196, -199, -202, -204, -207, -210, -212, -215, -217, -219, + -222, -224, -226, -228, -230, -232, -234, -236, -237, -239, -241, -242, -243, -245, -246, + -247, -248, -249, -250, -251, -252, -253, -254, -254, -255, -255, -255, -256, -256, -256 }; + +static int rHvY[] = + { 0, 4, 9, 13, 18, 22, 27, 31, 36, 40, 44, 49, 53, 58, 62, + 66, 71, 75, 79, 83, 88, 92, 96, 100, 104, 108, 112, 116, 120, 124, + 128, 132, 136, 139, 143, 147, 150, 154, 158, 161, 165, 168, 171, 175, 178, + 181, 184, 187, 190, 193, 196, 199, 202, 204, 207, 210, 212, 215, 217, 219, + 222, 224, 226, 228, 230, 232, 234, 236, 237, 239, 241, 242, 243, 245, 246, + 247, 248, 249, 250, 251, 252, 253, 254, 254, 255, 255, 255, 256, 256, 256, + 256, 256, 256, 256, 255, 255, 255, 254, 254, 253, 252, 251, 250, 249, 248, + 247, 246, 245, 243, 242, 241, 239, 237, 236, 234, 232, 230, 228, 226, 224, + 222, 219, 217, 215, 212, 210, 207, 204, 202, 199, 196, 193, 190, 187, 184, + 181, 178, 175, 171, 168, 165, 161, 158, 154, 150, 147, 143, 139, 136, 132, + 128, 124, 120, 116, 112, 108, 104, 100, 96, 92, 88, 83, 79, 75, 71, + 66, 62, 58, 53, 49, 44, 40, 36, 31, 27, 22, 18, 13, 9, 4 }; + +/*@ -charint @*/ + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtx.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2008, 2009 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file dmtx.c + * \brief Main libdmtx source file + */ + +#ifndef CALLBACK_POINT_PLOT +#define CALLBACK_POINT_PLOT(a,b,c,d) +#endif + +#ifndef CALLBACK_POINT_XFRM +#define CALLBACK_POINT_XFRM(a,b,c,d) +#endif + +#ifndef CALLBACK_MODULE +#define CALLBACK_MODULE(a,b,c,d,e) +#endif + +#ifndef CALLBACK_MATRIX +#define CALLBACK_MATRIX(a) +#endif + +#ifndef CALLBACK_FINAL +#define CALLBACK_FINAL(a,b) +#endif + +extern char * +dmtxVersion(void) +{ + return DmtxVersion; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtxencodebase256.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2011 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file dmtxencodebase256.c + * \brief Base 256 encoding rules + */ + +/** + * \brief Unrandomize 255 state + * \param value + * \param idx + * \return Unrandomized value + */ +static unsigned char +UnRandomize255State(unsigned char value, int idx) +{ + int pseudoRandom; + int tmp; + + pseudoRandom = ((149 * idx) % 255) + 1; + tmp = value - pseudoRandom; + if(tmp < 0) + tmp += 256; + + assert(tmp >= 0 && tmp < 256); + + return (unsigned char)tmp; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtxdecode.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2008, 2009 Mike Laughton. All rights reserved. + * Copyright 2009 Mackenzie Straight. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file dmtxdecode.c + * \brief Decode regions + */ + +/** + * \brief Initialize decode struct with default values + * \param img + * \return Initialized DmtxDecode struct + */ +extern DmtxDecode * +dmtxDecodeCreate(DmtxImage *img, int scale) +{ + DmtxDecode *dec; + int width, height; + + dec = (DmtxDecode *)calloc(1, sizeof(DmtxDecode)); + if(dec == NULL) + return NULL; + + width = dmtxImageGetProp(img, DmtxPropWidth) / scale; + height = dmtxImageGetProp(img, DmtxPropHeight) / scale; + + dec->edgeMin = DmtxUndefined; + dec->edgeMax = DmtxUndefined; + dec->scanGap = 1; + dec->squareDevn = cos(50 * (M_PI/180)); + dec->sizeIdxExpected = DmtxSymbolShapeAuto; + dec->edgeThresh = 10; + + dec->xMin = 0; + dec->xMax = width - 1; + dec->yMin = 0; + dec->yMax = height - 1; + dec->scale = scale; + + dec->cache = (unsigned char *)calloc(width * height, sizeof(unsigned char)); + if(dec->cache == NULL) { + free(dec); + return NULL; + } + + dec->image = img; + dec->grid = InitScanGrid(dec); + + return dec; +} + +/** + * \brief Deinitialize decode struct + * \param dec + * \return void + */ +extern DmtxPassFail +dmtxDecodeDestroy(DmtxDecode **dec) +{ + if(dec == NULL || *dec == NULL) + return DmtxFail; + + if((*dec)->cache != NULL) + free((*dec)->cache); + + free(*dec); + + *dec = NULL; + + return DmtxPass; +} + +/** + * \brief Set decoding behavior property + * \param dec + * \param prop + * \param value + * \return DmtxPass | DmtxFail + */ +extern DmtxPassFail +dmtxDecodeSetProp(DmtxDecode *dec, int prop, int value) +{ + switch(prop) { + case DmtxPropEdgeMin: + dec->edgeMin = value; + break; + case DmtxPropEdgeMax: + dec->edgeMax = value; + break; + case DmtxPropScanGap: + dec->scanGap = value; /* XXX Should this be scaled? */ + break; + case DmtxPropSquareDevn: + dec->squareDevn = cos(value * (M_PI/180.0)); + break; + case DmtxPropSymbolSize: + dec->sizeIdxExpected = value; + break; + case DmtxPropEdgeThresh: + dec->edgeThresh = value; + break; + /* Min and Max values arrive unscaled */ + case DmtxPropXmin: + dec->xMin = value / dec->scale; + break; + case DmtxPropXmax: + dec->xMax = value / dec->scale; + break; + case DmtxPropYmin: + dec->yMin = value / dec->scale; + break; + case DmtxPropYmax: + dec->yMax = value / dec->scale; + break; + default: + break; + } + + if(dec->squareDevn <= 0.0 || dec->squareDevn >= 1.0) + return DmtxFail; + + if(dec->scanGap < 1) + return DmtxFail; + + if(dec->edgeThresh < 1 || dec->edgeThresh > 100) + return DmtxFail; + + /* Reinitialize scangrid in case any inputs changed */ + dec->grid = InitScanGrid(dec); + + return DmtxPass; +} + +/** + * \brief Get decoding behavior property + * \param dec + * \param prop + * \return value + */ +extern int +dmtxDecodeGetProp(DmtxDecode *dec, int prop) +{ + switch(prop) { + case DmtxPropEdgeMin: + return dec->edgeMin; + case DmtxPropEdgeMax: + return dec->edgeMax; + case DmtxPropScanGap: + return dec->scanGap; + case DmtxPropSquareDevn: + return (int)(acos(dec->squareDevn) * 180.0/M_PI); + case DmtxPropSymbolSize: + return dec->sizeIdxExpected; + case DmtxPropEdgeThresh: + return dec->edgeThresh; + case DmtxPropXmin: + return dec->xMin; + case DmtxPropXmax: + return dec->xMax; + case DmtxPropYmin: + return dec->yMin; + case DmtxPropYmax: + return dec->yMax; + case DmtxPropScale: + return dec->scale; + case DmtxPropWidth: + return dmtxImageGetProp(dec->image, DmtxPropWidth) / dec->scale; + case DmtxPropHeight: + return dmtxImageGetProp(dec->image, DmtxPropHeight) / dec->scale; + default: + break; + } + + return DmtxUndefined; +} + +/** + * \brief Returns xxx + * \param img + * \param Scaled x coordinate + * \param Scaled y coordinate + * \return Scaled pixel offset + */ +extern unsigned char * +dmtxDecodeGetCache(DmtxDecode *dec, int x, int y) +{ +// int width, height; + + assert(dec != NULL); + +/* if(dec.cacheComplete == DmtxFalse) + CacheImage(); */ + +// Scale is always 1, so we can do it quicker +// width = dmtxDecodeGetProp(dec, DmtxPropWidth); +// height = dmtxDecodeGetProp(dec, DmtxPropHeight); + + if(x < 0 || x >= dec->image->width || y < 0 || y >= dec->image->height) + return NULL; + + return &(dec->cache[y * dec->image->width + x]); +} + +/** + * + * + */ +extern DmtxPassFail +dmtxDecodeGetPixelValue(DmtxDecode *dec, int x, int y, int channel, int *value) +{ + int xUnscaled, yUnscaled; + DmtxPassFail err; + + xUnscaled = x * dec->scale; + yUnscaled = y * dec->scale; + +/* Remove spherical lens distortion */ +/* int width, height; + float radiusPow2, radiusPow4; + float factor; + DmtxVector2 pointShifted; + DmtxVector2 correctedPoint; + + width = dmtxImageGetProp(img, DmtxPropWidth); + height = dmtxImageGetProp(img, DmtxPropHeight); + + pointShifted.X = point.X - width/2.0; + pointShifted.Y = point.Y - height/2.0; + + radiusPow2 = pointShifted.X * pointShifted.X + pointShifted.Y * pointShifted.Y; + radiusPow4 = radiusPow2 * radiusPow2; + + factor = 1 + (k1 * radiusPow2) + (k2 * radiusPow4); + + correctedPoint.X = pointShifted.X * factor + width/2.0; + correctedPoint.Y = pointShifted.Y * factor + height/2.0; + + return correctedPoint; */ + + err = dmtxImageGetPixelValue(dec->image, xUnscaled, yUnscaled, channel, value); + + return err; +} + +/** + * \brief Fill the region covered by the quadrilateral given by (p0,p1,p2,p3) in the cache. + */ +static void +CacheFillQuad(DmtxDecode *dec, DmtxPixelLoc p0, DmtxPixelLoc p1, DmtxPixelLoc p2, DmtxPixelLoc p3) +{ + DmtxBresLine lines[4]; + DmtxPixelLoc pEmpty = { 0, 0 }; + unsigned char *cache; + int *scanlineMin, *scanlineMax; + int minY, maxY, sizeY, posY, posX; + int i, idx; + + lines[0] = BresLineInit(p0, p1, pEmpty); + lines[1] = BresLineInit(p1, p2, pEmpty); + lines[2] = BresLineInit(p2, p3, pEmpty); + lines[3] = BresLineInit(p3, p0, pEmpty); + + minY = dec->yMax; + maxY = 0; + + minY = min(minY, p0.Y); maxY = max(maxY, p0.Y); + minY = min(minY, p1.Y); maxY = max(maxY, p1.Y); + minY = min(minY, p2.Y); maxY = max(maxY, p2.Y); + minY = min(minY, p3.Y); maxY = max(maxY, p3.Y); + + sizeY = maxY - minY + 1; + + scanlineMin = (int *)malloc(sizeY * sizeof(int)); + scanlineMax = (int *)calloc(sizeY, sizeof(int)); + + assert(scanlineMin); /* XXX handle this better */ + assert(scanlineMax); /* XXX handle this better */ + + for(i = 0; i < sizeY; i++) + scanlineMin[i] = dec->xMax; + + for(i = 0; i < 4; i++) { + while(lines[i].loc.X != lines[i].loc1.X || lines[i].loc.Y != lines[i].loc1.Y) { + idx = lines[i].loc.Y - minY; + scanlineMin[idx] = min(scanlineMin[idx], lines[i].loc.X); + scanlineMax[idx] = max(scanlineMax[idx], lines[i].loc.X); + BresLineStep(lines + i, 1, 0); + } + } + + for(posY = minY; posY < maxY && posY < dec->yMax; posY++) { + idx = posY - minY; + for(posX = scanlineMin[idx]; posX < scanlineMax[idx] && posX < dec->xMax; posX++) { + cache = dmtxDecodeGetCache(dec, posX, posY); + if(cache != NULL) + *cache |= 0x80; + } + } + + free(scanlineMin); + free(scanlineMax); +} + +/** + * \brief Convert fitted Data Matrix region into a decoded message + * \param dec + * \param reg + * \param fix + * \return Decoded message + */ +extern DmtxMessage * +dmtxDecodeMatrixRegion(DmtxDecode *dec, DmtxRegion *reg, int fix) +{ + DmtxMessage *msg; + DmtxVector2 topLeft, topRight, bottomLeft, bottomRight; + DmtxPixelLoc pxTopLeft, pxTopRight, pxBottomLeft, pxBottomRight; + + msg = dmtxMessageCreate(reg->sizeIdx, DmtxFormatMatrix); + if(msg == NULL) + return NULL; + + if(PopulateArrayFromMatrix(dec, reg, msg) != DmtxPass) { + dmtxMessageDestroy(&msg); + return NULL; + } + + /* maybe place remaining logic into new dmtxDecodePopulatedArray() + function so other people can pass in their own arrays */ + + ModulePlacementEcc200(msg->array, msg->code, + reg->sizeIdx, DmtxModuleOnRed | DmtxModuleOnGreen | DmtxModuleOnBlue); + + if(RsDecode(msg->code, reg->sizeIdx, fix) == DmtxFail) + { + dmtxMessageDestroy(&msg); + return NULL; + } + + topLeft.X = bottomLeft.X = topLeft.Y = topRight.Y = -0.1; + topRight.X = bottomRight.X = bottomLeft.Y = bottomRight.Y = 1.1; + + dmtxMatrix3VMultiplyBy(&topLeft, reg->fit2raw); + dmtxMatrix3VMultiplyBy(&topRight, reg->fit2raw); + dmtxMatrix3VMultiplyBy(&bottomLeft, reg->fit2raw); + dmtxMatrix3VMultiplyBy(&bottomRight, reg->fit2raw); + + pxTopLeft.X = (int)(0.5 + topLeft.X); + pxTopLeft.Y = (int)(0.5 + topLeft.Y); + pxBottomLeft.X = (int)(0.5 + bottomLeft.X); + pxBottomLeft.Y = (int)(0.5 + bottomLeft.Y); + pxTopRight.X = (int)(0.5 + topRight.X); + pxTopRight.Y = (int)(0.5 + topRight.Y); + pxBottomRight.X = (int)(0.5 + bottomRight.X); + pxBottomRight.Y = (int)(0.5 + bottomRight.Y); + + CacheFillQuad(dec, pxTopLeft, pxTopRight, pxBottomRight, pxBottomLeft); + + DecodeDataStream(msg, reg->sizeIdx, NULL); + + return msg; +} + +/** + * \brief Convert fitted Data Mosaic region into a decoded message + * \param dec + * \param reg + * \param fix + * \return Decoded message + */ +extern DmtxMessage * +dmtxDecodeMosaicRegion(DmtxDecode *dec, DmtxRegion *reg, int fix) +{ + int offset; + int colorPlane; + DmtxMessage *oMsg, *rMsg, *gMsg, *bMsg; + + colorPlane = reg->flowBegin.plane; + + /** + * Consider performing a color cube fit here to identify exact RGB of + * all 6 "cube-like" corners based on pixels located within region. Then + * force each sample pixel to the "cube-like" corner based o which one + * is nearest "sqrt(dr^2+dg^2+db^2)" (except sqrt is unnecessary). + * colorPlane = reg->flowBegin.plane; + * + * To find RGB values of primary colors, perform something like a + * histogram except instead of going from black to color N, go from + * (127,127,127) to color. Use color bins along with distance to + * identify value. An additional method will be required to get actual + * RGB instead of just a plane in 3D. */ + + reg->flowBegin.plane = 0; /* kind of a hack */ + rMsg = dmtxDecodeMatrixRegion(dec, reg, fix); + + reg->flowBegin.plane = 1; /* kind of a hack */ + gMsg = dmtxDecodeMatrixRegion(dec, reg, fix); + + reg->flowBegin.plane = 2; /* kind of a hack */ + bMsg = dmtxDecodeMatrixRegion(dec, reg, fix); + + reg->flowBegin.plane = colorPlane; + + oMsg = dmtxMessageCreate(reg->sizeIdx, DmtxFormatMosaic); + + if(oMsg == NULL || rMsg == NULL || gMsg == NULL || bMsg == NULL) { + dmtxMessageDestroy(&oMsg); + dmtxMessageDestroy(&rMsg); + dmtxMessageDestroy(&gMsg); + dmtxMessageDestroy(&bMsg); + return NULL; + } + + offset = 0; + memcpy(oMsg->output + offset, rMsg->output, rMsg->outputIdx); + offset += rMsg->outputIdx; + memcpy(oMsg->output + offset, gMsg->output, gMsg->outputIdx); + offset += gMsg->outputIdx; + memcpy(oMsg->output + offset, bMsg->output, bMsg->outputIdx); + offset += bMsg->outputIdx; + + oMsg->outputIdx = offset; + + dmtxMessageDestroy(&rMsg); + dmtxMessageDestroy(&gMsg); + dmtxMessageDestroy(&bMsg); + + return oMsg; +} + +/** + * + * + */ +extern unsigned char * +dmtxDecodeCreateDiagnostic(DmtxDecode *dec, int *totalBytes, int *headerBytes, int style) +{ + int i, row, col; + int width, height; + int widthDigits, heightDigits; + int count, channelCount; + int rgb[3]; + float shade; + unsigned char *pnm, *output, *cache; + + width = dmtxDecodeGetProp(dec, DmtxPropWidth); + height = dmtxDecodeGetProp(dec, DmtxPropHeight); + channelCount = dmtxImageGetProp(dec->image, DmtxPropChannelCount); + + style = 1; /* this doesn't mean anything yet */ + + /* Count width digits */ + for(widthDigits = 0, i = width; i > 0; i /= 10) + widthDigits++; + + /* Count height digits */ + for(heightDigits = 0, i = height; i > 0; i /= 10) + heightDigits++; + + *headerBytes = widthDigits + heightDigits + 9; + *totalBytes = *headerBytes + width * height * 3; + + pnm = (unsigned char *)malloc(*totalBytes); + if(pnm == NULL) + return NULL; + +#ifdef _VISUALC_ + count = sprintf_s((char *)pnm, *headerBytes + 1, "P6\n%d %d\n255\n", width, height); +#else + count = snprintf((char *)pnm, *headerBytes + 1, "P6\n%d %d\n255\n", width, height); +#endif + + if(count != *headerBytes) { + free(pnm); + return NULL; + } + + output = pnm + (*headerBytes); + for(row = height - 1; row >= 0; row--) { + for(col = 0; col < width; col++) { + cache = dmtxDecodeGetCache(dec, col, row); + if(cache == NULL) { + rgb[0] = 0; + rgb[1] = 0; + rgb[2] = 128; + } + else if(*cache & 0x40) { + rgb[0] = 255; + rgb[1] = 0; + rgb[2] = 0; + } + else { + shade = (*cache & 0x80) ? 0.0 : 0.7; + for(i = 0; i < 3; i++) { + if(i < channelCount) + dmtxDecodeGetPixelValue(dec, col, row, i, &rgb[i]); + else + dmtxDecodeGetPixelValue(dec, col, row, 0, &rgb[i]); + + rgb[i] += (int)(shade * (float)(255 - rgb[i]) + 0.5); + if(rgb[i] > 255) + rgb[i] = 255; + } + } + *(output++) = (unsigned char)rgb[0]; + *(output++) = (unsigned char)rgb[1]; + *(output++) = (unsigned char)rgb[2]; + } + } + assert(output == pnm + *totalBytes); + + return pnm; +} + +/** + * \brief Increment counters used to determine module values + * \param img + * \param reg + * \param tally + * \param xOrigin + * \param yOrigin + * \param mapWidth + * \param mapHeight + * \param dir + * \return void + */ +static void +TallyModuleJumps(DmtxDecode *dec, DmtxRegion *reg, int tally[][24], int xOrigin, int yOrigin, int mapWidth, int mapHeight, DmtxDirection dir) +{ + int extent, weight; + int travelStep; + int symbolRow, symbolCol; + int mapRow, mapCol; + int lineStart, lineStop; + int travelStart, travelStop; + int *line, *travel; + int jumpThreshold; + int darkOnLight; + int color; + int statusPrev, statusModule; + int tPrev, tModule; + + assert(dir == DmtxDirUp || dir == DmtxDirLeft || dir == DmtxDirDown || dir == DmtxDirRight); + + travelStep = (dir == DmtxDirUp || dir == DmtxDirRight) ? 1 : -1; + + /* Abstract row and column progress using pointers to allow grid + traversal in all 4 directions using same logic */ + + if((dir & DmtxDirHorizontal) != 0x00) { + line = &symbolRow; + travel = &symbolCol; + extent = mapWidth; + lineStart = yOrigin; + lineStop = yOrigin + mapHeight; + travelStart = (travelStep == 1) ? xOrigin - 1 : xOrigin + mapWidth; + travelStop = (travelStep == 1) ? xOrigin + mapWidth : xOrigin - 1; + } + else { + assert(dir & DmtxDirVertical); + line = &symbolCol; + travel = &symbolRow; + extent = mapHeight; + lineStart = xOrigin; + lineStop = xOrigin + mapWidth; + travelStart = (travelStep == 1) ? yOrigin - 1: yOrigin + mapHeight; + travelStop = (travelStep == 1) ? yOrigin + mapHeight : yOrigin - 1; + } + + + darkOnLight = (int)(reg->offColor > reg->onColor); + jumpThreshold = abs((int)(0.4 * (reg->offColor - reg->onColor) + 0.5)); + + assert(jumpThreshold >= 0); + + for(*line = lineStart; *line < lineStop; (*line)++) { + + /* Capture tModule for each leading border module as normal but + decide status based on predictable barcode border pattern */ + + *travel = travelStart; + color = ReadModuleColor(dec, reg, symbolRow, symbolCol, reg->sizeIdx, reg->flowBegin.plane); + tModule = (darkOnLight) ? reg->offColor - color : color - reg->offColor; + + statusModule = (travelStep == 1 || (*line & 0x01) == 0) ? DmtxModuleOnRGB : DmtxModuleOff; + + weight = extent; + + while((*travel += travelStep) != travelStop) { + + tPrev = tModule; + statusPrev = statusModule; + + /* For normal data-bearing modules capture color and decide + module status based on comparison to previous "known" module */ + + color = ReadModuleColor(dec, reg, symbolRow, symbolCol, reg->sizeIdx, reg->flowBegin.plane); + tModule = (darkOnLight) ? reg->offColor - color : color - reg->offColor; + + if(statusPrev == DmtxModuleOnRGB) { + if(tModule < tPrev - jumpThreshold) + statusModule = DmtxModuleOff; + else + statusModule = DmtxModuleOnRGB; + } + else if(statusPrev == DmtxModuleOff) { + if(tModule > tPrev + jumpThreshold) + statusModule = DmtxModuleOnRGB; + else + statusModule = DmtxModuleOff; + } + + mapRow = symbolRow - yOrigin; + mapCol = symbolCol - xOrigin; + assert(mapRow < 24 && mapCol < 24); + + if(statusModule == DmtxModuleOnRGB) + tally[mapRow][mapCol] += (2 * weight); + + weight--; + } + + assert(weight == 0); + } +} + +/** + * \brief Populate array with codeword values based on module colors + * \param msg + * \param img + * \param reg + * \return DmtxPass | DmtxFail + */ +static DmtxPassFail +PopulateArrayFromMatrix(DmtxDecode *dec, DmtxRegion *reg, DmtxMessage *msg) +{ + int weightFactor; + int mapWidth, mapHeight; + int xRegionTotal, yRegionTotal; + int xRegionCount, yRegionCount; + int xOrigin, yOrigin; + int mapCol, mapRow; + int colTmp, rowTmp, idx; + int *tally_temp = malloc(sizeof(int) * 24 * 24); int (*tally)[24] = (int (*)[24]) tally_temp; // [24][24]; /* Large enough to map largest single region */ + +/* memset(msg->array, 0x00, msg->arraySize); */ + + /* Capture number of regions present in barcode */ + xRegionTotal = dmtxGetSymbolAttribute(DmtxSymAttribHorizDataRegions, reg->sizeIdx); + yRegionTotal = dmtxGetSymbolAttribute(DmtxSymAttribVertDataRegions, reg->sizeIdx); + + /* Capture region dimensions (not including border modules) */ + mapWidth = dmtxGetSymbolAttribute(DmtxSymAttribDataRegionCols, reg->sizeIdx); + mapHeight = dmtxGetSymbolAttribute(DmtxSymAttribDataRegionRows, reg->sizeIdx); + + weightFactor = 2 * (mapHeight + mapWidth + 2); + assert(weightFactor > 0); + + /* Tally module changes for each region in each direction */ + for(yRegionCount = 0; yRegionCount < yRegionTotal; yRegionCount++) { + + /* Y location of mapping region origin in symbol coordinates */ + yOrigin = yRegionCount * (mapHeight + 2) + 1; + + for(xRegionCount = 0; xRegionCount < xRegionTotal; xRegionCount++) { + + /* X location of mapping region origin in symbol coordinates */ + xOrigin = xRegionCount * (mapWidth + 2) + 1; + + memset(tally, 0x00, 24 * 24 * sizeof(int)); + TallyModuleJumps(dec, reg, tally, xOrigin, yOrigin, mapWidth, mapHeight, DmtxDirUp); + TallyModuleJumps(dec, reg, tally, xOrigin, yOrigin, mapWidth, mapHeight, DmtxDirLeft); + TallyModuleJumps(dec, reg, tally, xOrigin, yOrigin, mapWidth, mapHeight, DmtxDirDown); + TallyModuleJumps(dec, reg, tally, xOrigin, yOrigin, mapWidth, mapHeight, DmtxDirRight); + + /* Decide module status based on final tallies */ + for(mapRow = 0; mapRow < mapHeight; mapRow++) { + for(mapCol = 0; mapCol < mapWidth; mapCol++) { + + rowTmp = (yRegionCount * mapHeight) + mapRow; + rowTmp = yRegionTotal * mapHeight - rowTmp - 1; + colTmp = (xRegionCount * mapWidth) + mapCol; + idx = (rowTmp * xRegionTotal * mapWidth) + colTmp; + + if(tally[mapRow][mapCol]/(float)weightFactor >= 0.5) + msg->array[idx] = DmtxModuleOnRGB; + else + msg->array[idx] = DmtxModuleOff; + + msg->array[idx] |= DmtxModuleAssigned; + } + } + } + } + + free(tally_temp); + + return DmtxPass; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtxdecodescheme.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2008, 2009 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file dmtxdecodescheme.c + */ + +/** + * \brief Translate encoded data stream into final output + * \param msg + * \param sizeIdx + * \param outputStart + * \return void + */ +static void +DecodeDataStream(DmtxMessage *msg, int sizeIdx, unsigned char *outputStart) +{ + DmtxBoolean macro = DmtxFalse; + DmtxScheme encScheme; + unsigned char *ptr, *dataEnd; + + msg->output = (outputStart == NULL) ? msg->output : outputStart; + msg->outputIdx = 0; + + ptr = msg->code; + dataEnd = ptr + dmtxGetSymbolAttribute(DmtxSymAttribSymbolDataWords, sizeIdx); + + /* Print macro header if first codeword triggers it */ + if(*ptr == DmtxValue05Macro || *ptr == DmtxValue06Macro) { + PushOutputMacroHeader(msg, *ptr); + macro = DmtxTrue; + } + + while(ptr < dataEnd) { + + encScheme = GetEncodationScheme(*ptr); + if(encScheme != DmtxSchemeAscii) + ptr++; + + switch(encScheme) { + case DmtxSchemeAscii: + ptr = DecodeSchemeAscii(msg, ptr, dataEnd); + break; + case DmtxSchemeC40: + case DmtxSchemeText: + ptr = DecodeSchemeC40Text(msg, ptr, dataEnd, encScheme); + break; + case DmtxSchemeX12: + ptr = DecodeSchemeX12(msg, ptr, dataEnd); + break; + case DmtxSchemeEdifact: + ptr = DecodeSchemeEdifact(msg, ptr, dataEnd); + break; + case DmtxSchemeBase256: + ptr = DecodeSchemeBase256(msg, ptr, dataEnd); + break; + default: + /* error */ + break; + } + } + + /* Print macro trailer if required */ + if(macro == DmtxTrue) + PushOutputMacroTrailer(msg); +} + +/** + * \brief Determine next encodation scheme + * \param encScheme + * \param cw + * \return Pointer to next undecoded codeword + */ +static int +GetEncodationScheme(unsigned char cw) +{ + DmtxScheme encScheme; + + switch(cw) { + case DmtxValueC40Latch: + encScheme = DmtxSchemeC40; + break; + case DmtxValueTextLatch: + encScheme = DmtxSchemeText; + break; + case DmtxValueX12Latch: + encScheme = DmtxSchemeX12; + break; + case DmtxValueEdifactLatch: + encScheme = DmtxSchemeEdifact; + break; + case DmtxValueBase256Latch: + encScheme = DmtxSchemeBase256; + break; + default: + encScheme = DmtxSchemeAscii; + break; + } + + return encScheme; +} + +/** + * + * + */ +static void +PushOutputWord(DmtxMessage *msg, int value) +{ + assert(value >= 0 && value < 256); + + msg->output[msg->outputIdx++] = (unsigned char)value; +} + +/** + * + * + */ +static void +PushOutputC40TextWord(DmtxMessage *msg, C40TextState *state, int value) +{ + assert(value >= 0 && value < 256); + + msg->output[msg->outputIdx] = (unsigned char)value; + + if(state->upperShift == DmtxTrue) { + assert(value < 128); + msg->output[msg->outputIdx] += 128; + } + + msg->outputIdx++; + + state->shift = DmtxC40TextBasicSet; + state->upperShift = DmtxFalse; +} + +/** + * + * + */ +static void +PushOutputMacroHeader(DmtxMessage *msg, int macroType) +{ + PushOutputWord(msg, '['); + PushOutputWord(msg, ')'); + PushOutputWord(msg, '>'); + PushOutputWord(msg, 30); /* ASCII RS */ + PushOutputWord(msg, '0'); + + assert(macroType == DmtxValue05Macro || macroType == DmtxValue06Macro); + if(macroType == DmtxValue05Macro) + PushOutputWord(msg, '5'); + else + PushOutputWord(msg, '6'); + + PushOutputWord(msg, 29); /* ASCII GS */ +} + +/** + * + * + */ +static void +PushOutputMacroTrailer(DmtxMessage *msg) +{ + PushOutputWord(msg, 30); /* ASCII RS */ + PushOutputWord(msg, 4); /* ASCII EOT */ +} + +/** + * \brief Decode stream assuming standard ASCII encodation + * \param msg + * \param ptr + * \param dataEnd + * \return Pointer to next undecoded codeword + */ +static unsigned char * +DecodeSchemeAscii(DmtxMessage *msg, unsigned char *ptr, unsigned char *dataEnd) +{ + int upperShift; + int codeword, digits; + + upperShift = DmtxFalse; + + while(ptr < dataEnd) { + + codeword = (int)(*ptr); + + if(GetEncodationScheme(*ptr) != DmtxSchemeAscii) + return ptr; + else + ptr++; + + if(upperShift == DmtxTrue) { + PushOutputWord(msg, codeword + 127); + upperShift = DmtxFalse; + } + else if(codeword == DmtxValueAsciiUpperShift) { + upperShift = DmtxTrue; + } + else if(codeword == DmtxValueAsciiPad) { + assert(dataEnd >= ptr); + assert(dataEnd - ptr <= INT_MAX); + msg->padCount = (int)(dataEnd - ptr); + return dataEnd; + } + else if(codeword <= 128) { + PushOutputWord(msg, codeword - 1); + } + else if(codeword <= 229) { + digits = codeword - 130; + PushOutputWord(msg, digits/10 + '0'); + PushOutputWord(msg, digits - (digits/10)*10 + '0'); + } + } + + return ptr; +} + +/** + * \brief Decode stream assuming C40 or Text encodation + * \param msg + * \param ptr + * \param dataEnd + * \param encScheme + * \return Pointer to next undecoded codeword + */ +static unsigned char * +DecodeSchemeC40Text(DmtxMessage *msg, unsigned char *ptr, unsigned char *dataEnd, DmtxScheme encScheme) +{ + int i; + int packed; + int c40Values[3]; + C40TextState state; + + state.shift = DmtxC40TextBasicSet; + state.upperShift = DmtxFalse; + + assert(encScheme == DmtxSchemeC40 || encScheme == DmtxSchemeText); + + /* Unlatch is implied if only one codeword remains */ + if(dataEnd - ptr < 2) + return ptr; + + while(ptr < dataEnd) { + + /* FIXME Also check that ptr+1 is safe to access */ + packed = (*ptr << 8) | *(ptr+1); + c40Values[0] = ((packed - 1)/1600); + c40Values[1] = ((packed - 1)/40) % 40; + c40Values[2] = (packed - 1) % 40; + ptr += 2; + + for(i = 0; i < 3; i++) { + if(state.shift == DmtxC40TextBasicSet) { /* Basic set */ + if(c40Values[i] <= 2) { + state.shift = c40Values[i] + 1; + } + else if(c40Values[i] == 3) { + PushOutputC40TextWord(msg, &state, ' '); + } + else if(c40Values[i] <= 13) { + PushOutputC40TextWord(msg, &state, c40Values[i] - 13 + '9'); /* 0-9 */ + } + else if(c40Values[i] <= 39) { + if(encScheme == DmtxSchemeC40) { + PushOutputC40TextWord(msg, &state, c40Values[i] - 39 + 'Z'); /* A-Z */ + } + else if(encScheme == DmtxSchemeText) { + PushOutputC40TextWord(msg, &state, c40Values[i] - 39 + 'z'); /* a-z */ + } + } + } + else if(state.shift == DmtxC40TextShift1) { /* Shift 1 set */ + PushOutputC40TextWord(msg, &state, c40Values[i]); /* ASCII 0 - 31 */ + } + else if(state.shift == DmtxC40TextShift2) { /* Shift 2 set */ + if(c40Values[i] <= 14) { + PushOutputC40TextWord(msg, &state, c40Values[i] + 33); /* ASCII 33 - 47 */ + } + else if(c40Values[i] <= 21) { + PushOutputC40TextWord(msg, &state, c40Values[i] + 43); /* ASCII 58 - 64 */ + } + else if(c40Values[i] <= 26) { + PushOutputC40TextWord(msg, &state, c40Values[i] + 69); /* ASCII 91 - 95 */ + } + else if(c40Values[i] == 27) { + PushOutputC40TextWord(msg, &state, 0x1d); /* FNC1 -- XXX depends on position? */ + } + else if(c40Values[i] == 30) { + state.upperShift = DmtxTrue; + state.shift = DmtxC40TextBasicSet; + } + } + else if(state.shift == DmtxC40TextShift3) { /* Shift 3 set */ + if(encScheme == DmtxSchemeC40) { + PushOutputC40TextWord(msg, &state, c40Values[i] + 96); + } + else if(encScheme == DmtxSchemeText) { + if(c40Values[i] == 0) + PushOutputC40TextWord(msg, &state, c40Values[i] + 96); + else if(c40Values[i] <= 26) + PushOutputC40TextWord(msg, &state, c40Values[i] - 26 + 'Z'); /* A-Z */ + else + PushOutputC40TextWord(msg, &state, c40Values[i] - 31 + 127); /* { | } ~ DEL */ + } + } + } + + /* Unlatch if codeword 254 follows 2 codewords in C40/Text encodation */ + if(*ptr == DmtxValueCTXUnlatch) + return ptr + 1; + + /* Unlatch is implied if only one codeword remains */ + if(dataEnd - ptr < 2) + return ptr; + } + + return ptr; +} + +/** + * \brief Decode stream assuming X12 encodation + * \param msg + * \param ptr + * \param dataEnd + * \return Pointer to next undecoded codeword + */ +static unsigned char * +DecodeSchemeX12(DmtxMessage *msg, unsigned char *ptr, unsigned char *dataEnd) +{ + int i; + int packed; + int x12Values[3]; + + /* Unlatch is implied if only one codeword remains */ + if(dataEnd - ptr < 2) + return ptr; + + while(ptr < dataEnd) { + + /* FIXME Also check that ptr+1 is safe to access */ + packed = (*ptr << 8) | *(ptr+1); + x12Values[0] = ((packed - 1)/1600); + x12Values[1] = ((packed - 1)/40) % 40; + x12Values[2] = (packed - 1) % 40; + ptr += 2; + + for(i = 0; i < 3; i++) { + if(x12Values[i] == 0) + PushOutputWord(msg, 13); + else if(x12Values[i] == 1) + PushOutputWord(msg, 42); + else if(x12Values[i] == 2) + PushOutputWord(msg, 62); + else if(x12Values[i] == 3) + PushOutputWord(msg, 32); + else if(x12Values[i] <= 13) + PushOutputWord(msg, x12Values[i] + 44); + else if(x12Values[i] <= 90) + PushOutputWord(msg, x12Values[i] + 51); + } + + /* Unlatch if codeword 254 follows 2 codewords in C40/Text encodation */ + if(*ptr == DmtxValueCTXUnlatch) + return ptr + 1; + + /* Unlatch is implied if only one codeword remains */ + if(dataEnd - ptr < 2) + return ptr; + } + + return ptr; +} + +/** + * \brief Decode stream assuming EDIFACT encodation + * \param msg + * \param ptr + * \param dataEnd + * \return Pointer to next undecoded codeword + */ +static unsigned char * +DecodeSchemeEdifact(DmtxMessage *msg, unsigned char *ptr, unsigned char *dataEnd) +{ + int i; + unsigned char unpacked[4]; + + /* Unlatch is implied if fewer than 3 codewords remain */ + if(dataEnd - ptr < 3) + return ptr; + + while(ptr < dataEnd) { + + /* FIXME Also check that ptr+2 is safe to access -- shouldn't be a + problem because I'm guessing you can guarantee there will always + be at least 3 error codewords */ + unpacked[0] = (*ptr & 0xfc) >> 2; + unpacked[1] = (*ptr & 0x03) << 4 | (*(ptr+1) & 0xf0) >> 4; + unpacked[2] = (*(ptr+1) & 0x0f) << 2 | (*(ptr+2) & 0xc0) >> 6; + unpacked[3] = *(ptr+2) & 0x3f; + + for(i = 0; i < 4; i++) { + + /* Advance input ptr (4th value comes from already-read 3rd byte) */ + if(i < 3) + ptr++; + + /* Test for unlatch condition */ + if(unpacked[i] == DmtxValueEdifactUnlatch) { + assert(msg->output[msg->outputIdx] == 0); /* XXX dirty why? */ + return ptr; + } + + PushOutputWord(msg, unpacked[i] ^ (((unpacked[i] & 0x20) ^ 0x20) << 1)); + } + + /* Unlatch is implied if fewer than 3 codewords remain */ + if(dataEnd - ptr < 3) + return ptr; + } + + return ptr; + +/* XXX the following version should be safer, but requires testing before replacing the old version + int bits = 0; + int bitCount = 0; + int value; + + while(ptr < dataEnd) { + + if(bitCount < 6) { + bits = (bits << 8) | *(ptr++); + bitCount += 8; + } + + value = bits >> (bitCount - 6); + bits -= (value << (bitCount - 6)); + bitCount -= 6; + + if(value == 0x1f) { + assert(bits == 0); // should be padded with zero-value bits + return ptr; + } + PushOutputWord(msg, value ^ (((value & 0x20) ^ 0x20) << 1)); + + // Unlatch implied if just completed triplet and 1 or 2 words are left + if(bitCount == 0 && dataEnd - ptr - 1 > 0 && dataEnd - ptr - 1 < 3) + return ptr; + } + + assert(bits == 0); // should be padded with zero-value bits + assert(bitCount == 0); // should be padded with zero-value bits + return ptr; +*/ +} + +/** + * \brief Decode stream assuming Base 256 encodation + * \param msg + * \param ptr + * \param dataEnd + * \return Pointer to next undecoded codeword + */ +static unsigned char * +DecodeSchemeBase256(DmtxMessage *msg, unsigned char *ptr, unsigned char *dataEnd) +{ + int d0, d1; + int idx; + unsigned char *ptrEnd; + + /* Find positional index used for unrandomizing */ + assert(ptr + 1 >= msg->code); + assert(ptr + 1 - msg->code <= INT_MAX); + idx = (int)(ptr + 1 - msg->code); + + d0 = UnRandomize255State(*(ptr++), idx++); + if(d0 == 0) { + ptrEnd = dataEnd; + } + else if(d0 <= 249) { + ptrEnd = ptr + d0; + } + else { + d1 = UnRandomize255State(*(ptr++), idx++); + ptrEnd = ptr + (d0 - 249) * 250 + d1; + } + + if(ptrEnd > dataEnd) + fb_alloc_fail(); // exit(40); /* XXX needs cleaner error handling */ + + while(ptr < ptrEnd) + PushOutputWord(msg, UnRandomize255State(*(ptr++), idx++)); + + return ptr; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtxmessage.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2008, 2009 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file dmtxmessage.c + * \brief Data message handling + */ + +/** + * \brief Allocate memory for message + * \param sizeIdx + * \param symbolFormat DmtxFormatMatrix | DmtxFormatMosaic + * \return Address of allocated memory + */ +extern DmtxMessage * +dmtxMessageCreate(int sizeIdx, int symbolFormat) +{ + DmtxMessage *message; + int mappingRows, mappingCols; + + assert(symbolFormat == DmtxFormatMatrix || symbolFormat == DmtxFormatMosaic); + + mappingRows = dmtxGetSymbolAttribute(DmtxSymAttribMappingMatrixRows, sizeIdx); + mappingCols = dmtxGetSymbolAttribute(DmtxSymAttribMappingMatrixCols, sizeIdx); + + message = (DmtxMessage *)calloc(1, sizeof(DmtxMessage)); + if(message == NULL) + return NULL; + + message->arraySize = sizeof(unsigned char) * mappingRows * mappingCols; + + message->array = (unsigned char *)calloc(1, message->arraySize); + if(message->array == NULL) { + perror("Calloc failed"); + dmtxMessageDestroy(&message); + return NULL; + } + + message->codeSize = sizeof(unsigned char) * + dmtxGetSymbolAttribute(DmtxSymAttribSymbolDataWords, sizeIdx) + + dmtxGetSymbolAttribute(DmtxSymAttribSymbolErrorWords, sizeIdx); + + if(symbolFormat == DmtxFormatMosaic) + message->codeSize *= 3; + + message->code = (unsigned char *)calloc(message->codeSize, sizeof(unsigned char)); + if(message->code == NULL) { + perror("Calloc failed"); + dmtxMessageDestroy(&message); + return NULL; + } + + /* XXX not sure if this is the right place or even the right approach. + Trying to allocate memory for the decoded data stream and will + initially assume that decoded data will not be larger than 2x encoded data */ + message->outputSize = sizeof(unsigned char) * message->codeSize * 10; + message->output = (unsigned char *)calloc(message->outputSize, sizeof(unsigned char)); + if(message->output == NULL) { + perror("Calloc failed"); + dmtxMessageDestroy(&message); + return NULL; + } + + return message; +} + +/** + * \brief Free memory previously allocated for message + * \param message + * \return void + */ +extern DmtxPassFail +dmtxMessageDestroy(DmtxMessage **msg) +{ + if(msg == NULL || *msg == NULL) + return DmtxFail; + + if((*msg)->array != NULL) + free((*msg)->array); + + if((*msg)->code != NULL) + free((*msg)->code); + + if((*msg)->output != NULL) + free((*msg)->output); + + free(*msg); + + *msg = NULL; + + return DmtxPass; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtxregion.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2008, 2009 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file dmtxregion.c + * \brief Detect barcode regions + */ + +#define DMTX_HOUGH_RES 180 + +/** + * \brief Create copy of existing region struct + * \param None + * \return Initialized DmtxRegion struct + */ +extern DmtxRegion * +dmtxRegionCreate(DmtxRegion *reg) +{ + DmtxRegion *regCopy; + + regCopy = (DmtxRegion *)malloc(sizeof(DmtxRegion)); + if(regCopy == NULL) + return NULL; + + memcpy(regCopy, reg, sizeof(DmtxRegion)); + + return regCopy; +} + +/** + * \brief Destroy region struct + * \param reg + * \return void + */ +extern DmtxPassFail +dmtxRegionDestroy(DmtxRegion **reg) +{ + if(reg == NULL || *reg == NULL) + return DmtxFail; + + free(*reg); + + *reg = NULL; + + return DmtxPass; +} + +/** + * \brief Find next barcode region + * \param dec Pointer to DmtxDecode information struct + * \return Detected region (if found) + */ +extern DmtxRegion * +dmtxRegionFindNext(DmtxDecode *dec, int max_iterations, int *current_iterations) +{ + int locStatus; + DmtxPixelLoc loc; + DmtxRegion *reg; + + /* Continue until we find a region or run out of chances */ + for(; *current_iterations < max_iterations; *current_iterations += 1) { + locStatus = PopGridLocation(&(dec->grid), &loc); + if(locStatus == DmtxRangeEnd) + break; + + /* Scan location for presence of valid barcode region */ + reg = dmtxRegionScanPixel(dec, loc.X, loc.Y); + if(reg != NULL) + return reg; + } + + return NULL; +} + +/** + * \brief Scan individual pixel for presence of barcode edge + * \param dec Pointer to DmtxDecode information struct + * \param loc Pixel location + * \return Detected region (if any) + */ +extern DmtxRegion * +dmtxRegionScanPixel(DmtxDecode *dec, int x, int y) +{ + unsigned char *cache; + DmtxRegion reg; + DmtxPointFlow flowBegin; + DmtxPixelLoc loc; + + loc.X = x; + loc.Y = y; + + cache = dmtxDecodeGetCache(dec, loc.X, loc.Y); + if(cache == NULL) + return NULL; + + if((int)(*cache & 0x80) != 0x00) + return NULL; + + /* Test for presence of any reasonable edge at this location */ + flowBegin = MatrixRegionSeekEdge(dec, loc); + if(flowBegin.mag < (int)(dec->edgeThresh * 7.65 + 0.5)) + return NULL; + + memset(®, 0x00, sizeof(DmtxRegion)); + + /* Determine barcode orientation */ + if(MatrixRegionOrientation(dec, ®, flowBegin) == DmtxFail) + return NULL; + if(dmtxRegionUpdateXfrms(dec, ®) == DmtxFail) + return NULL; + + /* Define top edge */ + if(MatrixRegionAlignCalibEdge(dec, ®, DmtxEdgeTop) == DmtxFail) + return NULL; + if(dmtxRegionUpdateXfrms(dec, ®) == DmtxFail) + return NULL; + + /* Define right edge */ + if(MatrixRegionAlignCalibEdge(dec, ®, DmtxEdgeRight) == DmtxFail) + return NULL; + if(dmtxRegionUpdateXfrms(dec, ®) == DmtxFail) + return NULL; + + CALLBACK_MATRIX(®); + + /* Calculate the best fitting symbol size */ + if(MatrixRegionFindSize(dec, ®) == DmtxFail) + return NULL; + + /* Found a valid matrix region */ + return dmtxRegionCreate(®); +} + +/** + * + * + */ +static DmtxPointFlow +MatrixRegionSeekEdge(DmtxDecode *dec, DmtxPixelLoc loc) +{ + int i; + int strongIdx; + int channelCount; + DmtxPointFlow flow, flowPlane[3]; + DmtxPointFlow flowPos, flowPosBack; + DmtxPointFlow flowNeg, flowNegBack; + + channelCount = dec->image->channelCount; + + /* Find whether red, green, or blue shows the strongest edge */ + strongIdx = 0; + for(i = 0; i < channelCount; i++) { + flowPlane[i] = GetPointFlow(dec, i, loc, dmtxNeighborNone); + if(i > 0 && flowPlane[i].mag > flowPlane[strongIdx].mag) + strongIdx = i; + } + + if(flowPlane[strongIdx].mag < 10) + return dmtxBlankEdge; + + flow = flowPlane[strongIdx]; + + flowPos = FindStrongestNeighbor(dec, flow, +1); + flowNeg = FindStrongestNeighbor(dec, flow, -1); + if(flowPos.mag != 0 && flowNeg.mag != 0) { + flowPosBack = FindStrongestNeighbor(dec, flowPos, -1); + flowNegBack = FindStrongestNeighbor(dec, flowNeg, +1); + if(flowPos.arrive == (flowPosBack.arrive+4)%8 && + flowNeg.arrive == (flowNegBack.arrive+4)%8) { + flow.arrive = dmtxNeighborNone; + CALLBACK_POINT_PLOT(flow.loc, 1, 1, 1); + return flow; + } + } + + return dmtxBlankEdge; +} + +/** + * + * + */ +static DmtxPassFail +MatrixRegionOrientation(DmtxDecode *dec, DmtxRegion *reg, DmtxPointFlow begin) +{ + int cross; + int minArea; + int scale; + int symbolShape; + int maxDiagonal; + DmtxPassFail err; + DmtxBestLine line1x, line2x; + DmtxBestLine line2n, line2p; + DmtxFollow fTmp; + + if(dec->sizeIdxExpected == DmtxSymbolSquareAuto || + (dec->sizeIdxExpected >= DmtxSymbol10x10 && + dec->sizeIdxExpected <= DmtxSymbol144x144)) + symbolShape = DmtxSymbolSquareAuto; + else if(dec->sizeIdxExpected == DmtxSymbolRectAuto || + (dec->sizeIdxExpected >= DmtxSymbol8x18 && + dec->sizeIdxExpected <= DmtxSymbol16x48)) + symbolShape = DmtxSymbolRectAuto; + else + symbolShape = DmtxSymbolShapeAuto; + + if(dec->edgeMax != DmtxUndefined) { + if(symbolShape == DmtxSymbolRectAuto) + maxDiagonal = (int)(1.23 * dec->edgeMax + 0.5); /* sqrt(5/4) + 10% */ + else + maxDiagonal = (int)(1.56 * dec->edgeMax + 0.5); /* sqrt(2) + 10% */ + } + else { + maxDiagonal = DmtxUndefined; + } + + /* Follow to end in both directions */ + err = TrailBlazeContinuous(dec, reg, begin, maxDiagonal); + if(err == DmtxFail || reg->stepsTotal < 40) { + TrailClear(dec, reg, 0x40); + return DmtxFail; + } + + /* Filter out region candidates that are smaller than expected */ + if(dec->edgeMin != DmtxUndefined) { + scale = dmtxDecodeGetProp(dec, DmtxPropScale); + + if(symbolShape == DmtxSymbolSquareAuto) + minArea = (dec->edgeMin * dec->edgeMin)/(scale * scale); + else + minArea = (2 * dec->edgeMin * dec->edgeMin)/(scale * scale); + + if((reg->boundMax.X - reg->boundMin.X) * (reg->boundMax.Y - reg->boundMin.Y) < minArea) { + TrailClear(dec, reg, 0x40); + return DmtxFail; + } + } + + line1x = FindBestSolidLine(dec, reg, 0, 0, +1, DmtxUndefined); + if(line1x.mag < 5) { + TrailClear(dec, reg, 0x40); + return DmtxFail; + } + + err = FindTravelLimits(dec, reg, &line1x); + if(line1x.distSq < 100 || line1x.devn * 10 >= sqrt((float)line1x.distSq)) { + TrailClear(dec, reg, 0x40); + return DmtxFail; + } + assert(line1x.stepPos >= line1x.stepNeg); + + fTmp = FollowSeek(dec, reg, line1x.stepPos + 5); + line2p = FindBestSolidLine(dec, reg, fTmp.step, line1x.stepNeg, +1, line1x.angle); + + fTmp = FollowSeek(dec, reg, line1x.stepNeg - 5); + line2n = FindBestSolidLine(dec, reg, fTmp.step, line1x.stepPos, -1, line1x.angle); + if(max(line2p.mag, line2n.mag) < 5) + return DmtxFail; + + if(line2p.mag > line2n.mag) { + line2x = line2p; + err = FindTravelLimits(dec, reg, &line2x); + if(line2x.distSq < 100 || line2x.devn * 10 >= sqrt((float)line2x.distSq)) + return DmtxFail; + + cross = ((line1x.locPos.X - line1x.locNeg.X) * (line2x.locPos.Y - line2x.locNeg.Y)) - + ((line1x.locPos.Y - line1x.locNeg.Y) * (line2x.locPos.X - line2x.locNeg.X)); + if(cross > 0) { + /* Condition 2 */ + reg->polarity = +1; + reg->locR = line2x.locPos; + reg->stepR = line2x.stepPos; + reg->locT = line1x.locNeg; + reg->stepT = line1x.stepNeg; + reg->leftLoc = line1x.locBeg; + reg->leftAngle = line1x.angle; + reg->bottomLoc = line2x.locBeg; + reg->bottomAngle = line2x.angle; + reg->leftLine = line1x; + reg->bottomLine = line2x; + } + else { + /* Condition 3 */ + reg->polarity = -1; + reg->locR = line1x.locNeg; + reg->stepR = line1x.stepNeg; + reg->locT = line2x.locPos; + reg->stepT = line2x.stepPos; + reg->leftLoc = line2x.locBeg; + reg->leftAngle = line2x.angle; + reg->bottomLoc = line1x.locBeg; + reg->bottomAngle = line1x.angle; + reg->leftLine = line2x; + reg->bottomLine = line1x; + } + } + else { + line2x = line2n; + err = FindTravelLimits(dec, reg, &line2x); + if(line2x.distSq < 100 || line2x.devn / sqrt((float)line2x.distSq) >= 0.1) + return DmtxFail; + + cross = ((line1x.locNeg.X - line1x.locPos.X) * (line2x.locNeg.Y - line2x.locPos.Y)) - + ((line1x.locNeg.Y - line1x.locPos.Y) * (line2x.locNeg.X - line2x.locPos.X)); + if(cross > 0) { + /* Condition 1 */ + reg->polarity = -1; + reg->locR = line2x.locNeg; + reg->stepR = line2x.stepNeg; + reg->locT = line1x.locPos; + reg->stepT = line1x.stepPos; + reg->leftLoc = line1x.locBeg; + reg->leftAngle = line1x.angle; + reg->bottomLoc = line2x.locBeg; + reg->bottomAngle = line2x.angle; + reg->leftLine = line1x; + reg->bottomLine = line2x; + } + else { + /* Condition 4 */ + reg->polarity = +1; + reg->locR = line1x.locPos; + reg->stepR = line1x.stepPos; + reg->locT = line2x.locNeg; + reg->stepT = line2x.stepNeg; + reg->leftLoc = line2x.locBeg; + reg->leftAngle = line2x.angle; + reg->bottomLoc = line1x.locBeg; + reg->bottomAngle = line1x.angle; + reg->leftLine = line2x; + reg->bottomLine = line1x; + } + } +/* CALLBACK_POINT_PLOT(reg->locR, 2, 1, 1); + CALLBACK_POINT_PLOT(reg->locT, 2, 1, 1); */ + + reg->leftKnown = reg->bottomKnown = 1; + + return DmtxPass; +} + +/** + * + * + */ +static long +DistanceSquared(DmtxPixelLoc a, DmtxPixelLoc b) +{ + long xDelta, yDelta; + + xDelta = a.X - b.X; + yDelta = a.Y - b.Y; + + return (xDelta * xDelta) + (yDelta * yDelta); +} + +/** + * + * + */ +extern DmtxPassFail +dmtxRegionUpdateCorners(DmtxDecode *dec, DmtxRegion *reg, DmtxVector2 p00, + DmtxVector2 p10, DmtxVector2 p11, DmtxVector2 p01) +{ + float xMax, yMax; + float tx, ty, phi, shx, scx, scy, skx, sky; + float dimOT, dimOR, dimTX, dimRX, ratio; + DmtxVector2 vOT, vOR, vTX, vRX, vTmp; + DmtxMatrix3 m, mtxy, mphi, mshx, mscx, mscy, mscxy, msky, mskx; + + xMax = (float)(dmtxDecodeGetProp(dec, DmtxPropWidth) - 1); + yMax = (float)(dmtxDecodeGetProp(dec, DmtxPropHeight) - 1); + + if(p00.X < 0.0 || p00.Y < 0.0 || p00.X > xMax || p00.Y > yMax || + p01.X < 0.0 || p01.Y < 0.0 || p01.X > xMax || p01.Y > yMax || + p10.X < 0.0 || p10.Y < 0.0 || p10.X > xMax || p10.Y > yMax) + return DmtxFail; + + dimOT = dmtxVector2Mag(dmtxVector2Sub(&vOT, &p01, &p00)); /* XXX could use MagSquared() */ + dimOR = dmtxVector2Mag(dmtxVector2Sub(&vOR, &p10, &p00)); + dimTX = dmtxVector2Mag(dmtxVector2Sub(&vTX, &p11, &p01)); + dimRX = dmtxVector2Mag(dmtxVector2Sub(&vRX, &p11, &p10)); + + /* Verify that sides are reasonably long */ + if(dimOT <= 8.0 || dimOR <= 8.0 || dimTX <= 8.0 || dimRX <= 8.0) + return DmtxFail; + + /* Verify that the 4 corners define a reasonably fat quadrilateral */ + ratio = dimOT / dimRX; + if(ratio <= 0.5 || ratio >= 2.0) + return DmtxFail; + + ratio = dimOR / dimTX; + if(ratio <= 0.5 || ratio >= 2.0) + return DmtxFail; + + /* Verify this is not a bowtie shape */ + if(dmtxVector2Cross(&vOR, &vRX) <= 0.0 || + dmtxVector2Cross(&vOT, &vTX) >= 0.0) + return DmtxFail; + + if(RightAngleTrueness(p00, p10, p11, M_PI_2) <= dec->squareDevn) + return DmtxFail; + if(RightAngleTrueness(p10, p11, p01, M_PI_2) <= dec->squareDevn) + return DmtxFail; + + /* Calculate values needed for transformations */ + tx = -1 * p00.X; + ty = -1 * p00.Y; + dmtxMatrix3Translate(mtxy, tx, ty); + + phi = atan2(vOT.X, vOT.Y); + dmtxMatrix3Rotate(mphi, phi); + dmtxMatrix3Multiply(m, mtxy, mphi); + + dmtxMatrix3VMultiply(&vTmp, &p10, m); + shx = -vTmp.Y / vTmp.X; + dmtxMatrix3Shear(mshx, 0.0, shx); + dmtxMatrix3MultiplyBy(m, mshx); + + scx = 1.0/vTmp.X; + dmtxMatrix3Scale(mscx, scx, 1.0); + dmtxMatrix3MultiplyBy(m, mscx); + + dmtxMatrix3VMultiply(&vTmp, &p11, m); + scy = 1.0/vTmp.Y; + dmtxMatrix3Scale(mscy, 1.0, scy); + dmtxMatrix3MultiplyBy(m, mscy); + + dmtxMatrix3VMultiply(&vTmp, &p11, m); + skx = vTmp.X; + dmtxMatrix3LineSkewSide(mskx, 1.0, skx, 1.0); + dmtxMatrix3MultiplyBy(m, mskx); + + dmtxMatrix3VMultiply(&vTmp, &p01, m); + sky = vTmp.Y; + dmtxMatrix3LineSkewTop(msky, sky, 1.0, 1.0); + dmtxMatrix3Multiply(reg->raw2fit, m, msky); + + /* Create inverse matrix by reverse (avoid straight matrix inversion) */ + dmtxMatrix3LineSkewTopInv(msky, sky, 1.0, 1.0); + dmtxMatrix3LineSkewSideInv(mskx, 1.0, skx, 1.0); + dmtxMatrix3Multiply(m, msky, mskx); + + dmtxMatrix3Scale(mscxy, 1.0/scx, 1.0/scy); + dmtxMatrix3MultiplyBy(m, mscxy); + + dmtxMatrix3Shear(mshx, 0.0, -shx); + dmtxMatrix3MultiplyBy(m, mshx); + + dmtxMatrix3Rotate(mphi, -phi); + dmtxMatrix3MultiplyBy(m, mphi); + + dmtxMatrix3Translate(mtxy, -tx, -ty); + dmtxMatrix3Multiply(reg->fit2raw, m, mtxy); + + return DmtxPass; +} + +/** + * + * + */ +extern DmtxPassFail +dmtxRegionUpdateXfrms(DmtxDecode *dec, DmtxRegion *reg) +{ + float radians; + DmtxRay2 rLeft, rBottom, rTop, rRight; + DmtxVector2 p00, p10, p11, p01; + + assert(reg->leftKnown != 0 && reg->bottomKnown != 0); + + /* Build ray representing left edge */ + rLeft.p.X = (float)reg->leftLoc.X; + rLeft.p.Y = (float)reg->leftLoc.Y; + radians = reg->leftAngle * (M_PI/DMTX_HOUGH_RES); + rLeft.v.X = cos(radians); + rLeft.v.Y = sin(radians); + rLeft.tMin = 0.0; + rLeft.tMax = dmtxVector2Norm(&rLeft.v); + + /* Build ray representing bottom edge */ + rBottom.p.X = (float)reg->bottomLoc.X; + rBottom.p.Y = (float)reg->bottomLoc.Y; + radians = reg->bottomAngle * (M_PI/DMTX_HOUGH_RES); + rBottom.v.X = cos(radians); + rBottom.v.Y = sin(radians); + rBottom.tMin = 0.0; + rBottom.tMax = dmtxVector2Norm(&rBottom.v); + + /* Build ray representing top edge */ + if(reg->topKnown != 0) { + rTop.p.X = (float)reg->topLoc.X; + rTop.p.Y = (float)reg->topLoc.Y; + radians = reg->topAngle * (M_PI/DMTX_HOUGH_RES); + rTop.v.X = cos(radians); + rTop.v.Y = sin(radians); + rTop.tMin = 0.0; + rTop.tMax = dmtxVector2Norm(&rTop.v); + } + else { + rTop.p.X = (float)reg->locT.X; + rTop.p.Y = (float)reg->locT.Y; + radians = reg->bottomAngle * (M_PI/DMTX_HOUGH_RES); + rTop.v.X = cos(radians); + rTop.v.Y = sin(radians); + rTop.tMin = 0.0; + rTop.tMax = rBottom.tMax; + } + + /* Build ray representing right edge */ + if(reg->rightKnown != 0) { + rRight.p.X = (float)reg->rightLoc.X; + rRight.p.Y = (float)reg->rightLoc.Y; + radians = reg->rightAngle * (M_PI/DMTX_HOUGH_RES); + rRight.v.X = cos(radians); + rRight.v.Y = sin(radians); + rRight.tMin = 0.0; + rRight.tMax = dmtxVector2Norm(&rRight.v); + } + else { + rRight.p.X = (float)reg->locR.X; + rRight.p.Y = (float)reg->locR.Y; + radians = reg->leftAngle * (M_PI/DMTX_HOUGH_RES); + rRight.v.X = cos(radians); + rRight.v.Y = sin(radians); + rRight.tMin = 0.0; + rRight.tMax = rLeft.tMax; + } + + /* Calculate 4 corners, real or imagined */ + if(dmtxRay2Intersect(&p00, &rLeft, &rBottom) == DmtxFail) + return DmtxFail; + + if(dmtxRay2Intersect(&p10, &rBottom, &rRight) == DmtxFail) + return DmtxFail; + + if(dmtxRay2Intersect(&p11, &rRight, &rTop) == DmtxFail) + return DmtxFail; + + if(dmtxRay2Intersect(&p01, &rTop, &rLeft) == DmtxFail) + return DmtxFail; + + if(dmtxRegionUpdateCorners(dec, reg, p00, p10, p11, p01) != DmtxPass) + return DmtxFail; + + return DmtxPass; +} + +/** + * + * + */ +static float +RightAngleTrueness(DmtxVector2 c0, DmtxVector2 c1, DmtxVector2 c2, float angle) +{ + DmtxVector2 vA, vB; + DmtxMatrix3 m; + + dmtxVector2Norm(dmtxVector2Sub(&vA, &c0, &c1)); + dmtxVector2Norm(dmtxVector2Sub(&vB, &c2, &c1)); + + dmtxMatrix3Rotate(m, angle); + dmtxMatrix3VMultiplyBy(&vB, m); + + return dmtxVector2Dot(&vA, &vB); +} + +void Matrix3VMultFast(DmtxVector2 *vIn, DmtxMatrix3 m) +{ + float w; + float x, y; + + w = vIn->X*m[0][2] + vIn->Y*m[1][2] + m[2][2]; + if(fabsf(w) <= DmtxAlmostZero) { + vIn->X = FLT_MAX; + vIn->Y = FLT_MAX; + return; + } + + x = (vIn->X*m[0][0] + vIn->Y*m[1][0] + m[2][0])/w; + y = (vIn->X*m[0][1] + vIn->Y*m[1][1] + m[2][1])/w; + vIn->X = x; vIn->Y = y; + return; +} + +/** + * \brief Read color of Data Matrix module location + * \param dec + * \param reg + * \param symbolRow + * \param symbolCol + * \param sizeIdx + * \return Averaged module color + */ +static int +ReadModuleColor(DmtxDecode *dec, DmtxRegion *reg, int symbolRow, int symbolCol, + int sizeIdx, int colorPlane) +{ + int err; + int i; + int symbolRows, symbolCols; + int color, colorTmp; + float sampleX[] = { 0.5, 0.4, 0.5, 0.6, 0.5 }; + float sampleY[] = { 0.5, 0.5, 0.4, 0.5, 0.6 }; + DmtxVector2 p; + + symbolRows = dmtxGetSymbolAttribute(DmtxSymAttribSymbolRows, sizeIdx); + symbolCols = dmtxGetSymbolAttribute(DmtxSymAttribSymbolCols, sizeIdx); + + color = colorTmp = 0; + if (dec->image->channelCount == 1) // quicker for grayscale + { + int x, y; + for(i = 0; i < 5; i++) { + + p.X = (1.0/symbolCols) * (symbolCol + sampleX[i]); + p.Y = (1.0/symbolRows) * (symbolRow + sampleY[i]); + +// dmtxMatrix3VMultiplyBy(&p, reg->fit2raw); + Matrix3VMultFast(&p, reg->fit2raw); + x = (int)(p.X + 0.5f); + y = (int)(p.Y + 0.5f); + if (x >= 0 && y >= 0 && x < dec->image->width && y < dec->image->height) + colorTmp = dec->image->pxl[(dec->image->height - 1 - y) * dec->image->rowSizeBytes + x]; + // err = dmtxDecodeGetPixelValue(dec, (int)(p.X + 0.5), (int)(p.Y + 0.5), + // colorPlane, &colorTmp); + color += colorTmp; + } + } + else + { + for(i = 0; i < 5; i++) { + + p.X = (1.0/symbolCols) * (symbolCol + sampleX[i]); + p.Y = (1.0/symbolRows) * (symbolRow + sampleY[i]); + + dmtxMatrix3VMultiplyBy(&p, reg->fit2raw); + + err = dmtxDecodeGetPixelValue(dec, (int)(p.X + 0.5), (int)(p.Y + 0.5), + colorPlane, &colorTmp); + color += colorTmp; + } + } + + return color/5; +} + +/** + * \brief Determine barcode size, expressed in modules + * \param image + * \param reg + * \return DmtxPass | DmtxFail + */ +static DmtxPassFail +MatrixRegionFindSize(DmtxDecode *dec, DmtxRegion *reg) +{ + int row, col; + int sizeIdxBeg, sizeIdxEnd; + int sizeIdx, bestSizeIdx; + int symbolRows, symbolCols; + int jumpCount, errors; + int color; + int colorOnAvg, bestColorOnAvg; + int colorOffAvg, bestColorOffAvg; + int contrast, bestContrast; + DmtxImage *img; + + img = dec->image; + bestSizeIdx = DmtxUndefined; + bestContrast = 0; + bestColorOnAvg = bestColorOffAvg = 0; + + if(dec->sizeIdxExpected == DmtxSymbolShapeAuto) { + sizeIdxBeg = 0; + sizeIdxEnd = DmtxSymbolSquareCount + DmtxSymbolRectCount; + } + else if(dec->sizeIdxExpected == DmtxSymbolSquareAuto) { + sizeIdxBeg = 0; + sizeIdxEnd = DmtxSymbolSquareCount; + } + else if(dec->sizeIdxExpected == DmtxSymbolRectAuto) { + sizeIdxBeg = DmtxSymbolSquareCount; + sizeIdxEnd = DmtxSymbolSquareCount + DmtxSymbolRectCount; + } + else { + sizeIdxBeg = dec->sizeIdxExpected; + sizeIdxEnd = dec->sizeIdxExpected + 1; + } + + /* Test each barcode size to find best contrast in calibration modules */ + for(sizeIdx = sizeIdxBeg; sizeIdx < sizeIdxEnd; sizeIdx++) { + + symbolRows = dmtxGetSymbolAttribute(DmtxSymAttribSymbolRows, sizeIdx); + symbolCols = dmtxGetSymbolAttribute(DmtxSymAttribSymbolCols, sizeIdx); + colorOnAvg = colorOffAvg = 0; + + /* Sum module colors along horizontal calibration bar */ + row = symbolRows - 1; + for(col = 0; col < symbolCols; col++) { + color = ReadModuleColor(dec, reg, row, col, sizeIdx, reg->flowBegin.plane); + if((col & 0x01) != 0x00) + colorOffAvg += color; + else + colorOnAvg += color; + } + + /* Sum module colors along vertical calibration bar */ + col = symbolCols - 1; + for(row = 0; row < symbolRows; row++) { + color = ReadModuleColor(dec, reg, row, col, sizeIdx, reg->flowBegin.plane); + if((row & 0x01) != 0x00) + colorOffAvg += color; + else + colorOnAvg += color; + } + + colorOnAvg = (colorOnAvg * 2)/(symbolRows + symbolCols); + colorOffAvg = (colorOffAvg * 2)/(symbolRows + symbolCols); + + contrast = abs(colorOnAvg - colorOffAvg); + if(contrast < 20) + continue; + + if(contrast > bestContrast) { + bestContrast = contrast; + bestSizeIdx = sizeIdx; + bestColorOnAvg = colorOnAvg; + bestColorOffAvg = colorOffAvg; + } + } + + /* If no sizes produced acceptable contrast then call it quits */ + if(bestSizeIdx == DmtxUndefined || bestContrast < 20) + return DmtxFail; + + reg->sizeIdx = bestSizeIdx; + reg->onColor = bestColorOnAvg; + reg->offColor = bestColorOffAvg; + + reg->symbolRows = dmtxGetSymbolAttribute(DmtxSymAttribSymbolRows, reg->sizeIdx); + reg->symbolCols = dmtxGetSymbolAttribute(DmtxSymAttribSymbolCols, reg->sizeIdx); + reg->mappingRows = dmtxGetSymbolAttribute(DmtxSymAttribMappingMatrixRows, reg->sizeIdx); + reg->mappingCols = dmtxGetSymbolAttribute(DmtxSymAttribMappingMatrixCols, reg->sizeIdx); + + /* Tally jumps on horizontal calibration bar to verify sizeIdx */ + jumpCount = CountJumpTally(dec, reg, 0, reg->symbolRows - 1, DmtxDirRight); + errors = abs(1 + jumpCount - reg->symbolCols); + if(jumpCount < 0 || errors > 2) + return DmtxFail; + + /* Tally jumps on vertical calibration bar to verify sizeIdx */ + jumpCount = CountJumpTally(dec, reg, reg->symbolCols - 1, 0, DmtxDirUp); + errors = abs(1 + jumpCount - reg->symbolRows); + if(jumpCount < 0 || errors > 2) + return DmtxFail; + + /* Tally jumps on horizontal finder bar to verify sizeIdx */ + errors = CountJumpTally(dec, reg, 0, 0, DmtxDirRight); + if(jumpCount < 0 || errors > 2) + return DmtxFail; + + /* Tally jumps on vertical finder bar to verify sizeIdx */ + errors = CountJumpTally(dec, reg, 0, 0, DmtxDirUp); + if(errors < 0 || errors > 2) + return DmtxFail; + + /* Tally jumps on surrounding whitespace, else fail */ + errors = CountJumpTally(dec, reg, 0, -1, DmtxDirRight); + if(errors < 0 || errors > 2) + return DmtxFail; + + errors = CountJumpTally(dec, reg, -1, 0, DmtxDirUp); + if(errors < 0 || errors > 2) + return DmtxFail; + + errors = CountJumpTally(dec, reg, 0, reg->symbolRows, DmtxDirRight); + if(errors < 0 || errors > 2) + return DmtxFail; + + errors = CountJumpTally(dec, reg, reg->symbolCols, 0, DmtxDirUp); + if(errors < 0 || errors > 2) + return DmtxFail; + + return DmtxPass; +} + +/** + * \brief Count the number of number of transitions between light and dark + * \param img + * \param reg + * \param xStart + * \param yStart + * \param dir + * \return Jump count + */ +static int +CountJumpTally(DmtxDecode *dec, DmtxRegion *reg, int xStart, int yStart, DmtxDirection dir) +{ + int x, xInc = 0; + int y, yInc = 0; + int state = DmtxModuleOn; + int jumpCount = 0; + int jumpThreshold; + int tModule, tPrev; + int darkOnLight; + int color; + + assert(xStart == 0 || yStart == 0); + assert(dir == DmtxDirRight || dir == DmtxDirUp); + + if(dir == DmtxDirRight) + xInc = 1; + else + yInc = 1; + + if(xStart == -1 || xStart == reg->symbolCols || + yStart == -1 || yStart == reg->symbolRows) + state = DmtxModuleOff; + + darkOnLight = (int)(reg->offColor > reg->onColor); + jumpThreshold = abs((int)(0.4 * (reg->onColor - reg->offColor) + 0.5)); + color = ReadModuleColor(dec, reg, yStart, xStart, reg->sizeIdx, reg->flowBegin.plane); + tModule = (darkOnLight) ? reg->offColor - color : color - reg->offColor; + + for(x = xStart + xInc, y = yStart + yInc; + (dir == DmtxDirRight && x < reg->symbolCols) || + (dir == DmtxDirUp && y < reg->symbolRows); + x += xInc, y += yInc) { + + tPrev = tModule; + color = ReadModuleColor(dec, reg, y, x, reg->sizeIdx, reg->flowBegin.plane); + tModule = (darkOnLight) ? reg->offColor - color : color - reg->offColor; + + if(state == DmtxModuleOff) { + if(tModule > tPrev + jumpThreshold) { + jumpCount++; + state = DmtxModuleOn; + } + } + else { + if(tModule < tPrev - jumpThreshold) { + jumpCount++; + state = DmtxModuleOff; + } + } + } + + return jumpCount; +} + +/** + * + * + */ +static DmtxPointFlow +GetPointFlow(DmtxDecode *dec, int colorPlane, DmtxPixelLoc loc, int arrive) +{ + static const int coefficient[] = { 0, 1, 2, 1, 0, -1, -2, -1 }; + int err; + int patternIdx, coefficientIdx; + int compass, compassMax; + int mag[4] = { 0 }; + int xAdjust, yAdjust; + int color, colorPattern[8]; + DmtxPointFlow flow; + + // check boundary conditions outside of the loop + if (loc.X <= 0 || loc.Y <= 0 || loc.X >= dec->image->width-1 || loc.Y >= dec->image->height-1) + return dmtxBlankEdge; // one or more pixels are past an edge + if (dec->image->channelCount == 1) // grayscale, do it quicker + { + uint8_t *s; + s = &dec->image->pxl[(dec->image->height - 1 - loc.Y) * dec->image->rowSizeBytes + loc.X]; + for (patternIdx=0; patternIdx < 8; patternIdx++) + { + colorPattern[patternIdx] = s[dmtxPatternX[patternIdx] - dmtxPatternY[patternIdx] * dec->image->rowSizeBytes]; + } + } + else + { + for(patternIdx = 0; patternIdx < 8; patternIdx++) { + xAdjust = loc.X + dmtxPatternX[patternIdx]; + yAdjust = loc.Y + dmtxPatternY[patternIdx]; + err = dmtxDecodeGetPixelValue(dec, xAdjust, yAdjust, colorPlane, + &colorPattern[patternIdx]); + if(err == DmtxFail) + return dmtxBlankEdge; + } + } + + /* Calculate this pixel's flow intensity for each direction (-45, 0, 45, 90) */ + compassMax = 0; + for(compass = 0; compass < 4; compass++) { + + /* Add portion from each position in the convolution matrix pattern */ + for(patternIdx = 0; patternIdx < 8; patternIdx++) { + + color = colorPattern[patternIdx]; + coefficientIdx = (patternIdx - compass + 8) % 8; +// if(coefficient[coefficientIdx] == 0) +// continue; + + mag[compass] += color * coefficient[coefficientIdx]; + } + + /* Identify strongest compass flow */ + if(compass != 0 && abs(mag[compass]) > abs(mag[compassMax])) + compassMax = compass; + } + + /* Convert signed compass direction into unique flow directions (0-7) */ + flow.plane = colorPlane; + flow.arrive = arrive; + flow.depart = (mag[compassMax] > 0) ? compassMax + 4 : compassMax; + flow.mag = abs(mag[compassMax]); + flow.loc = loc; + + return flow; +} + +/** + * + * + */ +static DmtxPointFlow +FindStrongestNeighbor(DmtxDecode *dec, DmtxPointFlow center, int sign) +{ + int i; + int strongIdx; + int attempt, attemptDiff; + int occupied; + unsigned char *cache; + DmtxPixelLoc loc; + DmtxPointFlow flow[8]; + + attempt = (sign < 0) ? center.depart : (center.depart+4)%8; + + occupied = 0; + strongIdx = DmtxUndefined; + for(i = 0; i < 8; i++) { + + loc.X = center.loc.X + dmtxPatternX[i]; + loc.Y = center.loc.Y + dmtxPatternY[i]; + + cache = dmtxDecodeGetCache(dec, loc.X, loc.Y); + if(cache == NULL) + continue; + + if((int)(*cache & 0x80) != 0x00) { + if(++occupied > 2) + return dmtxBlankEdge; + else + continue; + } + + attemptDiff = abs(attempt - i); + if(attemptDiff > 4) + attemptDiff = 8 - attemptDiff; + if(attemptDiff > 1) + continue; + + flow[i] = GetPointFlow(dec, center.plane, loc, i); + + if(strongIdx == DmtxUndefined || flow[i].mag > flow[strongIdx].mag || + (flow[i].mag == flow[strongIdx].mag && ((i & 0x01) != 0))) { + strongIdx = i; + } + } + + return (strongIdx == DmtxUndefined) ? dmtxBlankEdge : flow[strongIdx]; +} + +/** + * + * + */ +static DmtxFollow +FollowSeek(DmtxDecode *dec, DmtxRegion *reg, int seek) +{ + int i; + int sign; + DmtxFollow follow; + + follow.loc = reg->flowBegin.loc; + follow.step = 0; + follow.ptr = dmtxDecodeGetCache(dec, follow.loc.X, follow.loc.Y); + assert(follow.ptr != NULL); + follow.neighbor = *follow.ptr; + + sign = (seek > 0) ? +1 : -1; + for(i = 0; i != seek; i += sign) { + follow = FollowStep(dec, reg, follow, sign); + assert(follow.ptr != NULL); + assert(abs(follow.step) <= reg->stepsTotal); + } + + return follow; +} + +/** + * + * + */ +static DmtxFollow +FollowSeekLoc(DmtxDecode *dec, DmtxPixelLoc loc) +{ + DmtxFollow follow; + + follow.loc = loc; + follow.step = 0; + follow.ptr = dmtxDecodeGetCache(dec, follow.loc.X, follow.loc.Y); + assert(follow.ptr != NULL); + follow.neighbor = *follow.ptr; + + return follow; +} + + +/** + * + * + */ +static DmtxFollow +FollowStep(DmtxDecode *dec, DmtxRegion *reg, DmtxFollow followBeg, int sign) +{ + int patternIdx; + int stepMod; + int factor; + DmtxFollow follow; + + assert(abs(sign) == 1); + assert((int)(followBeg.neighbor & 0x40) != 0x00); + + factor = reg->stepsTotal + 1; + if(sign > 0) + stepMod = (factor + (followBeg.step % factor)) % factor; + else + stepMod = (factor - (followBeg.step % factor)) % factor; + + /* End of positive trail -- magic jump */ + if(sign > 0 && stepMod == reg->jumpToNeg) { + follow.loc = reg->finalNeg; + } + /* End of negative trail -- magic jump */ + else if(sign < 0 && stepMod == reg->jumpToPos) { + follow.loc = reg->finalPos; + } + /* Trail in progress -- normal jump */ + else { + patternIdx = (sign < 0) ? followBeg.neighbor & 0x07 : ((followBeg.neighbor & 0x38) >> 3); + follow.loc.X = followBeg.loc.X + dmtxPatternX[patternIdx]; + follow.loc.Y = followBeg.loc.Y + dmtxPatternY[patternIdx]; + } + + follow.step = followBeg.step + sign; + follow.ptr = dmtxDecodeGetCache(dec, follow.loc.X, follow.loc.Y); + assert(follow.ptr != NULL); + follow.neighbor = *follow.ptr; + + return follow; +} + +/** + * + * + */ +static DmtxFollow +FollowStep2(DmtxDecode *dec, DmtxFollow followBeg, int sign) +{ + int patternIdx; + DmtxFollow follow; + + assert(abs(sign) == 1); + assert((int)(followBeg.neighbor & 0x40) != 0x00); + + patternIdx = (sign < 0) ? followBeg.neighbor & 0x07 : ((followBeg.neighbor & 0x38) >> 3); + follow.loc.X = followBeg.loc.X + dmtxPatternX[patternIdx]; + follow.loc.Y = followBeg.loc.Y + dmtxPatternY[patternIdx]; + + follow.step = followBeg.step + sign; + follow.ptr = dmtxDecodeGetCache(dec, follow.loc.X, follow.loc.Y); + assert(follow.ptr != NULL); + follow.neighbor = *follow.ptr; + + return follow; +} + +/** + * vaiiiooo + * -------- + * 0x80 v = visited bit + * 0x40 a = assigned bit + * 0x38 u = 3 bits points upstream 0-7 + * 0x07 d = 3 bits points downstream 0-7 + */ +static DmtxPassFail +TrailBlazeContinuous(DmtxDecode *dec, DmtxRegion *reg, DmtxPointFlow flowBegin, int maxDiagonal) +{ + int posAssigns, negAssigns, clears; + int sign; + int steps; + unsigned char *cache, *cacheNext, *cacheBeg; + DmtxPointFlow flow, flowNext; + DmtxPixelLoc boundMin, boundMax; + + boundMin = boundMax = flowBegin.loc; + cacheBeg = dmtxDecodeGetCache(dec, flowBegin.loc.X, flowBegin.loc.Y); + if(cacheBeg == NULL) + return DmtxFail; + *cacheBeg = (0x80 | 0x40); /* Mark location as visited and assigned */ + + reg->flowBegin = flowBegin; + + posAssigns = negAssigns = 0; + for(sign = 1; sign >= -1; sign -= 2) { + + flow = flowBegin; + cache = cacheBeg; + + for(steps = 0; ; steps++) { + + if(maxDiagonal != DmtxUndefined && (boundMax.X - boundMin.X > maxDiagonal || + boundMax.Y - boundMin.Y > maxDiagonal)) + break; + + /* Find the strongest eligible neighbor */ + flowNext = FindStrongestNeighbor(dec, flow, sign); + if(flowNext.mag < 50) + break; + + /* Get the neighbor's cache location */ + cacheNext = dmtxDecodeGetCache(dec, flowNext.loc.X, flowNext.loc.Y); + if(cacheNext == NULL) + break; + assert(!(*cacheNext & 0x80)); + + /* Mark departure from current location. If flowing downstream + * (sign < 0) then departure vector here is the arrival vector + * of the next location. Upstream flow uses the opposite rule. */ + *cache |= (sign < 0) ? flowNext.arrive : flowNext.arrive << 3; + + /* Mark known direction for next location */ + /* If testing downstream (sign < 0) then next upstream is opposite of next arrival */ + /* If testing upstream (sign > 0) then next downstream is opposite of next arrival */ + *cacheNext = (sign < 0) ? (((flowNext.arrive + 4)%8) << 3) : ((flowNext.arrive + 4)%8); + *cacheNext |= (0x80 | 0x40); /* Mark location as visited and assigned */ + if(sign > 0) + posAssigns++; + else + negAssigns++; + cache = cacheNext; + flow = flowNext; + + if(flow.loc.X > boundMax.X) + boundMax.X = flow.loc.X; + else if(flow.loc.X < boundMin.X) + boundMin.X = flow.loc.X; + if(flow.loc.Y > boundMax.Y) + boundMax.Y = flow.loc.Y; + else if(flow.loc.Y < boundMin.Y) + boundMin.Y = flow.loc.Y; + +/* CALLBACK_POINT_PLOT(flow.loc, (sign > 0) ? 2 : 3, 1, 2); */ + } + + if(sign > 0) { + reg->finalPos = flow.loc; + reg->jumpToNeg = steps; + } + else { + reg->finalNeg = flow.loc; + reg->jumpToPos = steps; + } + } + reg->stepsTotal = reg->jumpToPos + reg->jumpToNeg; + reg->boundMin = boundMin; + reg->boundMax = boundMax; + + /* Clear "visited" bit from trail */ + clears = TrailClear(dec, reg, 0x80); + assert(posAssigns + negAssigns == clears - 1); + + /* XXX clean this up ... redundant test above */ + if(maxDiagonal != DmtxUndefined && (boundMax.X - boundMin.X > maxDiagonal || + boundMax.Y - boundMin.Y > maxDiagonal)) + return DmtxFail; + + return DmtxPass; +} + +/** + * recives bresline, and follows strongest neighbor unless it involves + * ratcheting bresline inward or backward (although back + outward is allowed). + * + */ +static int +TrailBlazeGapped(DmtxDecode *dec, DmtxRegion *reg, DmtxBresLine line, int streamDir) +{ + unsigned char *beforeCache, *afterCache; + DmtxBoolean onEdge; + int distSq, distSqMax; + int travel, outward; + int xDiff, yDiff; + int steps; + int stepDir, dirMap[] = { 0, 1, 2, 7, 8, 3, 6, 5, 4 }; + DmtxPassFail err; + DmtxPixelLoc beforeStep, afterStep; + DmtxPointFlow flow, flowNext; + DmtxPixelLoc loc0; + int xStep, yStep; + + loc0 = line.loc; + flow = GetPointFlow(dec, reg->flowBegin.plane, loc0, dmtxNeighborNone); + distSqMax = (line.xDelta * line.xDelta) + (line.yDelta * line.yDelta); + steps = 0; + onEdge = DmtxTrue; + + beforeStep = loc0; + beforeCache = dmtxDecodeGetCache(dec, loc0.X, loc0.Y); + if(beforeCache == NULL) + return DmtxFail; + else + *beforeCache = 0x00; /* probably should just overwrite one direction */ + + do { + if(onEdge == DmtxTrue) { + flowNext = FindStrongestNeighbor(dec, flow, streamDir); + if(flowNext.mag == DmtxUndefined) + break; + + err = BresLineGetStep(line, flowNext.loc, &travel, &outward); + if(flowNext.mag < 50 || outward < 0 || (outward == 0 && travel < 0)) { + onEdge = DmtxFalse; + } + else { + BresLineStep(&line, travel, outward); + flow = flowNext; + } + } + + if(onEdge == DmtxFalse) { + BresLineStep(&line, 1, 0); + flow = GetPointFlow(dec, reg->flowBegin.plane, line.loc, dmtxNeighborNone); + if(flow.mag > 50) + onEdge = DmtxTrue; + } + + afterStep = line.loc; + afterCache = dmtxDecodeGetCache(dec, afterStep.X, afterStep.Y); + if(afterCache == NULL) + break; + + /* Determine step direction using pure magic */ + xStep = afterStep.X - beforeStep.X; + yStep = afterStep.Y - beforeStep.Y; + assert(abs(xStep) <= 1 && abs(yStep) <= 1); + stepDir = dirMap[3 * yStep + xStep + 4]; + assert(stepDir != 8); + + if(streamDir < 0) { + *beforeCache |= (0x40 | stepDir); + *afterCache = (((stepDir + 4)%8) << 3); + } + else { + *beforeCache |= (0x40 | (stepDir << 3)); + *afterCache = ((stepDir + 4)%8); + } + + /* Guaranteed to have taken one step since top of loop */ + xDiff = line.loc.X - loc0.X; + yDiff = line.loc.Y - loc0.Y; + distSq = (xDiff * xDiff) + (yDiff * yDiff); + + beforeStep = line.loc; + beforeCache = afterCache; + steps++; + + } while(distSq < distSqMax); + + return steps; +} + +/** + * + * + */ +static int +TrailClear(DmtxDecode *dec, DmtxRegion *reg, int clearMask) +{ + int clears; + DmtxFollow follow; + + assert((clearMask | 0xff) == 0xff); + + /* Clear "visited" bit from trail */ + clears = 0; + follow = FollowSeek(dec, reg, 0); + while(abs(follow.step) <= reg->stepsTotal) { + assert((int)(*follow.ptr & clearMask) != 0x00); + *follow.ptr &= (clearMask ^ 0xff); + follow = FollowStep(dec, reg, follow, +1); + clears++; + } + + return clears; +} + +/** + * + * + */ +static DmtxBestLine +FindBestSolidLine(DmtxDecode *dec, DmtxRegion *reg, int step0, int step1, int streamDir, int houghAvoid) +{ + int *hough_temp = calloc(3 * DMTX_HOUGH_RES, sizeof(int)); int (*hough)[DMTX_HOUGH_RES] = (int (*)[DMTX_HOUGH_RES]) hough_temp; // [3][DMTX_HOUGH_RES] = { { 0 } }; + int houghMin, houghMax; + char *houghTest = malloc(DMTX_HOUGH_RES); // [DMTX_HOUGH_RES]; + int i; + int step; + int sign; + int tripSteps; + int angleBest; + int hOffset, hOffsetBest; + int xDiff, yDiff; + int dH; + DmtxRay2 rH; + DmtxFollow follow; + DmtxBestLine line; + DmtxPixelLoc rHp; + + memset(&line, 0x00, sizeof(DmtxBestLine)); + memset(&rH, 0x00, sizeof(DmtxRay2)); + angleBest = 0; + hOffset = hOffsetBest = 0; + + /* Always follow path flowing away from the trail start */ + if(step0 != 0) { + if(step0 > 0) { + sign = +1; + tripSteps = (step1 - step0 + reg->stepsTotal) % reg->stepsTotal; + } + else { + sign = -1; + tripSteps = (step0 - step1 + reg->stepsTotal) % reg->stepsTotal; + } + if(tripSteps == 0) + tripSteps = reg->stepsTotal; + } + else if(step1 != 0) { + sign = (step1 > 0) ? +1 : -1; + tripSteps = abs(step1); + } + else if(step1 == 0) { + sign = +1; + tripSteps = reg->stepsTotal; + } + assert(sign == streamDir); + + follow = FollowSeek(dec, reg, step0); + rHp = follow.loc; + + line.stepBeg = line.stepPos = line.stepNeg = step0; + line.locBeg = follow.loc; + line.locPos = follow.loc; + line.locNeg = follow.loc; + + /* Predetermine which angles to test */ + for(i = 0; i < DMTX_HOUGH_RES; i++) { + if(houghAvoid == DmtxUndefined) { + houghTest[i] = 1; + } + else { + houghMin = (houghAvoid + DMTX_HOUGH_RES/6) % DMTX_HOUGH_RES; + houghMax = (houghAvoid - DMTX_HOUGH_RES/6 + DMTX_HOUGH_RES) % DMTX_HOUGH_RES; + if(houghMin > houghMax) + houghTest[i] = (i > houghMin || i < houghMax) ? 1 : 0; + else + houghTest[i] = (i > houghMin && i < houghMax) ? 1 : 0; + } + } + + /* Test each angle for steps along path */ + for(step = 0; step < tripSteps; step++) { + + xDiff = follow.loc.X - rHp.X; + yDiff = follow.loc.Y - rHp.Y; + + /* Increment Hough accumulator */ + for(i = 0; i < DMTX_HOUGH_RES; i++) { + + if((int)houghTest[i] == 0) + continue; + + dH = (rHvX[i] * yDiff) - (rHvY[i] * xDiff); + if(dH >= -384 && dH <= 384) { + + if(dH > 128) + hOffset = 2; + else if(dH >= -128) + hOffset = 1; + else + hOffset = 0; + + hough[hOffset][i]++; + + /* New angle takes over lead */ + if(hough[hOffset][i] > hough[hOffsetBest][angleBest]) { + angleBest = i; + hOffsetBest = hOffset; + } + } + } + +/* CALLBACK_POINT_PLOT(follow.loc, (sign > 1) ? 4 : 3, 1, 2); */ + + follow = FollowStep(dec, reg, follow, sign); + } + + line.angle = angleBest; + line.hOffset = hOffsetBest; + line.mag = hough[hOffsetBest][angleBest]; + + free(houghTest); + free(hough_temp); + + return line; +} + +/** + * + * + */ +static DmtxBestLine +FindBestSolidLine2(DmtxDecode *dec, DmtxPixelLoc loc0, int tripSteps, int sign, int houghAvoid) +{ + int *hough_temp = calloc(3 * DMTX_HOUGH_RES, sizeof(int)); int (*hough)[DMTX_HOUGH_RES] = (int (*)[DMTX_HOUGH_RES]) hough_temp; // [3][DMTX_HOUGH_RES] = { { 0 } }; + int houghMin, houghMax; + char *houghTest = malloc(DMTX_HOUGH_RES); // [DMTX_HOUGH_RES]; + int i; + int step; + int angleBest; + int hOffset, hOffsetBest; + int xDiff, yDiff; + int dH; + DmtxRay2 rH; + DmtxBestLine line; + DmtxPixelLoc rHp; + DmtxFollow follow; + + memset(&line, 0x00, sizeof(DmtxBestLine)); + memset(&rH, 0x00, sizeof(DmtxRay2)); + angleBest = 0; + hOffset = hOffsetBest = 0; + + follow = FollowSeekLoc(dec, loc0); + rHp = line.locBeg = line.locPos = line.locNeg = follow.loc; + line.stepBeg = line.stepPos = line.stepNeg = 0; + + /* Predetermine which angles to test */ + for(i = 0; i < DMTX_HOUGH_RES; i++) { + if(houghAvoid == DmtxUndefined) { + houghTest[i] = 1; + } + else { + houghMin = (houghAvoid + DMTX_HOUGH_RES/6) % DMTX_HOUGH_RES; + houghMax = (houghAvoid - DMTX_HOUGH_RES/6 + DMTX_HOUGH_RES) % DMTX_HOUGH_RES; + if(houghMin > houghMax) + houghTest[i] = (i > houghMin || i < houghMax) ? 1 : 0; + else + houghTest[i] = (i > houghMin && i < houghMax) ? 1 : 0; + } + } + + /* Test each angle for steps along path */ + for(step = 0; step < tripSteps; step++) { + + xDiff = follow.loc.X - rHp.X; + yDiff = follow.loc.Y - rHp.Y; + + /* Increment Hough accumulator */ + for(i = 0; i < DMTX_HOUGH_RES; i++) { + + if((int)houghTest[i] == 0) + continue; + + dH = (rHvX[i] * yDiff) - (rHvY[i] * xDiff); + if(dH >= -384 && dH <= 384) { + if(dH > 128) + hOffset = 2; + else if(dH >= -128) + hOffset = 1; + else + hOffset = 0; + + hough[hOffset][i]++; + + /* New angle takes over lead */ + if(hough[hOffset][i] > hough[hOffsetBest][angleBest]) { + angleBest = i; + hOffsetBest = hOffset; + } + } + } + +/* CALLBACK_POINT_PLOT(follow.loc, (sign > 1) ? 4 : 3, 1, 2); */ + + follow = FollowStep2(dec, follow, sign); + } + + line.angle = angleBest; + line.hOffset = hOffsetBest; + line.mag = hough[hOffsetBest][angleBest]; + + free(houghTest); + free(hough_temp); + + return line; +} + +/** + * + * + */ +static DmtxPassFail +FindTravelLimits(DmtxDecode *dec, DmtxRegion *reg, DmtxBestLine *line) +{ + int i; + int distSq, distSqMax; + int xDiff, yDiff; + int posRunning, negRunning; + int posTravel, negTravel; + int posWander, posWanderMin, posWanderMax, posWanderMinLock, posWanderMaxLock; + int negWander, negWanderMin, negWanderMax, negWanderMinLock, negWanderMaxLock; + int cosAngle, sinAngle; + DmtxFollow followPos, followNeg; + DmtxPixelLoc loc0, posMax, negMax; + + /* line->stepBeg is already known to sit on the best Hough line */ + followPos = followNeg = FollowSeek(dec, reg, line->stepBeg); + loc0 = followPos.loc; + + cosAngle = rHvX[line->angle]; + sinAngle = rHvY[line->angle]; + + distSqMax = 0; + posMax = negMax = followPos.loc; + + posTravel = negTravel = 0; + posWander = posWanderMin = posWanderMax = posWanderMinLock = posWanderMaxLock = 0; + negWander = negWanderMin = negWanderMax = negWanderMinLock = negWanderMaxLock = 0; + + for(i = 0; i < reg->stepsTotal/2; i++) { + + posRunning = (int)(i < 10 || abs(posWander) < abs(posTravel)); + negRunning = (int)(i < 10 || abs(negWander) < abs(negTravel)); + + if(posRunning != 0) { + xDiff = followPos.loc.X - loc0.X; + yDiff = followPos.loc.Y - loc0.Y; + posTravel = (cosAngle * xDiff) + (sinAngle * yDiff); + posWander = (cosAngle * yDiff) - (sinAngle * xDiff); + + if(posWander >= -3*256 && posWander <= 3*256) { + distSq = DistanceSquared(followPos.loc, negMax); + if(distSq > distSqMax) { + posMax = followPos.loc; + distSqMax = distSq; + line->stepPos = followPos.step; + line->locPos = followPos.loc; + posWanderMinLock = posWanderMin; + posWanderMaxLock = posWanderMax; + } + } + else { + posWanderMin = min(posWanderMin, posWander); + posWanderMax = max(posWanderMax, posWander); + } + } + else if(!negRunning) { + break; + } + + if(negRunning != 0) { + xDiff = followNeg.loc.X - loc0.X; + yDiff = followNeg.loc.Y - loc0.Y; + negTravel = (cosAngle * xDiff) + (sinAngle * yDiff); + negWander = (cosAngle * yDiff) - (sinAngle * xDiff); + + if(negWander >= -3*256 && negWander < 3*256) { + distSq = DistanceSquared(followNeg.loc, posMax); + if(distSq > distSqMax) { + negMax = followNeg.loc; + distSqMax = distSq; + line->stepNeg = followNeg.step; + line->locNeg = followNeg.loc; + negWanderMinLock = negWanderMin; + negWanderMaxLock = negWanderMax; + } + } + else { + negWanderMin = min(negWanderMin, negWander); + negWanderMax = max(negWanderMax, negWander); + } + } + else if(!posRunning) { + break; + } + +/* CALLBACK_POINT_PLOT(followPos.loc, 2, 1, 2); + CALLBACK_POINT_PLOT(followNeg.loc, 4, 1, 2); */ + + followPos = FollowStep(dec, reg, followPos, +1); + followNeg = FollowStep(dec, reg, followNeg, -1); + } + line->devn = max(posWanderMaxLock - posWanderMinLock, negWanderMaxLock - negWanderMinLock)/256; + line->distSq = distSqMax; + +/* CALLBACK_POINT_PLOT(posMax, 2, 1, 1); + CALLBACK_POINT_PLOT(negMax, 2, 1, 1); */ + + return DmtxPass; +} + +/** + * + * + */ +static DmtxPassFail +MatrixRegionAlignCalibEdge(DmtxDecode *dec, DmtxRegion *reg, int edgeLoc) +{ + int streamDir; + int steps; + int avoidAngle; + int symbolShape; + DmtxVector2 pTmp; + DmtxPixelLoc loc0, loc1, locOrigin; + DmtxBresLine line; + DmtxFollow follow; + DmtxBestLine bestLine; + + /* Determine pixel coordinates of origin */ + pTmp.X = 0.0; + pTmp.Y = 0.0; + dmtxMatrix3VMultiplyBy(&pTmp, reg->fit2raw); + locOrigin.X = (int)(pTmp.X + 0.5); + locOrigin.Y = (int)(pTmp.Y + 0.5); + + if(dec->sizeIdxExpected == DmtxSymbolSquareAuto || + (dec->sizeIdxExpected >= DmtxSymbol10x10 && + dec->sizeIdxExpected <= DmtxSymbol144x144)) + symbolShape = DmtxSymbolSquareAuto; + else if(dec->sizeIdxExpected == DmtxSymbolRectAuto || + (dec->sizeIdxExpected >= DmtxSymbol8x18 && + dec->sizeIdxExpected <= DmtxSymbol16x48)) + symbolShape = DmtxSymbolRectAuto; + else + symbolShape = DmtxSymbolShapeAuto; + + /* Determine end locations of test line */ + if(edgeLoc == DmtxEdgeTop) { + streamDir = reg->polarity * -1; + avoidAngle = reg->leftLine.angle; + follow = FollowSeekLoc(dec, reg->locT); + pTmp.X = 0.8; + pTmp.Y = (symbolShape == DmtxSymbolRectAuto) ? 0.2 : 0.6; + } + else { + assert(edgeLoc == DmtxEdgeRight); + streamDir = reg->polarity; + avoidAngle = reg->bottomLine.angle; + follow = FollowSeekLoc(dec, reg->locR); + pTmp.X = (symbolShape == DmtxSymbolSquareAuto) ? 0.7 : 0.9; + pTmp.Y = 0.8; + } + + dmtxMatrix3VMultiplyBy(&pTmp, reg->fit2raw); + loc1.X = (int)(pTmp.X + 0.5); + loc1.Y = (int)(pTmp.Y + 0.5); + + loc0 = follow.loc; + line = BresLineInit(loc0, loc1, locOrigin); + steps = TrailBlazeGapped(dec, reg, line, streamDir); + + bestLine = FindBestSolidLine2(dec, loc0, steps, streamDir, avoidAngle); + if(bestLine.mag < 5) { + ; + } + + if(edgeLoc == DmtxEdgeTop) { + reg->topKnown = 1; + reg->topAngle = bestLine.angle; + reg->topLoc = bestLine.locBeg; + } + else { + reg->rightKnown = 1; + reg->rightAngle = bestLine.angle; + reg->rightLoc = bestLine.locBeg; + } + + return DmtxPass; +} + +/** + * + * + */ +static DmtxBresLine +BresLineInit(DmtxPixelLoc loc0, DmtxPixelLoc loc1, DmtxPixelLoc locInside) +{ + int cp; + DmtxBresLine line; + DmtxPixelLoc *locBeg, *locEnd; + + /* XXX Verify that loc0 and loc1 are inbounds */ + + /* Values that stay the same after initialization */ + line.loc0 = loc0; + line.loc1 = loc1; + line.xStep = (loc0.X < loc1.X) ? +1 : -1; + line.yStep = (loc0.Y < loc1.Y) ? +1 : -1; + line.xDelta = abs(loc1.X - loc0.X); + line.yDelta = abs(loc1.Y - loc0.Y); + line.steep = (int)(line.yDelta > line.xDelta); + + /* Take cross product to determine outward step */ + if(line.steep != 0) { + /* Point first vector up to get correct sign */ + if(loc0.Y < loc1.Y) { + locBeg = &loc0; + locEnd = &loc1; + } + else { + locBeg = &loc1; + locEnd = &loc0; + } + cp = (((locEnd->X - locBeg->X) * (locInside.Y - locEnd->Y)) - + ((locEnd->Y - locBeg->Y) * (locInside.X - locEnd->X))); + + line.xOut = (cp > 0) ? +1 : -1; + line.yOut = 0; + } + else { + /* Point first vector left to get correct sign */ + if(loc0.X > loc1.X) { + locBeg = &loc0; + locEnd = &loc1; + } + else { + locBeg = &loc1; + locEnd = &loc0; + } + cp = (((locEnd->X - locBeg->X) * (locInside.Y - locEnd->Y)) - + ((locEnd->Y - locBeg->Y) * (locInside.X - locEnd->X))); + + line.xOut = 0; + line.yOut = (cp > 0) ? +1 : -1; + } + + /* Values that change while stepping through line */ + line.loc = loc0; + line.travel = 0; + line.outward = 0; + line.error = (line.steep) ? line.yDelta/2 : line.xDelta/2; + +/* CALLBACK_POINT_PLOT(loc0, 3, 1, 1); + CALLBACK_POINT_PLOT(loc1, 3, 1, 1); */ + + return line; +} + +/** + * + * + */ +static DmtxPassFail +BresLineGetStep(DmtxBresLine line, DmtxPixelLoc target, int *travel, int *outward) +{ + /* Determine necessary step along and outward from Bresenham line */ + if(line.steep != 0) { + *travel = (line.yStep > 0) ? target.Y - line.loc.Y : line.loc.Y - target.Y; + BresLineStep(&line, *travel, 0); + *outward = (line.xOut > 0) ? target.X - line.loc.X : line.loc.X - target.X; + assert(line.yOut == 0); + } + else { + *travel = (line.xStep > 0) ? target.X - line.loc.X : line.loc.X - target.X; + BresLineStep(&line, *travel, 0); + *outward = (line.yOut > 0) ? target.Y - line.loc.Y : line.loc.Y - target.Y; + assert(line.xOut == 0); + } + + return DmtxPass; +} + +/** + * + * + */ +static DmtxPassFail +BresLineStep(DmtxBresLine *line, int travel, int outward) +{ + int i; + DmtxBresLine lineNew; + + lineNew = *line; + + assert(abs(travel) < 2); + assert(abs(outward) >= 0); + + /* Perform forward step */ + if(travel > 0) { + lineNew.travel++; + if(lineNew.steep != 0) { + lineNew.loc.Y += lineNew.yStep; + lineNew.error -= lineNew.xDelta; + if(lineNew.error < 0) { + lineNew.loc.X += lineNew.xStep; + lineNew.error += lineNew.yDelta; + } + } + else { + lineNew.loc.X += lineNew.xStep; + lineNew.error -= lineNew.yDelta; + if(lineNew.error < 0) { + lineNew.loc.Y += lineNew.yStep; + lineNew.error += lineNew.xDelta; + } + } + } + else if(travel < 0) { + lineNew.travel--; + if(lineNew.steep != 0) { + lineNew.loc.Y -= lineNew.yStep; + lineNew.error += lineNew.xDelta; + if(lineNew.error >= lineNew.yDelta) { + lineNew.loc.X -= lineNew.xStep; + lineNew.error -= lineNew.yDelta; + } + } + else { + lineNew.loc.X -= lineNew.xStep; + lineNew.error += lineNew.yDelta; + if(lineNew.error >= lineNew.xDelta) { + lineNew.loc.Y -= lineNew.yStep; + lineNew.error -= lineNew.xDelta; + } + } + } + + for(i = 0; i < outward; i++) { + /* Outward steps */ + lineNew.outward++; + lineNew.loc.X += lineNew.xOut; + lineNew.loc.Y += lineNew.yOut; + } + + *line = lineNew; + + return DmtxPass; +} + +/** + * + * + */ +#ifdef NOTDEFINED +static void +WriteDiagnosticImage(DmtxDecode *dec, DmtxRegion *reg, char *imagePath) +{ + int row, col; + int width, height; + unsigned char *cache; + int rgb[3]; + FILE *fp; + DmtxVector2 p; + DmtxImage *img; + + assert(reg != NULL); + + fp = fopen(imagePath, "wb"); + if(fp == NULL) { + exit(3); + } + + width = dmtxDecodeGetProp(dec, DmtxPropWidth); + height = dmtxDecodeGetProp(dec->image, DmtxPropHeight); + + img = dmtxImageCreate(NULL, width, height, DmtxPack24bppRGB); + + /* Populate image */ + for(row = 0; row < height; row++) { + for(col = 0; col < width; col++) { + + cache = dmtxDecodeGetCache(dec, col, row); + if(cache == NULL) { + rgb[0] = 0; + rgb[1] = 0; + rgb[2] = 128; + } + else { + dmtxDecodeGetPixelValue(dec, col, row, 0, &rgb[0]); + dmtxDecodeGetPixelValue(dec, col, row, 1, &rgb[1]); + dmtxDecodeGetPixelValue(dec, col, row, 2, &rgb[2]); + + p.X = col; + p.Y = row; + dmtxMatrix3VMultiplyBy(&p, reg->raw2fit); + + if(p.X < 0.0 || p.X > 1.0 || p.Y < 0.0 || p.Y > 1.0) { + rgb[0] = 0; + rgb[1] = 0; + rgb[2] = 128; + } + else if(p.X + p.Y > 1.0) { + rgb[0] += (0.4 * (255 - rgb[0])); + rgb[1] += (0.4 * (255 - rgb[1])); + rgb[2] += (0.4 * (255 - rgb[2])); + } + } + + dmtxImageSetRgb(img, col, row, rgb); + } + } + + /* Write additional markers */ + rgb[0] = 255; + rgb[1] = 0; + rgb[2] = 0; + dmtxImageSetRgb(img, reg->topLoc.X, reg->topLoc.Y, rgb); + dmtxImageSetRgb(img, reg->rightLoc.X, reg->rightLoc.Y, rgb); + + /* Write image to PNM file */ + fprintf(fp, "P6\n%d %d\n255\n", width, height); + for(row = height - 1; row >= 0; row--) { + for(col = 0; col < width; col++) { + dmtxImageGetRgb(img, col, row, rgb); + fwrite(rgb, sizeof(char), 3, fp); + } + } + + dmtxImageDestroy(&img); + + fclose(fp); +} +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtxsymbol.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2008, 2009 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file dmtxsymbol.c + * \brief Data Matrix symbol attributes + */ + +/** + * \brief Retrieve property based on symbol size + * \param attribute + * \param sizeIdx + * \return Attribute value + */ +extern int +dmtxGetSymbolAttribute(int attribute, int sizeIdx) +{ + static const int symbolRows[] = { 10, 12, 14, 16, 18, 20, 22, 24, 26, + 32, 36, 40, 44, 48, 52, + 64, 72, 80, 88, 96, 104, + 120, 132, 144, + 8, 8, 12, 12, 16, 16 }; + + static const int symbolCols[] = { 10, 12, 14, 16, 18, 20, 22, 24, 26, + 32, 36, 40, 44, 48, 52, + 64, 72, 80, 88, 96, 104, + 120, 132, 144, + 18, 32, 26, 36, 36, 48 }; + + static const int dataRegionRows[] = { 8, 10, 12, 14, 16, 18, 20, 22, 24, + 14, 16, 18, 20, 22, 24, + 14, 16, 18, 20, 22, 24, + 18, 20, 22, + 6, 6, 10, 10, 14, 14 }; + + static const int dataRegionCols[] = { 8, 10, 12, 14, 16, 18, 20, 22, 24, + 14, 16, 18, 20, 22, 24, + 14, 16, 18, 20, 22, 24, + 18, 20, 22, + 16, 14, 24, 16, 16, 22 }; + + static const int horizDataRegions[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 2, 2, 2, 2, 2, + 4, 4, 4, 4, 4, 4, + 6, 6, 6, + 1, 2, 1, 2, 2, 2 }; + + static const int interleavedBlocks[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 2, + 2, 4, 4, 4, 4, 6, + 6, 8, 10, + 1, 1, 1, 1, 1, 1 }; + + static const int symbolDataWords[] = { 3, 5, 8, 12, 18, 22, 30, 36, 44, + 62, 86, 114, 144, 174, 204, + 280, 368, 456, 576, 696, 816, + 1050, 1304, 1558, + 5, 10, 16, 22, 32, 49 }; + + static const int blockErrorWords[] = { 5, 7, 10, 12, 14, 18, 20, 24, 28, + 36, 42, 48, 56, 68, 42, + 56, 36, 48, 56, 68, 56, + 68, 62, 62, + 7, 11, 14, 18, 24, 28 }; + + static const int blockMaxCorrectable[] = { 2, 3, 5, 6, 7, 9, 10, 12, 14, + 18, 21, 24, 28, 34, 21, + 28, 18, 24, 28, 34, 28, + 34, 31, 31, + 3, 5, 7, 9, 12, 14 }; + + if(sizeIdx < 0 || sizeIdx >= DmtxSymbolSquareCount + DmtxSymbolRectCount) + return DmtxUndefined; + + switch(attribute) { + case DmtxSymAttribSymbolRows: + return symbolRows[sizeIdx]; + case DmtxSymAttribSymbolCols: + return symbolCols[sizeIdx]; + case DmtxSymAttribDataRegionRows: + return dataRegionRows[sizeIdx]; + case DmtxSymAttribDataRegionCols: + return dataRegionCols[sizeIdx]; + case DmtxSymAttribHorizDataRegions: + return horizDataRegions[sizeIdx]; + case DmtxSymAttribVertDataRegions: + return (sizeIdx < DmtxSymbolSquareCount) ? horizDataRegions[sizeIdx] : 1; + case DmtxSymAttribMappingMatrixRows: + return dataRegionRows[sizeIdx] * + dmtxGetSymbolAttribute(DmtxSymAttribVertDataRegions, sizeIdx); + case DmtxSymAttribMappingMatrixCols: + return dataRegionCols[sizeIdx] * horizDataRegions[sizeIdx]; + case DmtxSymAttribInterleavedBlocks: + return interleavedBlocks[sizeIdx]; + case DmtxSymAttribBlockErrorWords: + return blockErrorWords[sizeIdx]; + case DmtxSymAttribBlockMaxCorrectable: + return blockMaxCorrectable[sizeIdx]; + case DmtxSymAttribSymbolDataWords: + return symbolDataWords[sizeIdx]; + case DmtxSymAttribSymbolErrorWords: + return blockErrorWords[sizeIdx] * interleavedBlocks[sizeIdx]; + case DmtxSymAttribSymbolMaxCorrectable: + return blockMaxCorrectable[sizeIdx] * interleavedBlocks[sizeIdx]; + } + + return DmtxUndefined; +} + +/** + * \brief Retrieve data size for a specific symbol size and block number + * \param sizeIdx + * \param blockIdx + * \return Attribute value + */ +extern int +dmtxGetBlockDataSize(int sizeIdx, int blockIdx) +{ + int symbolDataWords; + int interleavedBlocks; + int count; + + symbolDataWords = dmtxGetSymbolAttribute(DmtxSymAttribSymbolDataWords, sizeIdx); + interleavedBlocks = dmtxGetSymbolAttribute(DmtxSymAttribInterleavedBlocks, sizeIdx); + + if(symbolDataWords < 1 || interleavedBlocks < 1) + return DmtxUndefined; + + count = (int)(symbolDataWords/interleavedBlocks); + + return (sizeIdx == DmtxSymbol144x144 && blockIdx < 8) ? count + 1 : count; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtxplacemod.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2008, 2009 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file dmtxplacemod.c + * \brief Data Matrix module placement + */ + +/** + * receives symbol row and col and returns status + * DmtxModuleOn / !DmtxModuleOn (DmtxModuleOff) + * DmtxModuleAssigned + * DmtxModuleVisited + * DmtxModuleData / !DmtxModuleData (DmtxModuleAlignment) + * row and col are expressed in symbol coordinates, so (0,0) is the intersection of the "L" + */ +int +dmtxSymbolModuleStatus(DmtxMessage *message, int sizeIdx, int symbolRow, int symbolCol) +{ + int symbolRowReverse; + int mappingRow, mappingCol; + int dataRegionRows, dataRegionCols; + int symbolRows, mappingCols; + + dataRegionRows = dmtxGetSymbolAttribute(DmtxSymAttribDataRegionRows, sizeIdx); + dataRegionCols = dmtxGetSymbolAttribute(DmtxSymAttribDataRegionCols, sizeIdx); + symbolRows = dmtxGetSymbolAttribute(DmtxSymAttribSymbolRows, sizeIdx); + mappingCols = dmtxGetSymbolAttribute(DmtxSymAttribMappingMatrixCols, sizeIdx); + + symbolRowReverse = symbolRows - symbolRow - 1; + mappingRow = symbolRowReverse - 1 - 2 * (symbolRowReverse / (dataRegionRows+2)); + mappingCol = symbolCol - 1 - 2 * (symbolCol / (dataRegionCols+2)); + + /* Solid portion of alignment patterns */ + if(symbolRow % (dataRegionRows+2) == 0 || + symbolCol % (dataRegionCols+2) == 0) + return (DmtxModuleOnRGB | (!DmtxModuleData)); + + /* Horinzontal calibration bars */ + if((symbolRow+1) % (dataRegionRows+2) == 0) + return (((symbolCol & 0x01) ? 0 : DmtxModuleOnRGB) | (!DmtxModuleData)); + + /* Vertical calibration bars */ + if((symbolCol+1) % (dataRegionCols+2) == 0) + return (((symbolRow & 0x01) ? 0 : DmtxModuleOnRGB) | (!DmtxModuleData)); + + /* Data modules */ + return (message->array[mappingRow * mappingCols + mappingCol] | DmtxModuleData); +} + +/** + * \brief Logical relationship between bit and module locations + * \param modules + * \param codewords + * \param sizeIdx + * \param moduleOnColor + * \return Number of codewords read + */ +static int +ModulePlacementEcc200(unsigned char *modules, unsigned char *codewords, int sizeIdx, int moduleOnColor) +{ + int row, col, chr; + int mappingRows, mappingCols; + + assert(moduleOnColor & (DmtxModuleOnRed | DmtxModuleOnGreen | DmtxModuleOnBlue)); + + mappingRows = dmtxGetSymbolAttribute(DmtxSymAttribMappingMatrixRows, sizeIdx); + mappingCols = dmtxGetSymbolAttribute(DmtxSymAttribMappingMatrixCols, sizeIdx); + + /* Start in the nominal location for the 8th bit of the first character */ + chr = 0; + row = 4; + col = 0; + + do { + /* Repeatedly first check for one of the special corner cases */ + if((row == mappingRows) && (col == 0)) + PatternShapeSpecial1(modules, mappingRows, mappingCols, &(codewords[chr++]), moduleOnColor); + else if((row == mappingRows-2) && (col == 0) && (mappingCols%4 != 0)) + PatternShapeSpecial2(modules, mappingRows, mappingCols, &(codewords[chr++]), moduleOnColor); + else if((row == mappingRows-2) && (col == 0) && (mappingCols%8 == 4)) + PatternShapeSpecial3(modules, mappingRows, mappingCols, &(codewords[chr++]), moduleOnColor); + else if((row == mappingRows+4) && (col == 2) && (mappingCols%8 == 0)) + PatternShapeSpecial4(modules, mappingRows, mappingCols, &(codewords[chr++]), moduleOnColor); + + /* Sweep upward diagonally, inserting successive characters */ + do { + if((row < mappingRows) && (col >= 0) && + !(modules[row*mappingCols+col] & DmtxModuleVisited)) + PatternShapeStandard(modules, mappingRows, mappingCols, row, col, &(codewords[chr++]), moduleOnColor); + row -= 2; + col += 2; + } while ((row >= 0) && (col < mappingCols)); + row += 1; + col += 3; + + /* Sweep downward diagonally, inserting successive characters */ + do { + if((row >= 0) && (col < mappingCols) && + !(modules[row*mappingCols+col] & DmtxModuleVisited)) + PatternShapeStandard(modules, mappingRows, mappingCols, row, col, &(codewords[chr++]), moduleOnColor); + row += 2; + col -= 2; + } while ((row < mappingRows) && (col >= 0)); + row += 3; + col += 1; + /* ... until the entire modules array is scanned */ + } while ((row < mappingRows) || (col < mappingCols)); + + /* If lower righthand corner is untouched then fill in the fixed pattern */ + if(!(modules[mappingRows * mappingCols - 1] & + DmtxModuleVisited)) { + + modules[mappingRows * mappingCols - 1] |= moduleOnColor; + modules[(mappingRows * mappingCols) - mappingCols - 2] |= moduleOnColor; + } /* XXX should this fixed pattern also be used in reading somehow? */ + + /* XXX compare that chr == region->dataSize here */ + return chr; /* XXX number of codewords read off */ +} + +/** + * \brief XXX + * \param modules + * \param mappingRows + * \param mappingCols + * \param row + * \param col + * \param codeword + * \param moduleOnColor + * \return void + */ +static void +PatternShapeStandard(unsigned char *modules, int mappingRows, int mappingCols, int row, int col, unsigned char *codeword, int moduleOnColor) +{ + PlaceModule(modules, mappingRows, mappingCols, row-2, col-2, codeword, DmtxMaskBit1, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, row-2, col-1, codeword, DmtxMaskBit2, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, row-1, col-2, codeword, DmtxMaskBit3, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, row-1, col-1, codeword, DmtxMaskBit4, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, row-1, col, codeword, DmtxMaskBit5, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, row, col-2, codeword, DmtxMaskBit6, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, row, col-1, codeword, DmtxMaskBit7, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, row, col, codeword, DmtxMaskBit8, moduleOnColor); +} + +/** + * \brief XXX + * \param modules + * \param mappingRows + * \param mappingCols + * \param codeword + * \param moduleOnColor + * \return void + */ +static void +PatternShapeSpecial1(unsigned char *modules, int mappingRows, int mappingCols, unsigned char *codeword, int moduleOnColor) +{ + PlaceModule(modules, mappingRows, mappingCols, mappingRows-1, 0, codeword, DmtxMaskBit1, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, mappingRows-1, 1, codeword, DmtxMaskBit2, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, mappingRows-1, 2, codeword, DmtxMaskBit3, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 0, mappingCols-2, codeword, DmtxMaskBit4, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 0, mappingCols-1, codeword, DmtxMaskBit5, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 1, mappingCols-1, codeword, DmtxMaskBit6, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 2, mappingCols-1, codeword, DmtxMaskBit7, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 3, mappingCols-1, codeword, DmtxMaskBit8, moduleOnColor); +} + +/** + * \brief XXX + * \param modules + * \param mappingRows + * \param mappingCols + * \param codeword + * \param moduleOnColor + * \return void + */ +static void +PatternShapeSpecial2(unsigned char *modules, int mappingRows, int mappingCols, unsigned char *codeword, int moduleOnColor) +{ + PlaceModule(modules, mappingRows, mappingCols, mappingRows-3, 0, codeword, DmtxMaskBit1, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, mappingRows-2, 0, codeword, DmtxMaskBit2, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, mappingRows-1, 0, codeword, DmtxMaskBit3, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 0, mappingCols-4, codeword, DmtxMaskBit4, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 0, mappingCols-3, codeword, DmtxMaskBit5, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 0, mappingCols-2, codeword, DmtxMaskBit6, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 0, mappingCols-1, codeword, DmtxMaskBit7, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 1, mappingCols-1, codeword, DmtxMaskBit8, moduleOnColor); +} + +/** + * \brief XXX + * \param modules + * \param mappingRows + * \param mappingCols + * \param codeword + * \param moduleOnColor + * \return void + */ +static void +PatternShapeSpecial3(unsigned char *modules, int mappingRows, int mappingCols, unsigned char *codeword, int moduleOnColor) +{ + PlaceModule(modules, mappingRows, mappingCols, mappingRows-3, 0, codeword, DmtxMaskBit1, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, mappingRows-2, 0, codeword, DmtxMaskBit2, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, mappingRows-1, 0, codeword, DmtxMaskBit3, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 0, mappingCols-2, codeword, DmtxMaskBit4, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 0, mappingCols-1, codeword, DmtxMaskBit5, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 1, mappingCols-1, codeword, DmtxMaskBit6, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 2, mappingCols-1, codeword, DmtxMaskBit7, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 3, mappingCols-1, codeword, DmtxMaskBit8, moduleOnColor); +} + +/** + * \brief XXX + * \param modules + * \param mappingRows + * \param mappingCols + * \param codeword + * \param moduleOnColor + * \return void + */ +static void +PatternShapeSpecial4(unsigned char *modules, int mappingRows, int mappingCols, unsigned char *codeword, int moduleOnColor) +{ + PlaceModule(modules, mappingRows, mappingCols, mappingRows-1, 0, codeword, DmtxMaskBit1, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, mappingRows-1, mappingCols-1, codeword, DmtxMaskBit2, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 0, mappingCols-3, codeword, DmtxMaskBit3, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 0, mappingCols-2, codeword, DmtxMaskBit4, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 0, mappingCols-1, codeword, DmtxMaskBit5, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 1, mappingCols-3, codeword, DmtxMaskBit6, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 1, mappingCols-2, codeword, DmtxMaskBit7, moduleOnColor); + PlaceModule(modules, mappingRows, mappingCols, 1, mappingCols-1, codeword, DmtxMaskBit8, moduleOnColor); +} + +/** + * \brief XXX + * \param modules + * \param mappingRows + * \param mappingCols + * \param row + * \param col + * \param codeword + * \param mask + * \param moduleOnColor + * \return void + */ +static void +PlaceModule(unsigned char *modules, int mappingRows, int mappingCols, int row, int col, unsigned char *codeword, int mask, int moduleOnColor) +{ + if(row < 0) { + row += mappingRows; + col += 4 - ((mappingRows+4)%8); + } + if(col < 0) { + col += mappingCols; + row += 4 - ((mappingCols+4)%8); + } + + /* If module has already been assigned then we are decoding the pattern into codewords */ + if((modules[row*mappingCols+col] & DmtxModuleAssigned) != 0) { + if((modules[row*mappingCols+col] & moduleOnColor) != 0) + *codeword |= mask; + else + *codeword &= (0xff ^ mask); + } + /* Otherwise we are encoding the codewords into a pattern */ + else { + if((*codeword & mask) != 0x00) + modules[row*mappingCols+col] |= moduleOnColor; + + modules[row*mappingCols+col] |= DmtxModuleAssigned; + } + + modules[row*mappingCols+col] |= DmtxModuleVisited; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtxreedsol.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2011 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * --------------------------------------------------------- + * Portions of this file were derived from the Reed-Solomon + * encoder/decoder released by Simon Rockliff in June 1991. + * --------------------------------------------------------- + * + * Contact: Mike Laughton + * + * \file dmtxreedsol.c + */ + +/** + * TODO: + * o try doxygen using using the JavaDoc style and JAVADOC_AUTOBRIEF = YES + * o switch doxygen to simplified syntax, and using "\file" instead of "@file" + */ + +#define NN 255 +#define MAX_ERROR_WORD_COUNT 68 + +/* GF add (a + b) */ +#define GfAdd(a,b) \ + ((a) ^ (b)) + +/* GF multiply (a * b) */ +#define GfMult(a,b) \ + (((a) == 0 || (b) == 0) ? 0 : antilog301[(log301[(a)] + log301[(b)]) % NN]) + +/* GF multiply by antilog (a * alpha**b) */ +#define GfMultAntilog(a,b) \ + (((a) == 0) ? 0 : antilog301[(log301[(a)] + (b)) % NN]) + +/* GF(256) log values using primitive polynomial 301 */ +static DmtxByte log301[] = + { 255, 0, 1, 240, 2, 225, 241, 53, 3, 38, 226, 133, 242, 43, 54, 210, + 4, 195, 39, 114, 227, 106, 134, 28, 243, 140, 44, 23, 55, 118, 211, 234, + 5, 219, 196, 96, 40, 222, 115, 103, 228, 78, 107, 125, 135, 8, 29, 162, + 244, 186, 141, 180, 45, 99, 24, 49, 56, 13, 119, 153, 212, 199, 235, 91, + 6, 76, 220, 217, 197, 11, 97, 184, 41, 36, 223, 253, 116, 138, 104, 193, + 229, 86, 79, 171, 108, 165, 126, 145, 136, 34, 9, 74, 30, 32, 163, 84, + 245, 173, 187, 204, 142, 81, 181, 190, 46, 88, 100, 159, 25, 231, 50, 207, + 57, 147, 14, 67, 120, 128, 154, 248, 213, 167, 200, 63, 236, 110, 92, 176, + 7, 161, 77, 124, 221, 102, 218, 95, 198, 90, 12, 152, 98, 48, 185, 179, + 42, 209, 37, 132, 224, 52, 254, 239, 117, 233, 139, 22, 105, 27, 194, 113, + 230, 206, 87, 158, 80, 189, 172, 203, 109, 175, 166, 62, 127, 247, 146, 66, + 137, 192, 35, 252, 10, 183, 75, 216, 31, 83, 33, 73, 164, 144, 85, 170, + 246, 65, 174, 61, 188, 202, 205, 157, 143, 169, 82, 72, 182, 215, 191, 251, + 47, 178, 89, 151, 101, 94, 160, 123, 26, 112, 232, 21, 51, 238, 208, 131, + 58, 69, 148, 18, 15, 16, 68, 17, 121, 149, 129, 19, 155, 59, 249, 70, + 214, 250, 168, 71, 201, 156, 64, 60, 237, 130, 111, 20, 93, 122, 177, 150 }; + +/* GF(256) antilog values using primitive polynomial 301 */ +static DmtxByte antilog301[] = + { 1, 2, 4, 8, 16, 32, 64, 128, 45, 90, 180, 69, 138, 57, 114, 228, + 229, 231, 227, 235, 251, 219, 155, 27, 54, 108, 216, 157, 23, 46, 92, 184, + 93, 186, 89, 178, 73, 146, 9, 18, 36, 72, 144, 13, 26, 52, 104, 208, + 141, 55, 110, 220, 149, 7, 14, 28, 56, 112, 224, 237, 247, 195, 171, 123, + 246, 193, 175, 115, 230, 225, 239, 243, 203, 187, 91, 182, 65, 130, 41, 82, + 164, 101, 202, 185, 95, 190, 81, 162, 105, 210, 137, 63, 126, 252, 213, 135, + 35, 70, 140, 53, 106, 212, 133, 39, 78, 156, 21, 42, 84, 168, 125, 250, + 217, 159, 19, 38, 76, 152, 29, 58, 116, 232, 253, 215, 131, 43, 86, 172, + 117, 234, 249, 223, 147, 11, 22, 44, 88, 176, 77, 154, 25, 50, 100, 200, + 189, 87, 174, 113, 226, 233, 255, 211, 139, 59, 118, 236, 245, 199, 163, 107, + 214, 129, 47, 94, 188, 85, 170, 121, 242, 201, 191, 83, 166, 97, 194, 169, + 127, 254, 209, 143, 51, 102, 204, 181, 71, 142, 49, 98, 196, 165, 103, 206, + 177, 79, 158, 17, 34, 68, 136, 61, 122, 244, 197, 167, 99, 198, 161, 111, + 222, 145, 15, 30, 60, 120, 240, 205, 183, 67, 134, 33, 66, 132, 37, 74, + 148, 5, 10, 20, 40, 80, 160, 109, 218, 153, 31, 62, 124, 248, 221, 151, + 3, 6, 12, 24, 48, 96, 192, 173, 119, 238, 241, 207, 179, 75, 150, 0 }; + +/** + * Decode xyz. + * More detailed description. + * \param code + * \param sizeIdx + * \param fix + * \return Function success (DmtxPass|DmtxFail) + */ +#undef CHKPASS +#define CHKPASS { if(passFail == DmtxFail) return DmtxFail; } +static DmtxPassFail +RsDecode(unsigned char *code, int sizeIdx, int fix) +{ + int i; + int blockStride, blockIdx; + int blockDataWords, blockErrorWords, blockTotalWords, blockMaxCorrectable; + int symbolDataWords, symbolErrorWords, symbolTotalWords; + DmtxBoolean error, repairable; + DmtxPassFail passFail; + unsigned char *word; + DmtxByte elpStorage[MAX_ERROR_WORD_COUNT]; + DmtxByte synStorage[MAX_ERROR_WORD_COUNT+1]; + DmtxByte recStorage[NN]; + DmtxByte locStorage[NN]; + DmtxByteList elp = dmtxByteListBuild(elpStorage, sizeof(elpStorage)); + DmtxByteList syn = dmtxByteListBuild(synStorage, sizeof(synStorage)); + DmtxByteList rec = dmtxByteListBuild(recStorage, sizeof(recStorage)); + DmtxByteList loc = dmtxByteListBuild(locStorage, sizeof(locStorage)); + + blockStride = dmtxGetSymbolAttribute(DmtxSymAttribInterleavedBlocks, sizeIdx); + blockErrorWords = dmtxGetSymbolAttribute(DmtxSymAttribBlockErrorWords, sizeIdx); + blockMaxCorrectable = dmtxGetSymbolAttribute(DmtxSymAttribBlockMaxCorrectable, sizeIdx); + symbolDataWords = dmtxGetSymbolAttribute(DmtxSymAttribSymbolDataWords, sizeIdx); + symbolErrorWords = dmtxGetSymbolAttribute(DmtxSymAttribSymbolErrorWords, sizeIdx); + symbolTotalWords = symbolDataWords + symbolErrorWords; + + /* For each interleaved block */ + for(blockIdx = 0; blockIdx < blockStride; blockIdx++) + { + /* Data word count depends on blockIdx due to special case at 144x144 */ + blockDataWords = dmtxGetBlockDataSize(sizeIdx, blockIdx); + blockTotalWords = blockErrorWords + blockDataWords; + + /* Populate received list (rec) with data and error codewords */ + dmtxByteListInit(&rec, 0, 0, &passFail); CHKPASS; + + /* Start with final error word and work backward */ + word = code + symbolTotalWords + blockIdx - blockStride; + for(i = 0; i < blockErrorWords; i++) + { + dmtxByteListPush(&rec, *word, &passFail); CHKPASS; + word -= blockStride; + } + + /* Start with final data word and work backward */ + word = code + blockIdx + (blockStride * (blockDataWords - 1)); + for(i = 0; i < blockDataWords; i++) + { + dmtxByteListPush(&rec, *word, &passFail); CHKPASS; + word -= blockStride; + } + + /* Compute syndromes (syn) */ + error = RsComputeSyndromes(&syn, &rec, blockErrorWords); + + /* Error(s) detected: Attempt repair */ + if(error) + { + /* Find error locator polynomial (elp) */ + repairable = RsFindErrorLocatorPoly(&elp, &syn, blockErrorWords, blockMaxCorrectable); + if(!repairable) + return DmtxFail; + + /* Find error positions (loc) */ + repairable = RsFindErrorLocations(&loc, &elp); + if(!repairable) + return DmtxFail; + + /* Find error values and repair */ + RsRepairErrors(&rec, &loc, &elp, &syn); + } + + /* + * Overwrite output with correct/corrected values + */ + + /* Start with first data word and work forward */ + word = code + blockIdx; + for(i = 0; i < blockDataWords; i++) + { + *word = dmtxByteListPop(&rec, &passFail); CHKPASS; + word += blockStride; + } + + /* Start with first error word and work forward */ + word = code + symbolDataWords + blockIdx; + for(i = 0; i < blockErrorWords; i++) + { + *word = dmtxByteListPop(&rec, &passFail); CHKPASS; + word += blockStride; + } + } + + return DmtxPass; +} + +/** + * Populate generator polynomial. + * Assume we have received bits grouped into mm-bit symbols in rec[i], + * i=0..(nn-1), and rec[i] is index form (ie as powers of alpha). We first + * compute the 2*tt syndromes by substituting alpha**i into rec(X) and + * evaluating, storing the syndromes in syn[i], i=1..2tt (leave syn[0] zero). + * \param syn + * \param rec + * \param blockErrorWords + * \return Are error(s) present? (DmtxPass|DmtxFail) + */ +/* XXX this CHKPASS isn't doing what we want ... really need a error reporting strategy */ +#undef CHKPASS +#define CHKPASS { if(passFail == DmtxFail) return DmtxTrue; } +static DmtxBoolean +RsComputeSyndromes(DmtxByteList *syn, const DmtxByteList *rec, int blockErrorWords) +{ + int i, j; + DmtxPassFail passFail; + DmtxBoolean error = DmtxFalse; + + /* Initialize all coefficients to 0 */ + dmtxByteListInit(syn, blockErrorWords + 1, 0, &passFail); CHKPASS; + + for(i = 1; i < syn->length; i++) + { + /* Calculate syndrome at i */ + for(j = 0; j < rec->length; j++) /* alternatively: j < blockTotalWords */ + syn->b[i] = GfAdd(syn->b[i], GfMultAntilog(rec->b[j], i*j)); + + /* Non-zero syndrome indicates presence of error(s) */ + if(syn->b[i] != 0) + error = DmtxTrue; + } + + return error; +} + +/** + * Find the error location polynomial using Berlekamp-Massey. + * More detailed description. + * \param elpOut + * \param syn + * \param errorWordCount + * \param maxCorrectable + * \return Is block repairable? (DmtxTrue|DmtxFalse) + */ +/* XXX this CHKPASS isn't doing what we want ... really need a error reporting strategy */ +#undef CHKPASS +#define CHKPASS { if(passFail == DmtxFail) { free(elpStorage_temp); return DmtxFalse; } } +static DmtxBoolean +RsFindErrorLocatorPoly(DmtxByteList *elpOut, const DmtxByteList *syn, int errorWordCount, int maxCorrectable) +{ + int i, iNext, j; + int m, mCmp, lambda; + DmtxByte disTmp, disStorage[MAX_ERROR_WORD_COUNT+1]; + DmtxByte *elpStorage_temp = malloc(sizeof(DmtxByte) * (MAX_ERROR_WORD_COUNT+2) * MAX_ERROR_WORD_COUNT); DmtxByte (*elpStorage)[MAX_ERROR_WORD_COUNT] = (DmtxByte (*)[MAX_ERROR_WORD_COUNT]) elpStorage_temp; // [MAX_ERROR_WORD_COUNT+2][MAX_ERROR_WORD_COUNT]; + DmtxByteList dis, elp[MAX_ERROR_WORD_COUNT+2]; + DmtxPassFail passFail; + + dis = dmtxByteListBuild(disStorage, sizeof(disStorage)); + dmtxByteListInit(&dis, 0, 0, &passFail); CHKPASS; + + for(i = 0; i < MAX_ERROR_WORD_COUNT + 2; i++) + { + elp[i] = dmtxByteListBuild(elpStorage[i], sizeof(elpStorage[i])); + dmtxByteListInit(&elp[i], 0, 0, &passFail); CHKPASS; + } + + /* iNext = 0 */ + dmtxByteListPush(&elp[0], 1, &passFail); CHKPASS; + dmtxByteListPush(&dis, 1, &passFail); CHKPASS; + + /* iNext = 1 */ + dmtxByteListPush(&elp[1], 1, &passFail); CHKPASS; + dmtxByteListPush(&dis, syn->b[1], &passFail); CHKPASS; + + for(iNext = 2, i = 1; /* explicit break */; i = iNext++) + { + if(dis.b[i] == 0) + { + /* Simple case: Copy directly from previous iteration */ + dmtxByteListCopy(&elp[iNext], &elp[i], &passFail); CHKPASS; + } + else + { + /* Find earlier iteration (m) that provides maximal (m - lambda) */ + for(m = 0, mCmp = 1; mCmp < i; mCmp++) + if(dis.b[mCmp] != 0 && (mCmp - elp[mCmp].length) >= (m - elp[m].length)) + m = mCmp; + + /* Calculate error location polynomial elp[i] (set 1st term) */ + for(lambda = elp[m].length - 1, j = 0; j <= lambda; j++) + elp[iNext].b[j+i-m] = antilog301[(NN - log301[dis.b[m]] + + log301[dis.b[i]] + log301[elp[m].b[j]]) % NN]; + + /* Calculate error location polynomial elp[i] (add 2nd term) */ + for(lambda = elp[i].length - 1, j = 0; j <= lambda; j++) + elp[iNext].b[j] = GfAdd(elp[iNext].b[j], elp[i].b[j]); + + elp[iNext].length = max(elp[i].length, elp[m].length + i - m); + } + + lambda = elp[iNext].length - 1; + if(i == errorWordCount || i >= lambda + maxCorrectable) + break; + + /* Calculate discrepancy dis.b[i] */ + for(disTmp = syn->b[iNext], j = 1; j <= lambda; j++) + disTmp = GfAdd(disTmp, GfMult(syn->b[iNext-j], elp[iNext].b[j])); + + assert(dis.length == iNext); + dmtxByteListPush(&dis, disTmp, &passFail); CHKPASS; + } + + dmtxByteListCopy(elpOut, &elp[iNext], &passFail); CHKPASS; + + free(elpStorage_temp); + + return (lambda <= maxCorrectable) ? DmtxTrue : DmtxFalse; +} + +/** + * Find roots of the error locator polynomial (Chien Search). + * If the degree of elp is <= tt, we substitute alpha**i, i=1..n into the elp + * to get the roots, hence the inverse roots, the error location numbers. + * If the number of errors located does not equal the degree of the elp, we + * have more than tt errors and cannot correct them. + * \param loc + * \param elp + * \return Is block repairable? (DmtxTrue|DmtxFalse) + */ +#undef CHKPASS +#define CHKPASS { if(passFail == DmtxFail) return DmtxFalse; } +static DmtxBoolean +RsFindErrorLocations(DmtxByteList *loc, const DmtxByteList *elp) +{ + int i, j; + int lambda = elp->length - 1; + DmtxPassFail passFail; + DmtxByte q, regStorage[MAX_ERROR_WORD_COUNT]; + DmtxByteList reg = dmtxByteListBuild(regStorage, sizeof(regStorage)); + + dmtxByteListCopy(®, elp, &passFail); CHKPASS; + dmtxByteListInit(loc, 0, 0, &passFail); CHKPASS; + + for(i = 1; i <= NN; i++) + { + for(q = 1, j = 1; j <= lambda; j++) + { + reg.b[j] = GfMultAntilog(reg.b[j], j); + q = GfAdd(q, reg.b[j]); + } + + if(q == 0) + { + dmtxByteListPush(loc, NN - i, &passFail); CHKPASS; + } + } + + return (loc->length == lambda) ? DmtxTrue : DmtxFalse; +} + +/** + * Find the error values and repair. + * Solve for the error value at the error location and correct the error. The + * procedure is that found in Lin and Costello. + * For the cases where the number of errors is known to be too large to + * correct, the information symbols as received are output (the advantage of + * systematic encoding is that hopefully some of the information symbols will + * be okay and that if we are in luck, the errors are in the parity part of + * the transmitted codeword). + * \param rec + * \param loc + * \param elp + * \param syn + */ +#undef CHKPASS +#define CHKPASS { if(passFail == DmtxFail) return DmtxFail; } +static DmtxPassFail +RsRepairErrors(DmtxByteList *rec, const DmtxByteList *loc, const DmtxByteList *elp, const DmtxByteList *syn) +{ + int i, j, q; + int lambda = elp->length - 1; + DmtxPassFail passFail; + DmtxByte zVal, root, err; + DmtxByte zStorage[MAX_ERROR_WORD_COUNT+1]; + DmtxByteList z = dmtxByteListBuild(zStorage, sizeof(zStorage)); + + /* Form polynomial z(x) */ + dmtxByteListPush(&z, 1, &passFail); CHKPASS; + for(i = 1; i <= lambda; i++) + { + for(zVal = GfAdd(syn->b[i], elp->b[i]), j = 1; j < i; j++) + zVal= GfAdd(zVal, GfMult(elp->b[i-j], syn->b[j])); + dmtxByteListPush(&z, zVal, &passFail); CHKPASS; + } + + for(i = 0; i < lambda; i++) + { + /* Calculate numerator of error term */ + root = NN - loc->b[i]; + + for(err = 1, j = 1; j <= lambda; j++) + err = GfAdd(err, GfMultAntilog(z.b[j], j * root)); + + if(err == 0) + continue; + + /* Calculate denominator of error term */ + for(q = 0, j = 0; j < lambda; j++) + { + if(j != i) + q += log301[1 ^ antilog301[(loc->b[j] + root) % NN]]; + } + q %= NN; + + err = GfMultAntilog(err, NN - q); + rec->b[loc->b[i]] = GfAdd(rec->b[loc->b[i]], err); + } + + return DmtxPass; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtxscangrid.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2008, 2009 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file dmtxscangrid.c + * \brief Scan grid tracking + */ + +/** + * \brief Initialize scan grid pattern + * \param dec + * \return Initialized grid + */ +static DmtxScanGrid +InitScanGrid(DmtxDecode *dec) +{ + int scale, smallestFeature; + int xExtent, yExtent, maxExtent; + int extent; + DmtxScanGrid grid; + + memset(&grid, 0x00, sizeof(DmtxScanGrid)); + + scale = dmtxDecodeGetProp(dec, DmtxPropScale); + smallestFeature = dmtxDecodeGetProp(dec, DmtxPropScanGap) / scale; + + grid.xMin = dmtxDecodeGetProp(dec, DmtxPropXmin); + grid.xMax = dmtxDecodeGetProp(dec, DmtxPropXmax); + grid.yMin = dmtxDecodeGetProp(dec, DmtxPropYmin); + grid.yMax = dmtxDecodeGetProp(dec, DmtxPropYmax); + + /* Values that get set once */ + xExtent = grid.xMax - grid.xMin; + yExtent = grid.yMax - grid.yMin; + maxExtent = (xExtent > yExtent) ? xExtent : yExtent; + + assert(maxExtent > 1); + + for(extent = 1; extent < maxExtent; extent = ((extent + 1) * 2) - 1) + if(extent <= smallestFeature) + grid.minExtent = extent; + + grid.maxExtent = extent; + + grid.xOffset = (grid.xMin + grid.xMax - grid.maxExtent) / 2; + grid.yOffset = (grid.yMin + grid.yMax - grid.maxExtent) / 2; + + /* Values that get reset for every level */ + grid.total = 1; + grid.extent = grid.maxExtent; + + SetDerivedFields(&grid); + + return grid; +} + +/** + * \brief Return the next good location (which may be the current location), + * and advance grid progress one position beyond that. If no good + * locations remain then return DmtxRangeEnd. + * \param grid + * \return void + */ +static int +PopGridLocation(DmtxScanGrid *grid, DmtxPixelLoc *locPtr) +{ + int locStatus; + + do { + locStatus = GetGridCoordinates(grid, locPtr); + + /* Always leave grid pointing at next available location */ + grid->pixelCount++; + + } while(locStatus == DmtxRangeBad); + + return locStatus; +} + +/** + * \brief Extract current grid position in pixel coordinates and return + * whether location is good, bad, or end + * \param grid + * \return Pixel location + */ +static int +GetGridCoordinates(DmtxScanGrid *grid, DmtxPixelLoc *locPtr) +{ + int count, half, quarter; + DmtxPixelLoc loc; + + /* Initially pixelCount may fall beyond acceptable limits. Update grid + * state before testing coordinates */ + + /* Jump to next cross pattern horizontally if current column is done */ + if(grid->pixelCount >= grid->pixelTotal) { + grid->pixelCount = 0; + grid->xCenter += grid->jumpSize; + } + + /* Jump to next cross pattern vertically if current row is done */ + if(grid->xCenter > grid->maxExtent) { + grid->xCenter = grid->startPos; + grid->yCenter += grid->jumpSize; + } + + /* Increment level when vertical step goes too far */ + if(grid->yCenter > grid->maxExtent) { + grid->total *= 4; + grid->extent /= 2; + SetDerivedFields(grid); + } + + if(grid->extent == 0 || grid->extent < grid->minExtent) { + locPtr->X = locPtr->Y = -1; + return DmtxRangeEnd; + } + + count = grid->pixelCount; + + assert(count < grid->pixelTotal); + + if(count == grid->pixelTotal - 1) { + /* center pixel */ + loc.X = grid->xCenter; + loc.Y = grid->yCenter; + } + else { + half = grid->pixelTotal / 2; + quarter = half / 2; + + /* horizontal portion */ + if(count < half) { + loc.X = grid->xCenter + ((count < quarter) ? (count - quarter) : (half - count)); + loc.Y = grid->yCenter; + } + /* vertical portion */ + else { + count -= half; + loc.X = grid->xCenter; + loc.Y = grid->yCenter + ((count < quarter) ? (count - quarter) : (half - count)); + } + } + + loc.X += grid->xOffset; + loc.Y += grid->yOffset; + + *locPtr = loc; + + if(loc.X < grid->xMin || loc.X > grid->xMax || + loc.Y < grid->yMin || loc.Y > grid->yMax) + return DmtxRangeBad; + + return DmtxRangeGood; +} + +/** + * \brief Update derived fields based on current state + * \param grid + * \return void + */ +static void +SetDerivedFields(DmtxScanGrid *grid) +{ + grid->jumpSize = grid->extent + 1; + grid->pixelTotal = 2 * grid->extent - 1; + grid->startPos = grid->extent / 2; + grid->pixelCount = 0; + grid->xCenter = grid->yCenter = grid->startPos; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtximage.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2008, 2009 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file dmtximage.c + * \brief Image handling + */ + +/** + * libdmtx stores image data as a large one-dimensional array of packed pixels, + * reading from the array when scanning barcodes and writing to it when creating + * a barcode. Beyond this interaction the calling program is responsible for + * populating and dispatching pixels between the image array and the outside + * world, whether that means loading an image from a file, acquiring camera + * input, displaying output to a screen, saving to disk, etc... + * + * By default, libdmtx treats the first pixel of an image array as the top-left + * corner of the physical image, with the final pixel landing at the bottom- + * right. However, if mapping a pixel buffer this way produces an inverted + * image the calling program can specify DmtxFlipY at image creation time to + * remove the inversion. This has a negligible effect on performance since it + * only modifies the pixel mapping math, and does not alter any pixel data. + * + * Regardless of how an image is stored internally, all libdmtx functions + * consider coordinate (0,0) to mathematically represent the bottom-left pixel + * location of an image using a right-handed coordinate system. + * + * (0,HEIGHT-1) (WIDTH-1,HEIGHT-1) + * + * array pos = 0,1,2,3,...-----------+ + * | | + * | | + * | libdmtx | + * | image | + * | coordinates | + * | | + * | | + * +---------...,N-2,N-1,N = array pos + * + * (0,0) (WIDTH-1,0) + * + * Notes: + * - OpenGL pixel arrays obtained with glReadPixels() are stored + * bottom-to-top; use DmtxFlipY + * - Many popular image formats (e.g., PNG, GIF) store rows + * top-to-bottom; use DmtxFlipNone + */ + +/** + * \brief XXX + * \param XXX + * \return XXX + */ +extern DmtxImage * +dmtxImageCreate(unsigned char *pxl, int width, int height, int pack) +{ + DmtxPassFail err; + DmtxImage *img; + + if(pxl == NULL || width < 1 || height < 1) + return NULL; + + img = (DmtxImage *)calloc(1, sizeof(DmtxImage)); + if(img == NULL) + return NULL; + + img->pxl = pxl; + img->width = width; + img->height = height; + img->pixelPacking = pack; + img->bitsPerPixel = GetBitsPerPixel(pack); + img->bytesPerPixel = img->bitsPerPixel/8; + img->rowPadBytes = 0; + img->rowSizeBytes = img->width * img->bytesPerPixel + img->rowPadBytes; + img->imageFlip = DmtxFlipNone; + + /* Leave channelStart[] and bitsPerChannel[] with zeros from calloc */ + img->channelCount = 0; + + switch(pack) { + case DmtxPackCustom: + break; + case DmtxPack1bppK: + err = dmtxImageSetChannel(img, 0, 1); + return NULL; /* unsupported packing order */ +/* break; */ + case DmtxPack8bppK: + err = dmtxImageSetChannel(img, 0, 8); + break; + case DmtxPack16bppRGB: + case DmtxPack16bppBGR: + case DmtxPack16bppYCbCr: + err = dmtxImageSetChannel(img, 0, 5); + err = dmtxImageSetChannel(img, 5, 5); + err = dmtxImageSetChannel(img, 10, 5); + break; + case DmtxPack24bppRGB: + case DmtxPack24bppBGR: + case DmtxPack24bppYCbCr: + case DmtxPack32bppRGBX: + case DmtxPack32bppBGRX: + err = dmtxImageSetChannel(img, 0, 8); + err = dmtxImageSetChannel(img, 8, 8); + err = dmtxImageSetChannel(img, 16, 8); + break; + case DmtxPack16bppRGBX: + case DmtxPack16bppBGRX: + err = dmtxImageSetChannel(img, 0, 5); + err = dmtxImageSetChannel(img, 5, 5); + err = dmtxImageSetChannel(img, 10, 5); + break; + case DmtxPack16bppXRGB: + case DmtxPack16bppXBGR: + err = dmtxImageSetChannel(img, 1, 5); + err = dmtxImageSetChannel(img, 6, 5); + err = dmtxImageSetChannel(img, 11, 5); + break; + case DmtxPack32bppXRGB: + case DmtxPack32bppXBGR: + err = dmtxImageSetChannel(img, 8, 8); + err = dmtxImageSetChannel(img, 16, 8); + err = dmtxImageSetChannel(img, 24, 8); + break; + case DmtxPack32bppCMYK: + err = dmtxImageSetChannel(img, 0, 8); + err = dmtxImageSetChannel(img, 8, 8); + err = dmtxImageSetChannel(img, 16, 8); + err = dmtxImageSetChannel(img, 24, 8); + break; + default: + return NULL; + } + + return img; +} + +/** + * \brief Free libdmtx image memory + * \param img pointer to img location + * \return DmtxFail | DmtxPass + */ +extern DmtxPassFail +dmtxImageDestroy(DmtxImage **img) +{ + if(img == NULL || *img == NULL) + return DmtxFail; + + free(*img); + + *img = NULL; + + return DmtxPass; +} + +/** + * + * + */ +extern DmtxPassFail +dmtxImageSetChannel(DmtxImage *img, int channelStart, int bitsPerChannel) +{ + if(img->channelCount >= 4) /* IMAGE_MAX_CHANNEL */ + return DmtxFail; + + /* New channel extends beyond pixel data */ +/* if(channelStart + bitsPerChannel > img->bitsPerPixel) + return DmtxFail; */ + + img->bitsPerChannel[img->channelCount] = bitsPerChannel; + img->channelStart[img->channelCount] = channelStart; + (img->channelCount)++; + + return DmtxPass; +} + +/** + * \brief Set image property + * \param img pointer to image + * \return image width + */ +extern DmtxPassFail +dmtxImageSetProp(DmtxImage *img, int prop, int value) +{ + if(img == NULL) + return DmtxFail; + + switch(prop) { + case DmtxPropRowPadBytes: + img->rowPadBytes = value; + img->rowSizeBytes = img->width * (img->bitsPerPixel/8) + img->rowPadBytes; + break; + case DmtxPropImageFlip: + img->imageFlip = value; + break; + default: + break; + } + + return DmtxPass; +} + +/** + * \brief Get image width + * \param img pointer to image + * \return image width + */ +extern int +dmtxImageGetProp(DmtxImage *img, int prop) +{ + if(img == NULL) + return DmtxUndefined; + + switch(prop) { + case DmtxPropWidth: + return img->width; + case DmtxPropHeight: + return img->height; + case DmtxPropPixelPacking: + return img->pixelPacking; + case DmtxPropBitsPerPixel: + return img->bitsPerPixel; + case DmtxPropBytesPerPixel: + return img->bytesPerPixel; + case DmtxPropRowPadBytes: + return img->rowPadBytes; + case DmtxPropRowSizeBytes: + return img->rowSizeBytes; + case DmtxPropImageFlip: + return img->imageFlip; + case DmtxPropChannelCount: + return img->channelCount; + default: + break; + } + + return DmtxUndefined; +} + +/** + * \brief Returns pixel offset for image + * \param img + * \param x coordinate + * \param y coordinate + * \return pixel byte offset + */ +extern int +dmtxImageGetByteOffset(DmtxImage *img, int x, int y) +{ + assert(img != NULL); + assert(!(img->imageFlip & DmtxFlipX)); /* DmtxFlipX is not an option */ + + if(dmtxImageContainsInt(img, 0, x, y) == DmtxFalse) + return DmtxUndefined; + + if(img->imageFlip & DmtxFlipY) + return (y * img->rowSizeBytes + x * img->bytesPerPixel); + + return ((img->height - y - 1) * img->rowSizeBytes + x * img->bytesPerPixel); +} + +/** + * + * + */ +extern DmtxPassFail +dmtxImageGetPixelValue(DmtxImage *img, int x, int y, int channel, int *value) +{ + int offset; +/* unsigned char *pixelPtr; + int pixelValue; + int mask; + int bitShift; */ + + assert(img != NULL); + assert(channel < img->channelCount); + + offset = dmtxImageGetByteOffset(img, x, y); + if(offset == DmtxUndefined) + return DmtxFail; + + switch(img->bitsPerChannel[channel]) { + case 1: +/* assert(img->bitsPerPixel == 1); + mask = 0x01 << (7 - offset%8); + *value = (img->pxl[offset/8] & mask) ? 255 : 0; */ + break; + case 5: + /* XXX might be expensive if we want to scale perfect 0-255 range */ +/* assert(img->bitsPerPixel == 16); + pixelPtr = img->pxl + (offset * (img->bitsPerPixel/8)); + pixelValue = (*pixelPtr << 8) | (*(pixelPtr+1)); + bitShift = img->bitsPerPixel - 5 - img->channelStart[channel]; + mask = 0x1f << bitShift; + *value = (((pixelValue & mask) >> bitShift) << 3); */ + break; + case 8: + assert(img->channelStart[channel] % 8 == 0); + assert(img->bitsPerPixel % 8 == 0); + *value = img->pxl[offset + channel]; + break; + } + + return DmtxPass; +} + +/** + * + * + */ +extern DmtxPassFail +dmtxImageSetPixelValue(DmtxImage *img, int x, int y, int channel, int value) +{ + int offset; +/* unsigned char *pixelPtr; */ +/* int pixelValue; */ +/* int mask; */ +/* int bitShift; */ + + assert(img != NULL); + assert(channel < img->channelCount); + + offset = dmtxImageGetByteOffset(img, x, y); + if(offset == DmtxUndefined) + return DmtxFail; + + switch(img->bitsPerChannel[channel]) { + case 1: +/* assert(img->bitsPerPixel == 1); + mask = 0x01 << (7 - offset%8); + *value = (img->pxl[offset/8] & mask) ? 255 : 0; */ + break; + case 5: + /* XXX might be expensive if we want to scale perfect 0-255 range */ +/* assert(img->bitsPerPixel == 16); + pixelPtr = img->pxl + (offset * (img->bitsPerPixel/8)); + pixelValue = (*pixelPtr << 8) | (*(pixelPtr+1)); + bitShift = img->bitsPerPixel - 5 - img->channelStart[channel]; + mask = 0x1f << bitShift; + *value = (((pixelValue & mask) >> bitShift) << 3); */ + break; + case 8: + assert(img->channelStart[channel] % 8 == 0); + assert(img->bitsPerPixel % 8 == 0); + img->pxl[offset + channel] = value; + break; + } + + return DmtxPass; +} + +/** + * \brief Test whether image contains a coordinate expressed in integers + * \param img + * \param margin width + * \param x coordinate + * \param y coordinate + * \return DmtxTrue | DmtxFalse + */ +extern DmtxBoolean +dmtxImageContainsInt(DmtxImage *img, int margin, int x, int y) +{ + assert(img != NULL); + + if(x - margin >= 0 && x + margin < img->width && + y - margin >= 0 && y + margin < img->height) + return DmtxTrue; + + return DmtxFalse; +} + +/** + * \brief Test whether image contains a coordinate expressed in floating points + * \param img + * \param x coordinate + * \param y coordinate + * \return DmtxTrue | DmtxFalse + */ +extern DmtxBoolean +dmtxImageContainsFloat(DmtxImage *img, float x, float y) +{ + assert(img != NULL); + + if(x >= 0.0 && x < (float)img->width && y >= 0.0 && y < (float)img->height) + return DmtxTrue; + + return DmtxFalse; +} + +/** + * + * + */ +static int +GetBitsPerPixel(int pack) +{ + switch(pack) { + case DmtxPack1bppK: + return 1; + case DmtxPack8bppK: + return 8; + case DmtxPack16bppRGB: + case DmtxPack16bppRGBX: + case DmtxPack16bppXRGB: + case DmtxPack16bppBGR: + case DmtxPack16bppBGRX: + case DmtxPack16bppXBGR: + case DmtxPack16bppYCbCr: + return 16; + case DmtxPack24bppRGB: + case DmtxPack24bppBGR: + case DmtxPack24bppYCbCr: + return 24; + case DmtxPack32bppRGBX: + case DmtxPack32bppXRGB: + case DmtxPack32bppBGRX: + case DmtxPack32bppXBGR: + case DmtxPack32bppCMYK: + return 32; + default: + break; + } + + return DmtxUndefined; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtxbytelist.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2010 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file file.c + */ + +/** + * + * + */ +extern DmtxByteList +dmtxByteListBuild(DmtxByte *storage, int capacity) +{ + DmtxByteList list; + + list.b = storage; + list.capacity = capacity; + list.length = 0; + + return list; +} + +/** + * + * + */ +extern void +dmtxByteListInit(DmtxByteList *list, int length, DmtxByte value, DmtxPassFail *passFail) +{ + if(length > list->capacity) + { + *passFail = DmtxFail; + } + else + { + list->length = length; + memset(list->b, value, sizeof(DmtxByte) * list->capacity); + *passFail = DmtxPass; + } +} + +/** + * + * + */ +extern void +dmtxByteListClear(DmtxByteList *list) +{ + memset(list->b, 0x00, sizeof(DmtxByte) * list->capacity); + list->length = 0; +} + +/** + * + * + */ +extern DmtxBoolean +dmtxByteListHasCapacity(DmtxByteList *list) +{ + return (list->length < list->capacity) ? DmtxTrue : DmtxFalse; +} + +/** + * + * + */ +extern void +dmtxByteListCopy(DmtxByteList *dst, const DmtxByteList *src, DmtxPassFail *passFail) +{ + int length; + + if(dst->capacity < src->length) + { + *passFail = DmtxFail; /* dst must be large enough to hold src data */ + } + else + { + /* Copy as many bytes as dst can hold or src can provide (smaller of two) */ + length = (dst->capacity < src->capacity) ? dst->capacity : src->capacity; + + dst->length = src->length; + memcpy(dst->b, src->b, sizeof(unsigned char) * length); + *passFail = DmtxPass; + } +} + +/** + * + * + */ +extern void +dmtxByteListPush(DmtxByteList *list, DmtxByte value, DmtxPassFail *passFail) +{ + if(list->length >= list->capacity) + { + *passFail = DmtxFail; + } + else + { + list->b[list->length++] = value; + *passFail = DmtxPass; + } +} + +/** + * + * + */ +extern DmtxByte +dmtxByteListPop(DmtxByteList *list, DmtxPassFail *passFail) +{ + *passFail = (list->length > 0) ? DmtxPass : DmtxFail; + + return list->b[--(list->length)]; +} + +/** + * + * + */ +extern void +dmtxByteListPrint(DmtxByteList *list, char *prefix) +{ + int i; + + if(prefix != NULL) + fprintf(stdout, "%s", prefix); + + for(i = 0; i < list->length; i++) + fprintf(stdout, " %d", list->b[i]); + + fputc('\n', stdout); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtxvector2.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** +* libdmtx - Data Matrix Encoding/Decoding Library +* Copyright 2008, 2009 Mike Laughton. All rights reserved. +* +* See LICENSE file in the main project directory for full +* terms of use and distribution. +* +* Contact: Mike Laughton +* +* \file dmtxvector2.c +* \brief 2D Vector math +*/ + +/** +* +* +*/ +extern DmtxVector2 * +dmtxVector2AddTo(DmtxVector2 *v1, const DmtxVector2 *v2) +{ + v1->X += v2->X; + v1->Y += v2->Y; + + return v1; +} + +/** +* +* +*/ +extern DmtxVector2 * +dmtxVector2Add(DmtxVector2 *vOut, const DmtxVector2 *v1, const DmtxVector2 *v2) +{ + *vOut = *v1; + + return dmtxVector2AddTo(vOut, v2); +} + +/** +* +* +*/ +extern DmtxVector2 * +dmtxVector2SubFrom(DmtxVector2 *v1, const DmtxVector2 *v2) +{ + v1->X -= v2->X; + v1->Y -= v2->Y; + + return v1; +} + +/** +* +* +*/ +extern DmtxVector2 * +dmtxVector2Sub(DmtxVector2 *vOut, const DmtxVector2 *v1, const DmtxVector2 *v2) +{ + *vOut = *v1; + + return dmtxVector2SubFrom(vOut, v2); +} + +/** +* +* +*/ +extern DmtxVector2 * +dmtxVector2ScaleBy(DmtxVector2 *v, float s) +{ + v->X *= s; + v->Y *= s; + + return v; +} + +/** +* +* +*/ +extern DmtxVector2 * +dmtxVector2Scale(DmtxVector2 *vOut, const DmtxVector2 *v, float s) +{ + *vOut = *v; + + return dmtxVector2ScaleBy(vOut, s); +} + +/** +* +* +*/ +extern float +dmtxVector2Cross(const DmtxVector2 *v1, const DmtxVector2 *v2) +{ + return (v1->X * v2->Y) - (v1->Y * v2->X); +} + +/** +* +* +*/ +extern float +dmtxVector2Norm(DmtxVector2 *v) +{ + float mag; + + mag = dmtxVector2Mag(v); + + if(mag <= DmtxAlmostZero) + return -1.0; /* XXX this doesn't look clean */ + + dmtxVector2ScaleBy(v, 1/mag); + + return mag; +} + +/** +* +* +*/ +extern float +dmtxVector2Dot(const DmtxVector2 *v1, const DmtxVector2 *v2) +{ + return (v1->X * v2->X) + (v1->Y * v2->Y); +} + +/** +* +* +*/ +extern float +dmtxVector2Mag(const DmtxVector2 *v) +{ + return sqrt(v->X * v->X + v->Y * v->Y); +} + +/** +* +* +*/ +extern float +dmtxDistanceFromRay2(const DmtxRay2 *r, const DmtxVector2 *q) +{ + DmtxVector2 vSubTmp; + + /* Assumes that v is a unit vector */ + assert(fabs(1.0 - dmtxVector2Mag(&(r->v))) <= DmtxAlmostZero); + + return dmtxVector2Cross(&(r->v), dmtxVector2Sub(&vSubTmp, q, &(r->p))); +} + +/** +* +* +*/ +extern float +dmtxDistanceAlongRay2(const DmtxRay2 *r, const DmtxVector2 *q) +{ + DmtxVector2 vSubTmp; + +#ifdef DEBUG + /* Assumes that v is a unit vector */ + if(fabs(1.0 - dmtxVector2Mag(&(r->v))) > DmtxAlmostZero) { + ; /* XXX big error goes here */ + } +#endif + + return dmtxVector2Dot(dmtxVector2Sub(&vSubTmp, q, &(r->p)), &(r->v)); +} + +/** +* +* +*/ +extern DmtxPassFail +dmtxRay2Intersect(DmtxVector2 *point, const DmtxRay2 *p0, const DmtxRay2 *p1) +{ + float numer, denom; + DmtxVector2 w; + + denom = dmtxVector2Cross(&(p1->v), &(p0->v)); + if(fabs(denom) <= DmtxAlmostZero) + return DmtxFail; + + dmtxVector2Sub(&w, &(p1->p), &(p0->p)); + numer = dmtxVector2Cross(&(p1->v), &w); + + return dmtxPointAlongRay2(point, p0, numer/denom); +} + +/** +* +* +*/ +extern DmtxPassFail +dmtxPointAlongRay2(DmtxVector2 *point, const DmtxRay2 *r, float t) +{ + DmtxVector2 vTmp; + + /* Ray should always have unit length of 1 */ + assert(fabs(1.0 - dmtxVector2Mag(&(r->v))) <= DmtxAlmostZero); + + dmtxVector2Scale(&vTmp, &(r->v), t); + dmtxVector2Add(point, &(r->p), &vTmp); + + return DmtxPass; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "dmtxmatrix3.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * libdmtx - Data Matrix Encoding/Decoding Library + * Copyright 2008, 2009 Mike Laughton. All rights reserved. + * + * See LICENSE file in the main project directory for full + * terms of use and distribution. + * + * Contact: Mike Laughton + * + * \file dmtxmatrix3.c + * \brief 2D Matrix (3x3) math + */ + +/** + * \brief Copy matrix contents + * \param m0 Copy target + * \param m1 Copy source + * \return void + */ +extern void +dmtxMatrix3Copy(DmtxMatrix3 m0, DmtxMatrix3 m1) +{ + memcpy(m0, m1, sizeof(DmtxMatrix3)); +} + +/** + * \brief Generate identity transformation matrix + * \param m Generated matrix + * \return void + * + * | 1 0 0 | + * m = | 0 1 0 | + * | 0 0 1 | + * + * Transform "m" + * (doesn't change anything) + * |\ + * (0,1) x----o +--+ \ (0,1) x----o + * | | | \ | | + * | | | / | | + * +----* +--+ / +----* + * (0,0) (1,0) |/ (0,0) (1,0) + * + */ +extern void +dmtxMatrix3Identity(DmtxMatrix3 m) +{ + static DmtxMatrix3 tmp = { {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1} }; + dmtxMatrix3Copy(m, tmp); +} + +/** + * \brief Generate translate transformation matrix + * \param m Generated matrix + * \param tx + * \param ty + * \return void + * + * | 1 0 0 | + * m = | 0 1 0 | + * | tx ty 1 | + * + * Transform "m" + * _____ (tx,1+ty) x----o (1+tx,1+ty) + * \ | | | + * (0,1) x----o / | (0,1) +-|--+ | + * | | / /\| | +----* (1+tx,ty) + * | | \ / | | + * +----* ` +----+ + * (0,0) (1,0) (0,0) (1,0) + * + */ +void dmtxMatrix3Translate(DmtxMatrix3 m, float tx, float ty) +{ + dmtxMatrix3Identity(m); + m[2][0] = tx; + m[2][1] = ty; +} + +/** + * \brief Generate rotate transformation + * \param m Generated matrix + * \param angle + * \return void + * + * | cos(a) sin(a) 0 | + * m = | -sin(a) cos(a) 0 | + * | 0 0 1 | + * o + * Transform "m" / ` + * ___ / ` + * (0,1) x----o |/ \ x * (cos(a),sin(a)) + * | | '-- | ` / + * | | ___/ ` / a + * +----* `+ - - - - - - + * (0,0) (1,0) (0,0) + * + */ +extern void +dmtxMatrix3Rotate(DmtxMatrix3 m, float angle) +{ + float sinAngle, cosAngle; + + sinAngle = sin(angle); + cosAngle = cos(angle); + + dmtxMatrix3Identity(m); + m[0][0] = cosAngle; + m[0][1] = sinAngle; + m[1][0] = -sinAngle; + m[1][1] = cosAngle; +} + +/** + * \brief Generate scale transformation matrix + * \param m Generated matrix + * \param sx + * \param sy + * \return void + * + * | sx 0 0 | + * m = | 0 sy 0 | + * | 0 0 1 | + * + * Transform "m" + * _____ (0,sy) x-------o (sx,sy) + * \ | | | + * (0,1) x----o / | (0,1) +----+ | + * | | / /\| | | | + * | | \ / | | | + * +----* ` +----+--* + * (0,0) (1,0) (0,0) (sx,0) + * + */ +extern void +dmtxMatrix3Scale(DmtxMatrix3 m, float sx, float sy) +{ + dmtxMatrix3Identity(m); + m[0][0] = sx; + m[1][1] = sy; +} + +/** + * \brief Generate shear transformation matrix + * \param m Generated matrix + * \param shx + * \param shy + * \return void + * + * | 0 shy 0 | + * m = | shx 0 0 | + * | 0 0 1 | + */ +extern void +dmtxMatrix3Shear(DmtxMatrix3 m, float shx, float shy) +{ + dmtxMatrix3Identity(m); + m[1][0] = shx; + m[0][1] = shy; +} + +/** + * \brief Generate top line skew transformation + * \param m + * \param b0 + * \param b1 + * \param sz + * \return void + * + * | b1/b0 0 (b1-b0)/(sz*b0) | + * m = | 0 sz/b0 0 | + * | 0 0 1 | + * + * (sz,b1) o + * /| Transform "m" + * / | + * / | +--+ + * / | | | + * (0,b0) x | | | + * | | +-+ +-+ + * (0,sz) +----+ \ / (0,sz) x----o + * | | \ / | | + * | | \/ | | + * +----+ +----+ + * (0,0) (sz,0) (0,0) (sz,0) + * + */ +extern void +dmtxMatrix3LineSkewTop(DmtxMatrix3 m, float b0, float b1, float sz) +{ + assert(b0 >= DmtxAlmostZero); + + dmtxMatrix3Identity(m); + m[0][0] = b1/b0; + m[1][1] = sz/b0; + m[0][2] = (b1 - b0)/(sz*b0); +} + +/** + * \brief Generate top line skew transformation (inverse) + * \param m + * \param b0 + * \param b1 + * \param sz + * \return void + */ +extern void +dmtxMatrix3LineSkewTopInv(DmtxMatrix3 m, float b0, float b1, float sz) +{ + assert(b1 >= DmtxAlmostZero); + + dmtxMatrix3Identity(m); + m[0][0] = b0/b1; + m[1][1] = b0/sz; + m[0][2] = (b0 - b1)/(sz*b1); +} + +/** + * \brief Generate side line skew transformation + * \param m + * \param b0 + * \param b1 + * \param sz + * \return void + */ +extern void +dmtxMatrix3LineSkewSide(DmtxMatrix3 m, float b0, float b1, float sz) +{ + assert(b0 >= DmtxAlmostZero); + + dmtxMatrix3Identity(m); + m[0][0] = sz/b0; + m[1][1] = b1/b0; + m[1][2] = (b1 - b0)/(sz*b0); +} + +/** + * \brief Generate side line skew transformation (inverse) + * \param m + * \param b0 + * \param b1 + * \param sz + * \return void + */ +extern void +dmtxMatrix3LineSkewSideInv(DmtxMatrix3 m, float b0, float b1, float sz) +{ + assert(b1 >= DmtxAlmostZero); + + dmtxMatrix3Identity(m); + m[0][0] = b0/sz; + m[1][1] = b0/b1; + m[1][2] = (b0 - b1)/(sz*b1); +} + +/** + * \brief Multiply two matrices to create a third + * \param mOut + * \param m0 + * \param m1 + * \return void + */ +extern void +dmtxMatrix3Multiply(DmtxMatrix3 mOut, DmtxMatrix3 m0, DmtxMatrix3 m1) +{ + int i, j, k; + float val; + + for(i = 0; i < 3; i++) { + for(j = 0; j < 3; j++) { + val = 0.0; + for(k = 0; k < 3; k++) { + val += m0[i][k] * m1[k][j]; + } + mOut[i][j] = val; + } + } +} + +/** + * \brief Multiply two matrices in place + * \param m0 + * \param m1 + * \return void + */ +extern void +dmtxMatrix3MultiplyBy(DmtxMatrix3 m0, DmtxMatrix3 m1) +{ + DmtxMatrix3 mTmp; + + dmtxMatrix3Copy(mTmp, m0); + dmtxMatrix3Multiply(m0, mTmp, m1); +} + +/** + * \brief Multiply vector and matrix + * \param vOut Vector (output) + * \param vIn Vector (input) + * \param m Matrix to be multiplied + * \return DmtxPass | DmtxFail + */ +extern int +dmtxMatrix3VMultiply(DmtxVector2 *vOut, DmtxVector2 *vIn, DmtxMatrix3 m) +{ + float w; + + w = vIn->X*m[0][2] + vIn->Y*m[1][2] + m[2][2]; + if(fabs(w) <= DmtxAlmostZero) { + vOut->X = FLT_MAX; + vOut->Y = FLT_MAX; + return DmtxFail; + } + + vOut->X = (vIn->X*m[0][0] + vIn->Y*m[1][0] + m[2][0])/w; + vOut->Y = (vIn->X*m[0][1] + vIn->Y*m[1][1] + m[2][1])/w; + + return DmtxPass; +} + +/** + * \brief Multiply vector and matrix in place + * \param v Vector (input and output) + * \param m Matrix to be multiplied + * \return DmtxPass | DmtxFail + */ +extern int +dmtxMatrix3VMultiplyBy(DmtxVector2 *v, DmtxMatrix3 m) +{ + int success; + DmtxVector2 vOut; + + success = dmtxMatrix3VMultiply(&vOut, v, m); + *v = vOut; + + return success; +} + +/** + * \brief Print matrix contents to STDOUT + * \param m + * \return void + */ +extern void +dmtxMatrix3Print(DmtxMatrix3 m) +{ + fprintf(stdout, "%8.8f\t%8.8f\t%8.8f\n", m[0][0], m[0][1], m[0][2]); + fprintf(stdout, "%8.8f\t%8.8f\t%8.8f\n", m[1][0], m[1][1], m[1][2]); + fprintf(stdout, "%8.8f\t%8.8f\t%8.8f\n", m[2][0], m[2][1], m[2][2]); + fprintf(stdout, "\n"); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void imlib_find_datamatrices(list_t *out, image_t *ptr, rectangle_t *roi, int effort) +{ + uint8_t *grayscale_image = (ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? ptr->data : fb_alloc(roi->w * roi->h, FB_ALLOC_NO_HINT); + + if (ptr->pixfmt != PIXFORMAT_GRAYSCALE) { + image_t img; + img.w = roi->w; + img.h = roi->h; + img.pixfmt = PIXFORMAT_GRAYSCALE; + img.data = grayscale_image; + img.size = img.w * img.h; + imlib_pixfmt_to(&img, ptr, roi); + // imlib_draw_image(&img, ptr, 0, 0, 1.f, 1.f, roi, -1, 256, NULL, NULL, 0, NULL, NULL); + } + + umm_init_x(fb_avail()); + + DmtxImage *image = dmtxImageCreate(grayscale_image, + (ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? ptr->w : roi->w, + (ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? ptr->h : roi->h, + DmtxPack8bppK); + + DmtxDecode *decode = dmtxDecodeCreate(image, 1); + dmtxDecodeSetProp(decode, DmtxPropXmin, (ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? roi->x : 0); + dmtxDecodeSetProp(decode, DmtxPropYmin, (ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? roi->y : 0); + dmtxDecodeSetProp(decode, DmtxPropXmax, ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? roi->x : 0) + (roi->w - 1)); + dmtxDecodeSetProp(decode, DmtxPropYmax, ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? roi->y : 0) + (roi->h - 1)); + + imlib_list_init(out, sizeof(find_datamatrices_list_lnk_data_t)); + + int max_iterations = effort; + int current_iterations = 0; + for (DmtxRegion *region = dmtxRegionFindNext(decode, max_iterations, ¤t_iterations); region; region = dmtxRegionFindNext(decode, max_iterations, ¤t_iterations)) { + DmtxMessage *message = dmtxDecodeMatrixRegion(decode, region, DmtxUndefined); + + if (message) { + find_datamatrices_list_lnk_data_t lnk_data; + + DmtxVector2 p[4]; + + p[0].X = p[0].Y = p[1].Y = p[3].X = 0.0; + p[1].X = p[3].Y = p[2].X = p[2].Y = 1.0; + + dmtxMatrix3VMultiplyBy(&p[0], region->fit2raw); + dmtxMatrix3VMultiplyBy(&p[1], region->fit2raw); + dmtxMatrix3VMultiplyBy(&p[2], region->fit2raw); + dmtxMatrix3VMultiplyBy(&p[3], region->fit2raw); + + int height = dmtxDecodeGetProp(decode, DmtxPropHeight); + + rectangle_init(&(lnk_data.rect), + fast_roundf(p[0].X) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->x), + height - 1 - fast_roundf(p[0].Y) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->y), 0, 0); + + for (size_t k = 1, l = (sizeof(p) / sizeof(p[0])); k < l; k++) { + rectangle_t temp; + rectangle_init(&temp, fast_roundf(p[k].X) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->x), + height - 1 - fast_roundf(p[k].Y) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->y), 0, 0); + rectangle_united(&(lnk_data.rect), &temp); + } + + // Add corners... + lnk_data.corners[0].x = fast_roundf(p[3].X) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->x); // top-left + lnk_data.corners[0].y = height - 1 - fast_roundf(p[3].Y) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->y); // top-left + lnk_data.corners[1].x = fast_roundf(p[2].X) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->x); // top-right + lnk_data.corners[1].y = height - 1 - fast_roundf(p[2].Y) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->y); // top-right + lnk_data.corners[2].x = fast_roundf(p[1].X) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->x); // bottom-right + lnk_data.corners[2].y = height - 1 - fast_roundf(p[1].Y) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->y); // bottom-right + lnk_data.corners[3].x = fast_roundf(p[0].X) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->x); // bottom-left + lnk_data.corners[3].y = height - 1 - fast_roundf(p[0].Y) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->y); // bottom-left + + // Payload is NOT already null terminated. + lnk_data.payload_len = message->outputIdx; + lnk_data.payload = xalloc(message->outputIdx); + memcpy(lnk_data.payload, message->output, message->outputIdx); + + int rotate = fast_roundf((((2 * M_PI) + fast_atan2f(p[1].Y - p[0].Y, p[1].X - p[0].X)) * 180) / M_PI); + if(rotate >= 360) rotate -= 360; + + lnk_data.rotation = rotate; + lnk_data.rows = dmtxGetSymbolAttribute(DmtxSymAttribSymbolRows, region->sizeIdx); + lnk_data.columns = dmtxGetSymbolAttribute(DmtxSymAttribSymbolCols, region->sizeIdx); + lnk_data.capacity = dmtxGetSymbolAttribute(DmtxSymAttribSymbolDataWords, region->sizeIdx); + lnk_data.padding = message->padCount; + + list_push_back(out, &lnk_data); + + dmtxMessageDestroy(&message); + } + + dmtxRegionDestroy(®ion); + } + + dmtxDecodeDestroy(&decode); + dmtxImageDestroy(&image); + + fb_free(NULL); // umm_init_x(); + if (ptr->pixfmt != PIXFORMAT_GRAYSCALE) { + fb_free(grayscale_image); // grayscale_image; + } +} + +#pragma GCC diagnostic pop +#endif //IMLIB_ENABLE_DATAMATRICES diff --git a/github_source/minicv2/src/draw.c b/github_source/minicv2/src/draw.c new file mode 100644 index 0000000..c4fa337 --- /dev/null +++ b/github_source/minicv2/src/draw.c @@ -0,0 +1,8226 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Basic drawing functions. + */ +#include "font.h" +#include "imlib.h" +#include "unaligned_memcpy.h" + +// #ifdef IMLIB_ENABLE_DMA2D +// #include STM32_HAL_H +// #include "dma.h" +// #endif + +void* imlib_compute_row_ptr(const image_t *img, int y) { + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + return IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + } + case PIXFORMAT_GRAYSCALE: { + return IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + } + case PIXFORMAT_RGB565: { + return IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + } + case PIXFORMAT_RGB888: { + return IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + } + default: { + // This shouldn't happen, at least we return a valid memory block + return img->data; + } + } +} +inline int imlib_get_pixel(image_t *img, int x, int y) +{ + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + return IMAGE_GET_BINARY_PIXEL(img, x, y); + } + case PIXFORMAT_GRAYSCALE: { + return IMAGE_GET_GRAYSCALE_PIXEL(img, x, y); + } + case PIXFORMAT_RGB565: { + return IMAGE_GET_RGB565_PIXEL(img, x, y); + } + case PIXFORMAT_RGB888: { + return pixel24232(IMAGE_GET_RGB888_PIXEL(img, x, y)); + } + default: { + return -1; + } + } +} +inline int imlib_get_pixel_fast(image_t *img, const void *row_ptr, int x) +{ + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + return IMAGE_GET_BINARY_PIXEL_FAST((uint32_t*)row_ptr, x); + } + case PIXFORMAT_GRAYSCALE: { + return IMAGE_GET_GRAYSCALE_PIXEL_FAST((uint8_t*)row_ptr, x); + } + case PIXFORMAT_RGB565: { + return IMAGE_GET_RGB565_PIXEL_FAST((uint16_t*)row_ptr, x); + } + case PIXFORMAT_RGB888: { + return pixel24232(IMAGE_GET_RGB888_PIXEL_FAST((pixel24_t*)row_ptr, x)); + } + default: { + return -1; + } + } +} + + +// Set pixel (handles boundary check and image type check). +void imlib_set_pixel(image_t *img, int x, int y, int p) +{ + if ((0 <= x) && (x < img->w) && (0 <= y) && (y < img->h)) { + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + IMAGE_PUT_BINARY_PIXEL(img, x, y, p); + break; + } + case PIXFORMAT_GRAYSCALE: { + IMAGE_PUT_GRAYSCALE_PIXEL(img, x, y, p); + break; + } + case PIXFORMAT_RGB565: { + IMAGE_PUT_RGB565_PIXEL(img, x, y, p); + break; + } + case PIXFORMAT_RGB888: { + IMAGE_PUT_RGB888_PIXEL(img, x, y, p); + break; + } + default: { + break; + } + } + } +} + +// https://stackoverflow.com/questions/1201200/fast-algorithm-for-drawing-filled-circles +static void point_fill(image_t *img, int cx, int cy, int r0, int r1, int c) +{ + for (int y = r0; y <= r1; y++) { + for (int x = r0; x <= r1; x++) { + if (((x * x) + (y * y)) <= (r0 * r0)) { + imlib_set_pixel(img, cx + x, cy + y, c); + } + } + } +} + +// https://rosettacode.org/wiki/Bitmap/Bresenham%27s_line_algorithm#C +void imlib_draw_line(image_t *img, int x0, int y0, int x1, int y1, int c, int thickness) +{ + if (thickness > 0) { + int thickness0 = (thickness - 0) / 2; + int thickness1 = (thickness - 1) / 2; + int dx = abs(x1 - x0), sx = (x0 < x1) ? 1 : -1; + int dy = abs(y1 - y0), sy = (y0 < y1) ? 1 : -1; + int err = ((dx > dy) ? dx : -dy) / 2; + + for (;;) { + point_fill(img, x0, y0, -thickness0, thickness1, c); + if ((x0 == x1) && (y0 == y1)) break; + int e2 = err; + if (e2 > -dx) { err -= dy; x0 += sx; } + if (e2 < dy) { err += dx; y0 += sy; } + } + } +} + +static void xLine(image_t *img, int x1, int x2, int y, int c) +{ + while (x1 <= x2) imlib_set_pixel(img, x1++, y, c); +} + +static void yLine(image_t *img, int x, int y1, int y2, int c) +{ + while (y1 <= y2) imlib_set_pixel(img, x, y1++, c); +} + +void imlib_draw_rectangle(image_t *img, int rx, int ry, int rw, int rh, int c, int thickness, bool fill) +{ + if (fill) { + + for (int y = ry, yy = ry + rh; y < yy; y++) { + for (int x = rx, xx = rx + rw; x < xx; x++) { + imlib_set_pixel(img, x, y, c); + } + } + + } else if (thickness > 0) { + int thickness0 = (thickness - 0) / 2; + int thickness1 = (thickness - 1) / 2; + + for (int i = rx - thickness0, j = rx + rw + thickness1, k = ry + rh - 1; i < j; i++) { + yLine(img, i, ry - thickness0, ry + thickness1, c); + yLine(img, i, k - thickness0, k + thickness1, c); + } + + for (int i = ry - thickness0, j = ry + rh + thickness1, k = rx + rw - 1; i < j; i++) { + xLine(img, rx - thickness0, rx + thickness1, i, c); + xLine(img, k - thickness0, k + thickness1, i, c); + } + } +} + +// https://stackoverflow.com/questions/27755514/circle-with-thickness-drawing-algorithm +void imlib_draw_circle(image_t *img, int cx, int cy, int r, int c, int thickness, bool fill) +{ + if (fill) { + point_fill(img, cx, cy, -r, r, c); + } else if (thickness > 0) { + int thickness0 = (thickness - 0) / 2; + int thickness1 = (thickness - 1) / 2; + + int xo = r + thickness0; + int xi = IM_MAX(r - thickness1, 0); + int xi_tmp = xi; + int y = 0; + int erro = 1 - xo; + int erri = 1 - xi; + + while(xo >= y) { + xLine(img, cx + xi, cx + xo, cy + y, c); + yLine(img, cx + y, cy + xi, cy + xo, c); + xLine(img, cx - xo, cx - xi, cy + y, c); + yLine(img, cx - y, cy + xi, cy + xo, c); + xLine(img, cx - xo, cx - xi, cy - y, c); + yLine(img, cx - y, cy - xo, cy - xi, c); + xLine(img, cx + xi, cx + xo, cy - y, c); + yLine(img, cx + y, cy - xo, cy - xi, c); + + y++; + + if (erro < 0) { + erro += 2 * y + 1; + } else { + xo--; + erro += 2 * (y - xo + 1); + } + + if (y > xi_tmp) { + xi = y; + } else { + if (erri < 0) { + erri += 2 * y + 1; + } else { + xi--; + erri += 2 * (y - xi + 1); + } + } + } + } +} +void imlib_draw_cross(image_t *img, int x, int y, int c, int size, int thickness) +{ + imlib_draw_line(img, x - size, y , x + size, y , c, thickness); + imlib_draw_line(img, x , y - size, x , y + size, c, thickness); +} +// https://scratch.mit.edu/projects/50039326/ +static void scratch_draw_pixel(image_t *img, int x0, int y0, int dx, int dy, float shear_dx, float shear_dy, int r0, int r1, int c) +{ + point_fill(img, x0 + dx, y0 + dy + (int)fast_roundf((dx * shear_dy) / shear_dx), r0, r1, c); +} + +// https://scratch.mit.edu/projects/50039326/ +static void scratch_draw_line(image_t *img, int x0, int y0, int dx, int dy0, int dy1, float shear_dx, float shear_dy, int c) +{ + int y = y0 + (int)fast_roundf((dx * shear_dy) / shear_dx); + yLine(img, x0 + dx, y + dy0, y + dy1, c); +} + +// https://scratch.mit.edu/projects/50039326/ +static void scratch_draw_sheared_ellipse(image_t *img, int x0, int y0, int width, int height, bool filled, float shear_dx, float shear_dy, int c, int thickness) +{ + int thickness0 = (thickness - 0) / 2; + int thickness1 = (thickness - 1) / 2; + if (((thickness > 0) || filled) && (shear_dx != 0)) { + int a_squared = width * width; + int four_a_squared = a_squared * 4; + int b_squared = height * height; + int four_b_squared = b_squared * 4; + + int x = 0; + int y = height; + int sigma = (2 * b_squared) + (a_squared * (1 - (2 * height))); + + while ((b_squared * x) <= (a_squared * y)) { + if (filled) { + scratch_draw_line(img, x0, y0, x, -y, y, shear_dx, shear_dy, c); + scratch_draw_line(img, x0, y0, -x, -y, y, shear_dx, shear_dy, c); + } else { + scratch_draw_pixel(img, x0, y0, x, y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, -x, y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, -x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); + } + + if (sigma >= 0) { + sigma += four_a_squared * (1 - y); + y -= 1; + } + + sigma += b_squared * ((4 * x) + 6); + x += 1; + } + + x = width; + y = 0; + sigma = (2 * a_squared) + (b_squared * (1 - (2 * width))); + + while ((a_squared * y) <= (b_squared * x)) { + if (filled) { + scratch_draw_line(img, x0, y0, x, -y, y, shear_dx, shear_dy, c); + scratch_draw_line(img, x0, y0, -x, -y, y, shear_dx, shear_dy, c); + } else { + scratch_draw_pixel(img, x0, y0, x, y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, -x, y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); + scratch_draw_pixel(img, x0, y0, -x, -y, shear_dx, shear_dy, -thickness0, thickness1, c); + } + + if (sigma >= 0) { + sigma += four_b_squared * (1 - x); + x -= 1; + } + + sigma += a_squared * ((4 * y) + 6); + y += 1; + } + } +} + +// https://scratch.mit.edu/projects/50039326/ +static void scratch_draw_rotated_ellipse(image_t *img, int x, int y, int x_axis, int y_axis, int rotation, bool filled, int c, int thickness) +{ + if ((x_axis > 0) && (y_axis > 0)) { + if ((x_axis == y_axis) || (rotation == 0)) { + scratch_draw_sheared_ellipse(img, x, y, x_axis / 2, y_axis / 2, filled, 1, 0, c, thickness); + } else if (rotation == 90) { + scratch_draw_sheared_ellipse(img, x, y, y_axis / 2, x_axis / 2, filled, 1, 0, c, thickness); + } else { + + // Avoid rotations above 90. + if (rotation > 90) { + rotation -= 90; + int temp = x_axis; + x_axis = y_axis; + y_axis = temp; + } + + // Avoid rotations above 45. + if (rotation > 45) { + rotation -= 90; + int temp = x_axis; + x_axis = y_axis; + y_axis = temp; + } + + float theta = fast_atanf(IM_DIV(y_axis, x_axis) * (-tanf(IM_DEG2RAD(rotation)))); + float shear_dx = (x_axis * cosf(theta) * cosf(IM_DEG2RAD(rotation))) - (y_axis * sinf(theta) * sinf(IM_DEG2RAD(rotation))); + float shear_dy = (x_axis * cosf(theta) * sinf(IM_DEG2RAD(rotation))) + (y_axis * sinf(theta) * cosf(IM_DEG2RAD(rotation))); + float shear_x_axis = fast_fabsf(shear_dx); + float shear_y_axis = IM_DIV((y_axis * x_axis), shear_x_axis); + scratch_draw_sheared_ellipse(img, x, y, fast_floorf(shear_x_axis / 2), fast_floorf(shear_y_axis / 2), filled, shear_dx, shear_dy, c, thickness); + } + } +} + +void imlib_draw_ellipse(image_t *img, int cx, int cy, int rx, int ry, int rotation, int c, int thickness, bool fill) +{ + int r = rotation % 180; + if (r < 0) r += 180; + + scratch_draw_rotated_ellipse(img, cx, cy, rx * 2, ry * 2, r, fill, c, thickness); +} + +// char rotation == 0, 90, 180, 360, etc. +// string rotation == 0, 90, 180, 360, etc. +void imlib_draw_string(image_t *img, int x_off, int y_off, const char *str, int c, float scale, int x_spacing, int y_spacing, bool mono_space, + int char_rotation, bool char_hmirror, bool char_vflip, int string_rotation, bool string_hmirror, bool string_vflip) +{ + char_rotation %= 360; + if (char_rotation < 0) char_rotation += 360; + char_rotation = (char_rotation / 90) * 90; + + string_rotation %= 360; + if (string_rotation < 0) string_rotation += 360; + string_rotation = (string_rotation / 90) * 90; + + bool char_swap_w_h = (char_rotation == 90) || (char_rotation == 270); + bool char_upsidedown = (char_rotation == 180) || (char_rotation == 270); + + if (string_hmirror) x_off -= fast_floorf(font[0].w * scale) - 1; + if (string_vflip) y_off -= fast_floorf(font[0].h * scale) - 1; + + int org_x_off = x_off; + int org_y_off = y_off; + const int anchor = x_off; + + for(char ch, last = '\0'; (ch = *str); str++, last = ch) { + + if ((last == '\r') && (ch == '\n')) { // handle "\r\n" strings + continue; + } + + if ((ch == '\n') || (ch == '\r')) { // handle '\n' or '\r' strings + x_off = anchor; + y_off += (string_vflip ? -1 : +1) * (fast_floorf((char_swap_w_h ? font[0].w : font[0].h) * scale) + y_spacing); // newline height == space height + continue; + } + + if ((ch < ' ') || (ch > '~')) { // handle unknown characters + continue; + } + + const glyph_t *g = &font[ch - ' ']; + + if (!mono_space) { + // Find the first pixel set and offset to that. + bool exit = false; + + if (!char_swap_w_h) { + for (int x = 0, xx = g->w; x < xx; x++) { + for (int y = 0, yy = g->h; y < yy; y++) { + if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] & + (1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) { + x_off += (string_hmirror ? +1 : -1) * fast_floorf(x * scale); + exit = true; + break; + } + } + + if (exit) break; + } + } else { + for (int y = g->h - 1; y >= 0; y--) { + for (int x = 0, xx = g->w; x < xx; x++) { + if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] & + (1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) { + x_off += (string_hmirror ? +1 : -1) * fast_floorf((g->h - 1 - y) * scale); + exit = true; + break; + } + } + + if (exit) break; + } + } + } + + for (int y = 0, yy = fast_floorf(g->h * scale); y < yy; y++) { + for (int x = 0, xx = fast_floorf(g->w * scale); x < xx; x++) { + if (g->data[fast_floorf(y / scale)] & (1 << (g->w - 1 - fast_floorf(x / scale)))) { + int16_t x_tmp = x_off + (char_hmirror ? (xx - x - 1) : x), y_tmp = y_off + (char_vflip ? (yy - y - 1) : y); + point_rotate(x_tmp, y_tmp, IM_DEG2RAD(char_rotation), x_off + (xx / 2), y_off + (yy / 2), &x_tmp, &y_tmp); + point_rotate(x_tmp, y_tmp, IM_DEG2RAD(string_rotation), org_x_off, org_y_off, &x_tmp, &y_tmp); + imlib_set_pixel(img, x_tmp, y_tmp, c); + } + } + } + + if (mono_space) { + x_off += (string_hmirror ? -1 : +1) * (fast_floorf((char_swap_w_h ? g->h : g->w) * scale) + x_spacing); + } else { + // Find the last pixel set and offset to that. + bool exit = false; + + if (!char_swap_w_h) { + for (int x = g->w - 1; x >= 0; x--) { + for (int y = g->h - 1; y >= 0; y--) { + if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] & + (1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) { + x_off += (string_hmirror ? -1 : +1) * (fast_floorf((x + 2) * scale) + x_spacing); + exit = true; + break; + } + } + + if (exit) break; + } + } else { + for (int y = 0, yy = g->h; y < yy; y++) { + for (int x = g->w - 1; x >= 0; x--) { + if (g->data[(char_upsidedown ^ char_vflip) ? (g->h - 1 - y) : y] & + (1 << ((char_upsidedown ^ char_hmirror ^ string_hmirror) ? x : (g->w - 1 - x)))) { + x_off += (string_hmirror ? -1 : +1) * (fast_floorf(((g->h - 1 - y) + 2) * scale) + x_spacing); + exit = true; + break; + } + } + + if (exit) break; + } + } + + if (!exit) x_off += (string_hmirror ? -1 : +1) * fast_floorf(scale * 3); // space char + } + } +} + +void imlib_draw_row_setup(imlib_draw_row_data_t *data) +{ + image_t temp; + temp.w = data->dst_img->w; + temp.h = data->dst_img->h; + temp.pixfmt = data->src_img_pixfmt; + + // Image Row Size should be the width of the destination image + // but with the bpp of the source image. + size_t image_row_size = image_size(&temp) / data->dst_img->h; + + data->toggle = 0; + data->row_buffer[0] = fb_alloc(image_row_size, FB_ALLOC_NO_HINT); + +#ifdef IMLIB_ENABLE_DMA2D + +#else + data->row_buffer[1] = data->row_buffer[0]; +#endif + + int alpha = data->alpha, max = 256; + + if (data->dst_img->pixfmt == PIXFORMAT_RGB565) { + alpha >>= 3; // 5-bit alpha for RGB565 + max = 32; + } + + // Set smuad_alpha and smuad_alpha_palette even if we don't use them with DMA2D as we may have + // to fallback to using them if the draw_image calls imlib_draw_row_put_row_buffer(). + data->smuad_alpha = data->black_background ? alpha : ((alpha << 16) | (max - alpha)); + + if (data->alpha_palette) { + data->smuad_alpha_palette = fb_alloc(256 * sizeof(uint32_t), FB_ALLOC_NO_HINT); + + for (int i = 0, a = alpha; i < 256; i++) { + int new_alpha = fast_roundf((a * data->alpha_palette[i]) / 255.f); + data->smuad_alpha_palette[i] = data->black_background ? new_alpha : ((new_alpha << 16) | (max - new_alpha)); + } + } else { + data->smuad_alpha_palette = NULL; + } +} + +void imlib_draw_row_teardown(imlib_draw_row_data_t *data) +{ + if (data->smuad_alpha_palette) fb_free(data->smuad_alpha_palette); +#ifdef IMLIB_ENABLE_DMA2D +#endif + fb_free(data->row_buffer[0]); // data->row_buffer[0] +} + +#ifdef IMLIB_ENABLE_DMA2D +#endif + +void *imlib_draw_row_get_row_buffer(imlib_draw_row_data_t *data) +{ + void *result = data->row_buffer[data->toggle]; + data->toggle = !data->toggle; + return result; +} + +void imlib_draw_row_put_row_buffer(imlib_draw_row_data_t *data, void *row_buffer) +{ + data->row_buffer[data->toggle] = row_buffer; + data->toggle = !data->toggle; +#ifdef IMLIB_ENABLE_DMA2D +#endif +} + +// Draws (x_end - x_start) pixels. +// src width must be equal to dst width. +void imlib_draw_row(int x_start, int x_end, int y_row, imlib_draw_row_data_t *data) +{ + #define BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha) \ + ({ \ + __typeof__ (src_pixel) _src_pixel = (src_pixel); \ + __typeof__ (dst_pixel) _dst_pixel = (dst_pixel); \ + __typeof__ (smuad_alpha) _smuad_alpha = (smuad_alpha); \ + const long mask_r = 0x7c007c00, mask_g = 0x07e007e0, mask_b = 0x001f001f; \ + uint32_t rgb = (_src_pixel << 16) | _dst_pixel; \ + long rb = ((rgb >> 1) & mask_r) | (rgb & mask_b); \ + long g = rgb & mask_g; \ + int rb_out = __SMUAD(_smuad_alpha, rb) >> 5; \ + int g_out = __SMUAD(_smuad_alpha, g) >> 5; \ + ((rb_out << 1) & 0xf800) | (g_out & 0x07e0) | (rb_out & 0x001f); \ + }) + + #define BLEND_RGB566_0(src_pixel, smuad_alpha) \ + ({ \ + __typeof__ (src_pixel) _src_pixel = (src_pixel); \ + __typeof__ (smuad_alpha) _smuad_alpha = (smuad_alpha); \ + int rb_out = ((_src_pixel & 0xf81f) * _smuad_alpha) >> 5; \ + int g_out = ((_src_pixel & 0x7e0) * _smuad_alpha) >> 5; \ + (rb_out & 0xf81f) | (g_out & 0x7e0); \ + }) + #define BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha) \ + ({ \ + __typeof__ (src_pixel) _src_pixel = (src_pixel); \ + __typeof__ (dst_pixel) _dst_pixel = (dst_pixel); \ + __typeof__ (smuad_alpha) _smuad_alpha = (smuad_alpha); \ + int rgb_out = 0;\ + uint8_t *_rgb_s = (uint8_t*)&_src_pixel;\ + uint8_t *_rgb_d = (uint8_t*)&_dst_pixel;\ + uint8_t *_rgb_o = (uint8_t*)&rgb_out;\ + uint16_t *_smuad_alpha_tmp = (uint16_t*)&_smuad_alpha;\ + _rgb_o[0] = ((_rgb_s[0] * _smuad_alpha_tmp[0] + _rgb_d[0] * _smuad_alpha_tmp[1]) >> 8);\ + _rgb_o[1] = ((_rgb_s[1] * _smuad_alpha_tmp[0] + _rgb_d[1] * _smuad_alpha_tmp[1]) >> 8);\ + _rgb_o[2] = ((_rgb_s[2] * _smuad_alpha_tmp[0] + _rgb_d[2] * _smuad_alpha_tmp[1]) >> 8);\ + rgb_out;\ + }) + + #define BLEND_RGB888_0(src_pixel, smuad_alpha) \ + ({ \ + __typeof__ (src_pixel) _src_pixel = (src_pixel); \ + __typeof__ (smuad_alpha) _smuad_alpha = (smuad_alpha); \ + int rgb_out = 0;\ + uint8_t *_rgb_s = (uint8_t*)&_src_pixel;\ + uint8_t *_rgb_o = (uint8_t*)&rgb_out;\ + uint16_t *_smuad_alpha_tmp = (uint16_t*)&_smuad_alpha;\ + _rgb_o[0] = ((_rgb_s[0] * _smuad_alpha_tmp[0]) >> 8);\ + _rgb_o[1] = ((_rgb_s[1] * _smuad_alpha_tmp[0]) >> 8);\ + _rgb_o[2] = ((_rgb_s[2] * _smuad_alpha_tmp[0]) >> 8);\ + rgb_out;\ + }) + + #define COLOR_GRAYSCALE_BINARY_MIN_LSL16 (COLOR_GRAYSCALE_BINARY_MIN << 16) + #define COLOR_GRAYSCALE_BINARY_MAX_LSL16 (COLOR_GRAYSCALE_BINARY_MAX << 16) + + switch (data->dst_img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *dst32 = data->dst_row_override ? + ((uint32_t *) data->dst_row_override) : IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(data->dst_img, y_row); + switch (data->src_img_pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *src32 = (uint32_t *) data->row_buffer[!data->toggle]; + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + uint32_t alpha_pal0 = smuad_alpha_palette[0], alpha_pal255 = smuad_alpha_palette[255]; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + long smuad_pixel = (pixel ? COLOR_GRAYSCALE_BINARY_MAX_LSL16 : COLOR_GRAYSCALE_BINARY_MIN_LSL16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + long smuad_pixel = pixel ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN; + pixel = ((smuad_alpha * smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + uint32_t alpha_pal0 = smuad_alpha_palette[0], alpha_pal255 = smuad_alpha_palette[255]; + uint32_t pal0 = color_palette[0], pal255 = color_palette[255]; + pal0 = COLOR_RGB565_TO_Y(pal0) << 16; + pal255 = COLOR_RGB565_TO_Y(pal255) << 16; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + long smuad_pixel = (pixel ? pal255 : pal0) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + long smuad_pixel = pixel ? pal255 : pal0; + pixel = ((smuad_alpha * smuad_pixel) >> 24) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + uint16_t pal0 = color_palette[0], pal255 = color_palette[255]; + pal0 = COLOR_RGB565_TO_Y(pal0) > 127; + pal255 = COLOR_RGB565_TO_Y(pal255) > 127; + switch ((pal0 << 1) | (pal255 << 0)) { + case 0: { + for (int x = x_start; x < x_end; x++) { + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, 0); + } + break; + } + case 1: { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + break; + } + case 2: { + for (int x = x_start; x < x_end; x++) { + int pixel = !IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + break; + } + case 3: { + for (int x = x_start; x < x_end; x++) { + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, 1); + } + break; + } + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + long smuad_pixel = (IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? COLOR_GRAYSCALE_BINARY_MAX_LSL16 : COLOR_GRAYSCALE_BINARY_MIN_LSL16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + int pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + long smuad_pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN; + int pixel = ((smuad_alpha * smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + uint32_t pal0 = color_palette[0], pal255 = color_palette[255]; + pal0 = COLOR_RGB565_TO_Y(pal0) << 16; + pal255 = COLOR_RGB565_TO_Y(pal255) << 16; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + long smuad_pixel = (IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? pal255 : pal0) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + int pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + long smuad_pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? pal255 : pal0; + int pixel = ((smuad_alpha * smuad_pixel) >> 24) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + // More desirable results are produced by alpha blending with 8-bits. + // if (!data->color_palette) { + // for (int x = x_start; x < x_end; x++) { + // int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) & IMAGE_GET_BINARY_PIXEL_FAST(dst32, x); + // IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + // } + // } else { + // const uint16_t *color_palette = data->color_palette; + // uint16_t pal0 = color_palette[0], pal255 = color_palette[255]; + // pal0 = COLOR_RGB565_TO_Y(pal0) > 127; + // pal255 = COLOR_RGB565_TO_Y(pal255) > 127; + // switch ((pal0 << 1) | (pal255 << 0)) { + // case 0: { + // for (int x = x_start; x < x_end; x++) { + // IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, 0); + // } + // break; + // } + // case 1: { + // for (int x = x_start; x < x_end; x++) { + // int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) & IMAGE_GET_BINARY_PIXEL_FAST(dst32, x); + // IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + // } + // break; + // } + // case 2: { + // for (int x = x_start; x < x_end; x++) { + // int pixel = !(IMAGE_GET_BINARY_PIXEL_FAST(src32, x) | IMAGE_GET_BINARY_PIXEL_FAST(dst32, x)); + // IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + // } + // break; + // } + // case 3: { + // for (int x = x_start; x < x_end; x++) { + // IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, 1); + // } + // break; + // } + // } + // } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *src8 = ((uint8_t *) data->row_buffer[!data->toggle]) + x_start; + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[pixel]; + long smuad_pixel = (pixel << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[pixel]; + pixel = ((smuad_alpha * pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[pixel]; + pixel = color_palette[pixel]; + pixel = COLOR_RGB565_TO_Y(pixel); + long smuad_pixel = (pixel << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[pixel]; + pixel = color_palette[pixel]; + pixel = COLOR_RGB565_TO_Y(pixel); + pixel = ((smuad_alpha * pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src8++ > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = color_palette[*src8++]; + pixel = COLOR_RGB565_TO_Y(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + long smuad_pixel = (*src8++ << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + int pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = ((smuad_alpha * (*src8++)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = color_palette[*src8++]; + pixel = COLOR_RGB565_TO_Y(pixel); + long smuad_pixel = (pixel << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = color_palette[*src8++]; + pixel = COLOR_RGB565_TO_Y(pixel); + pixel = ((smuad_alpha * pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *src16 = ((uint16_t *) data->row_buffer[!data->toggle]) + x_start; + if (data->rgb_channel < 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + long smuad_pixel = (pixel << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + pixel = ((smuad_alpha * pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + pixel = ((smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_Y(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_Y(pixel)]; + pixel = COLOR_RGB565_TO_Y(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = ((smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_Y(pixel)]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_Y(pixel)]; + pixel = ((smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } + } else if (data->rgb_channel == 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + long smuad_pixel = (pixel << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + pixel = ((smuad_alpha * pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + pixel = ((smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_R8(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_R8(pixel)]; + pixel = COLOR_RGB565_TO_Y(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + long smuad_pixel = (COLOR_RGB565_TO_R8(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = ((smuad_alpha * COLOR_RGB565_TO_R8(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_R8(pixel)]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_R8(pixel)]; + pixel = ((smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } + } else if (data->rgb_channel == 1) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + long smuad_pixel = (pixel << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + pixel = ((smuad_alpha * pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + pixel = ((smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_G8(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_G8(pixel)]; + pixel = COLOR_RGB565_TO_Y(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + long smuad_pixel = (COLOR_RGB565_TO_G8(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = ((smuad_alpha * COLOR_RGB565_TO_G8(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_G8(pixel)]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_G8(pixel)]; + pixel = ((smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } + } else if (data->rgb_channel == 2) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + long smuad_pixel = (pixel << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + pixel = ((smuad_alpha * pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + pixel = ((smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_B8(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_B8(pixel)]; + pixel = COLOR_RGB565_TO_Y(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + long smuad_pixel = (COLOR_RGB565_TO_B8(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = ((smuad_alpha * COLOR_RGB565_TO_B8(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_B8(pixel)]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_B8(pixel)]; + pixel = ((smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *src24 = ((pixel24_t *) data->row_buffer[!data->toggle]) + x_start; + if (data->rgb_channel < 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + long smuad_pixel = (pixel << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + pixel = ((smuad_alpha * pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + pixel = ((smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_Y(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_Y(pixel)]); + pixel = COLOR_RGB888_TO_Y(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = ((smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_Y(pixel)]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_Y(pixel)]); + pixel = ((smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } + } else if (data->rgb_channel == 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + long smuad_pixel = (pixel << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + pixel = ((smuad_alpha * pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + pixel = ((smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_R8(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_R8(pixel)]); + pixel = COLOR_RGB888_TO_Y(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + long smuad_pixel = (COLOR_RGB888_TO_R8(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = ((smuad_alpha * COLOR_RGB888_TO_R8(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_R8(pixel)]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_R8(pixel)]); + pixel = ((smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } + } else if (data->rgb_channel == 1) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + long smuad_pixel = (pixel << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + pixel = ((smuad_alpha * pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + pixel = ((smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_G8(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_G8(pixel)]); + pixel = COLOR_RGB888_TO_Y(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + long smuad_pixel = (COLOR_RGB888_TO_G8(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = ((smuad_alpha * COLOR_RGB888_TO_G8(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_G8(pixel)]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_G8(pixel)]); + pixel = ((smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } + } else if (data->rgb_channel == 2) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + long smuad_pixel = (pixel << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel]; + pixel = ((smuad_alpha * pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + pixel = ((smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_B8(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_B8(pixel)]); + pixel = COLOR_RGB888_TO_Y(pixel) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + long smuad_pixel = (COLOR_RGB888_TO_B8(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = ((smuad_alpha * COLOR_RGB888_TO_B8(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_B8(pixel)]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | (IMAGE_GET_BINARY_PIXEL_FAST(dst32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN); + pixel = (__SMUAD(smuad_alpha, smuad_pixel) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_B8(pixel)]); + pixel = ((smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8) > 127; + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, x, pixel); + } + } + } + } + } + break; + } + + default: { + break; + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *dst8 = (data->dst_row_override ? ((uint8_t *) data->dst_row_override) : IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(data->dst_img, y_row)) + x_start; + switch (data->src_img_pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *src32 = (uint32_t *) data->row_buffer[!data->toggle]; + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + uint32_t alpha_pal0 = smuad_alpha_palette[0], alpha_pal255 = smuad_alpha_palette[255]; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + long smuad_pixel = (pixel ? COLOR_GRAYSCALE_BINARY_MAX_LSL16 : COLOR_GRAYSCALE_BINARY_MIN_LSL16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + long smuad_pixel = pixel ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN; + *dst8++ = (smuad_alpha * smuad_pixel) >> 8; + } + } + } else { + const uint16_t *color_palette = data->color_palette; + uint32_t alpha_pal0 = smuad_alpha_palette[0], alpha_pal255 = smuad_alpha_palette[255]; + uint32_t pal0 = color_palette[0], pal255 = color_palette[255]; + pal0 = COLOR_RGB565_TO_Y(pal0) << 16; + pal255 = COLOR_RGB565_TO_Y(pal255) << 16; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + long smuad_pixel = (pixel ? pal255 : pal0) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + long smuad_pixel = pixel ? pal255 : pal0; + *dst8++ = (smuad_alpha * smuad_pixel) >> 24; + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + *dst8++ = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN; + } + } else { + const uint16_t *color_palette = data->color_palette; + uint16_t pal0 = color_palette[0], pal255 = color_palette[255]; + pal0 = COLOR_RGB565_TO_Y(pal0); + pal255 = COLOR_RGB565_TO_Y(pal255); + for (int x = x_start; x < x_end; x++) { + *dst8++ = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? pal255 : pal0; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + long smuad_pixel = (IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? COLOR_GRAYSCALE_BINARY_MAX_LSL16 : COLOR_GRAYSCALE_BINARY_MIN_LSL16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + long smuad_pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? COLOR_GRAYSCALE_BINARY_MAX : COLOR_GRAYSCALE_BINARY_MIN; + *dst8++ = (smuad_alpha * smuad_pixel) >> 8; + } + } + } else { + const uint16_t *color_palette = data->color_palette; + uint32_t pal0 = color_palette[0], pal255 = color_palette[255]; + pal0 = COLOR_RGB565_TO_Y(pal0) << 16; + pal255 = COLOR_RGB565_TO_Y(pal255) << 16; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + long smuad_pixel = (IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? pal255 : pal0) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + long smuad_pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? pal255 : pal0; + *dst8++ = (smuad_alpha * smuad_pixel) >> 24; + } + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *src8 = ((uint8_t *) data->row_buffer[!data->toggle]) + x_start; + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[pixel]; + long smuad_pixel = (pixel << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[pixel]; + *dst8++ = (smuad_alpha * pixel) >> 8; + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[pixel]; + pixel = color_palette[pixel]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[pixel]; + pixel = color_palette[pixel]; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8; + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + unaligned_memcpy(dst8 , src8, (x_end - x_start) * sizeof(uint8_t)); + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = color_palette[*src8++]; + *dst8++ = COLOR_RGB565_TO_Y(pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + long smuad_pixel = (*src8++ << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + *dst8++ = (smuad_alpha * (*src8++)) >> 8; + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = color_palette[*src8++]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = color_palette[*src8++]; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8; + } + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *src16 = ((uint16_t *) data->row_buffer[!data->toggle]) + x_start; + if (data->rgb_channel < 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + long smuad_pixel = (pixel_y << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + *dst8++ = (smuad_alpha * pixel_y) >> 8; + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8; + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst8++ = COLOR_RGB565_TO_Y(pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_Y(pixel)]; + *dst8++ = COLOR_RGB565_TO_Y(pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8; + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_Y(pixel)]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_Y(pixel)]; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8; + } + } + } + } + } else if (data->rgb_channel == 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + long smuad_pixel = (pixel_y << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + *dst8++ = (smuad_alpha * pixel_y) >> 8; + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8; + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst8++ = COLOR_RGB565_TO_R8(pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_R8(pixel)]; + *dst8++ = COLOR_RGB565_TO_Y(pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + long smuad_pixel = (COLOR_RGB565_TO_R8(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_R8(pixel)) >> 8; + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_R8(pixel)]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_R8(pixel)]; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8; + } + } + } + } + } else if (data->rgb_channel == 1) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + long smuad_pixel = (pixel_y << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + *dst8++ = (smuad_alpha * pixel_y) >> 8; + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8; + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst8++ = COLOR_RGB565_TO_G8(pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_G8(pixel)]; + *dst8++ = COLOR_RGB565_TO_Y(pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + long smuad_pixel = (COLOR_RGB565_TO_G8(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_G8(pixel)) >> 8; + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_G8(pixel)]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_G8(pixel)]; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8; + } + } + } + } + } else if (data->rgb_channel == 2) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + long smuad_pixel = (pixel_y << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + *dst8++ = (smuad_alpha * pixel_y) >> 8; + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + int pixel_y = COLOR_RGB565_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = color_palette[pixel_y]; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8; + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst8++ = COLOR_RGB565_TO_B8(pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_B8(pixel)]; + *dst8++ = COLOR_RGB565_TO_Y(pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + long smuad_pixel = (COLOR_RGB565_TO_B8(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_B8(pixel)) >> 8; + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_B8(pixel)]; + long smuad_pixel = (COLOR_RGB565_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = color_palette[COLOR_RGB565_TO_B8(pixel)]; + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8; + } + } + } + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *src24 = ((pixel24_t *) data->row_buffer[!data->toggle]) + x_start; + if (data->rgb_channel < 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + long smuad_pixel = (pixel_y << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + *dst8++ = (smuad_alpha * pixel_y) >> 8; + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_Y(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + *dst8++ = (smuad_alpha * COLOR_RGB565_TO_Y(pixel)) >> 8; + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + *dst8++ = COLOR_RGB888_TO_Y(pixel); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_Y(pixel)]); + *dst8++ = COLOR_RGB888_TO_Y(pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + *dst8++ = (smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8; + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_Y(pixel)]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_Y(pixel)]); + *dst8++ = (smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8; + } + } + } + } + } else if (data->rgb_channel == 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + long smuad_pixel = (pixel_y << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + *dst8++ = (smuad_alpha * pixel_y) >> 8; + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_R8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + *dst8++ = (smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8; + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + *dst8++ = COLOR_RGB888_TO_R8(pixel); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_R8(pixel)]); + *dst8++ = COLOR_RGB888_TO_Y(pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + long smuad_pixel = (COLOR_RGB888_TO_R8(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + *dst8++ = (smuad_alpha * COLOR_RGB888_TO_R8(pixel)) >> 8; + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_R8(pixel)]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_R8(pixel)]); + *dst8++ = (smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8; + } + } + } + } + } else if (data->rgb_channel == 1) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + long smuad_pixel = (pixel_y << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + *dst8++ = (smuad_alpha * pixel_y) >> 8; + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_G8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + *dst8++ = (smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8; + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + *dst8++ = COLOR_RGB888_TO_G8(pixel); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_G8(pixel)]); + *dst8++ = COLOR_RGB888_TO_Y(pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + long smuad_pixel = (COLOR_RGB888_TO_G8(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + *dst8++ = (smuad_alpha * COLOR_RGB888_TO_G8(pixel)) >> 8; + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_G8(pixel)]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_G8(pixel)]); + *dst8++ = (smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8; + } + } + } + } + } else if (data->rgb_channel == 2) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + long smuad_pixel = (pixel_y << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + *dst8++ = (smuad_alpha * pixel_y) >> 8; + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + int pixel_y = COLOR_RGB888_TO_B8(pixel); + long smuad_alpha = smuad_alpha_palette[pixel_y]; + pixel = pixel24232(color_palette[pixel_y]); + *dst8++ = (smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8; + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + *dst8++ = COLOR_RGB888_TO_B8(pixel); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_B8(pixel)]); + *dst8++ = COLOR_RGB888_TO_Y(pixel); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + long smuad_pixel = (COLOR_RGB888_TO_B8(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + *dst8++ = (smuad_alpha * COLOR_RGB888_TO_B8(pixel)) >> 8; + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_B8(pixel)]); + long smuad_pixel = (COLOR_RGB888_TO_Y(pixel) << 16) | *dst8; + *dst8++ = __SMUAD(smuad_alpha, smuad_pixel) >> 8; + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = pixel24232(color_palette[COLOR_RGB888_TO_B8(pixel)]); + *dst8++ = (smuad_alpha * COLOR_RGB888_TO_Y(pixel)) >> 8; + } + } + } + } + } + break; + } + default: { + break; + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *dst16 = (data->dst_row_override ? ((uint16_t *) data->dst_row_override) : IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(data->dst_img, y_row)) + x_start; + switch (data->src_img_pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *src32 = (uint32_t *) data->row_buffer[!data->toggle]; + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + uint32_t alpha_pal0 = smuad_alpha_palette[0], alpha_pal255 = smuad_alpha_palette[255]; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + int src_pixel = pixel ? COLOR_RGB565_BINARY_MAX : COLOR_RGB565_BINARY_MIN; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + int src_pixel = pixel ? COLOR_RGB565_BINARY_MAX : COLOR_RGB565_BINARY_MIN; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + uint32_t alpha_pal0 = smuad_alpha_palette[0], alpha_pal255 = smuad_alpha_palette[255]; + uint16_t pal0 = color_palette[0], pal255 = color_palette[255]; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + int src_pixel = pixel ? pal255 : pal0; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + int src_pixel = pixel ? pal255 : pal0; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + *dst16++ = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? COLOR_RGB565_BINARY_MAX : COLOR_RGB565_BINARY_MIN; + } + } else { + const uint16_t *color_palette = data->color_palette; + uint16_t pal0 = color_palette[0], pal255 = color_palette[255]; + for (int x = x_start; x < x_end; x++) { + *dst16++ = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? pal255 : pal0; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? COLOR_RGB565_BINARY_MAX : COLOR_RGB565_BINARY_MIN; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? COLOR_RGB565_BINARY_MAX : COLOR_RGB565_BINARY_MIN; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + uint16_t pal0 = color_palette[0], pal255 = color_palette[255]; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? pal255 : pal0; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? pal255 : pal0; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *src8 = ((uint8_t *) data->row_buffer[!data->toggle]) + x_start; +#ifdef IMLIB_ENABLE_DMA2D + if (data->dma2d_enabled) { + if (!data->callback) HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + #if defined(MCU_SERIES_F7) || defined(MCU_SERIES_H7) + SCB_CleanDCache_by_Addr((uint32_t *) src8, (x_end - x_start) * sizeof(uint8_t)); + SCB_CleanInvalidateDCache_by_Addr((uint32_t *) dst16, (x_end - x_start) * sizeof(uint16_t)); + #endif + HAL_DMA2D_BlendingStart(&data->dma2d, (uint32_t) src8, (uint32_t) dst16, (uint32_t) dst16, x_end - x_start, 1); + if (data->callback) HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + } else if (data->smuad_alpha_palette) { +#else + if (data->smuad_alpha_palette) { +#endif + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[src_pixel]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[src_pixel]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[src_pixel]; + src_pixel = color_palette[src_pixel]; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[src_pixel]; + src_pixel = color_palette[src_pixel]; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src8++; + *dst16++ = COLOR_Y_TO_RGB565(pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + *dst16++ = color_palette[*src8++]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src8++; + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src8++; + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = color_palette[*src8++]; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = color_palette[*src8++]; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *src16 = ((uint16_t *) data->row_buffer[!data->toggle]) + x_start; + if (data->rgb_channel < 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + long smuad_alpha = smuad_alpha_palette[COLOR_RGB565_TO_Y(src_pixel)]; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + long smuad_alpha = smuad_alpha_palette[COLOR_RGB565_TO_Y(src_pixel)]; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_Y(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = color_palette[src_pixel_y]; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_Y(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = color_palette[src_pixel_y]; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + unaligned_memcpy(dst16, src16, (x_end - x_start) * sizeof(uint16_t)); + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst16++ = color_palette[COLOR_RGB565_TO_Y(pixel)]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { +#ifdef IMLIB_ENABLE_DMA2D + if (data->dma2d_enabled) { + if (!data->callback) HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + #if defined(MCU_SERIES_F7) || defined(MCU_SERIES_H7) + SCB_CleanDCache_by_Addr((uint32_t *) src16, (x_end - x_start) * sizeof(uint16_t)); + SCB_CleanInvalidateDCache_by_Addr((uint32_t *) dst16, (x_end - x_start) * sizeof(uint16_t)); + #endif + HAL_DMA2D_BlendingStart(&data->dma2d, (uint32_t) src16, (uint32_t) dst16, (uint32_t) dst16, x_end - x_start, 1); + if (data->callback) HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + } else if (!data->black_background) { +#else + if (!data->black_background) { +#endif + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = color_palette[COLOR_RGB565_TO_Y(src_pixel)]; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = color_palette[COLOR_RGB565_TO_Y(src_pixel)]; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } + } + } else if (data->rgb_channel == 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_R8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel_y); + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_R8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel_y); + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_R8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = color_palette[src_pixel_y]; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_R8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = color_palette[src_pixel_y]; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_R8(pixel); + *dst16++ = COLOR_Y_TO_RGB565(pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst16++ = color_palette[COLOR_RGB565_TO_R8(pixel)]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = COLOR_RGB565_TO_R8(src_pixel); + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = COLOR_RGB565_TO_R8(src_pixel); + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = color_palette[COLOR_RGB565_TO_R8(src_pixel)]; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = color_palette[COLOR_RGB565_TO_R8(src_pixel)]; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } + } + } else if (data->rgb_channel == 1) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_G8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel_y); + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_G8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel_y); + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_G8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = color_palette[src_pixel_y]; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_G8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = color_palette[src_pixel_y]; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_G8(pixel); + *dst16++ = COLOR_Y_TO_RGB565(pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst16++ = color_palette[COLOR_RGB565_TO_G8(pixel)]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = COLOR_RGB565_TO_G8(src_pixel); + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = COLOR_RGB565_TO_G8(src_pixel); + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = color_palette[COLOR_RGB565_TO_G8(src_pixel)]; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = color_palette[COLOR_RGB565_TO_G8(src_pixel)]; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } + } + } else if (data->rgb_channel == 2) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_B8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel_y); + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_B8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel_y); + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_B8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = color_palette[src_pixel_y]; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_B8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = color_palette[src_pixel_y]; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_B8(pixel); + *dst16++ = COLOR_Y_TO_RGB565(pixel); + } + } else { + const uint16_t *color_palette = data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst16++ = color_palette[COLOR_RGB565_TO_B8(pixel)]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = COLOR_RGB565_TO_B8(src_pixel); + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = COLOR_RGB565_TO_B8(src_pixel); + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } else { + const uint16_t *color_palette = data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = color_palette[COLOR_RGB565_TO_B8(src_pixel)]; + int dst_pixel = *dst16; + *dst16++ = BLEND_RGB566(src_pixel, dst_pixel, smuad_alpha); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = color_palette[COLOR_RGB565_TO_B8(src_pixel)]; + *dst16++ = BLEND_RGB566_0(src_pixel, smuad_alpha); + } + } + } + } + } + break; + } + default: { + break; + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *dst24 = (data->dst_row_override ? ((pixel24_t *) data->dst_row_override) : IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(data->dst_img, y_row)) + x_start; + switch (data->src_img_pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *src32 = (uint32_t *) data->row_buffer[!data->toggle]; + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + uint32_t alpha_pal0 = smuad_alpha_palette[0], alpha_pal255 = smuad_alpha_palette[255]; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + int src_pixel = pixel ? COLOR_RGB888_BINARY_MAX : COLOR_RGB888_BINARY_MIN; + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + int src_pixel = pixel ? COLOR_RGB888_BINARY_MAX : COLOR_RGB888_BINARY_MIN; + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + uint32_t alpha_pal0 = smuad_alpha_palette[0], alpha_pal255 = smuad_alpha_palette[255]; + pixel24_t pal0 = color_palette[0], pal255 = color_palette[255]; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + int src_pixel = pixel ? pixel24232(pal255) : pixel24232(pal0); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x); + long smuad_alpha = pixel ? alpha_pal255 : alpha_pal0; + int src_pixel = pixel ? pixel24232(pal255) : pixel24232(pal0); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + *dst24++ = pixel32224(IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? COLOR_RGB888_BINARY_MAX : COLOR_RGB888_BINARY_MIN); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + pixel24_t pal0 = color_palette[0], pal255 = color_palette[255]; + for (int x = x_start; x < x_end; x++) { + *dst24++ = pixel32224(IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? pixel24232(pal255) : pixel24232(pal0)); + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? COLOR_RGB888_BINARY_MAX : COLOR_RGB888_BINARY_MIN; + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? COLOR_RGB888_BINARY_MAX : COLOR_RGB888_BINARY_MIN; + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + pixel24_t pal0 = color_palette[0], pal255 = color_palette[255]; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? pixel24232(pal255) : pixel24232(pal0); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = IMAGE_GET_BINARY_PIXEL_FAST(src32, x) ? pixel24232(pal255) : pixel24232(pal0); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *src8 = ((uint8_t *) data->row_buffer[!data->toggle]) + x_start; +#ifdef IMLIB_ENABLE_DMA2D + if (data->dma2d_enabled) { + if (!data->callback) HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + #if defined(MCU_SERIES_F7) || defined(MCU_SERIES_H7) + SCB_CleanDCache_by_Addr((uint32_t *) src8, (x_end - x_start) * sizeof(uint8_t)); + SCB_CleanInvalidateDCache_by_Addr((uint32_t *) dst24, (x_end - x_start) * sizeof(uint16_t)); + #endif + HAL_DMA2D_BlendingStart(&data->dma2d, (uint32_t) src8, (uint32_t) dst24, (uint32_t) dst24, x_end - x_start, 1); + if (data->callback) HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + } else if (data->smuad_alpha_palette) { +#else + if (data->smuad_alpha_palette) { +#endif + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[src_pixel]; + src_pixel = COLOR_Y_TO_RGB888(src_pixel); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[src_pixel]; + src_pixel = COLOR_Y_TO_RGB888(src_pixel); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[src_pixel]; + src_pixel = pixel24232(color_palette[src_pixel]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src8++; + long smuad_alpha = smuad_alpha_palette[src_pixel]; + src_pixel = pixel24232(color_palette[src_pixel]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src8++; + *dst24++ = pixel32224(COLOR_Y_TO_RGB888(pixel)); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + *dst24++ = color_palette[*src8++]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src8++; + src_pixel = COLOR_Y_TO_RGB888(src_pixel); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src8++; + src_pixel = COLOR_Y_TO_RGB888(src_pixel); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(color_palette[*src8++]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(color_palette[*src8++]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *src16 = ((uint16_t *) data->row_buffer[!data->toggle]) + x_start; + if (data->rgb_channel < 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + long smuad_alpha = smuad_alpha_palette[COLOR_RGB565_TO_Y(src_pixel)]; + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + long smuad_alpha = smuad_alpha_palette[COLOR_RGB565_TO_Y(src_pixel)]; + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_Y(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_Y(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + unaligned_memcpy(dst24, src16, (x_end - x_start) * sizeof(pixel24_t)); + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst24++ = color_palette[COLOR_RGB565_TO_Y(pixel)]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { +#ifdef IMLIB_ENABLE_DMA2D + if (data->dma2d_enabled) { + if (!data->callback) HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + #if defined(MCU_SERIES_F7) || defined(MCU_SERIES_H7) + SCB_CleanDCache_by_Addr((uint32_t *) src16, (x_end - x_start) * sizeof(uint16_t)); + SCB_CleanInvalidateDCache_by_Addr((uint32_t *) dst24, (x_end - x_start) * sizeof(uint16_t)); + #endif + HAL_DMA2D_BlendingStart(&data->dma2d, (uint32_t) src16, (uint32_t) dst24, (uint32_t) dst24, x_end - x_start, 1); + if (data->callback) HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + } else if (!data->black_background) { +#else + if (!data->black_background) { +#endif + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = pixel24232(color_palette[COLOR_RGB565_TO_Y(src_pixel)]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = pixel24232(color_palette[COLOR_RGB565_TO_Y(src_pixel)]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } + } else if (data->rgb_channel == 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_R8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel_y); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_R8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel_y); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_R8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_R8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_R8(pixel); + *dst24++ = pixel32224(COLOR_Y_TO_RGB565(pixel)); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst24++ = color_palette[COLOR_RGB565_TO_R8(pixel)]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = COLOR_RGB565_TO_R8(src_pixel); + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = COLOR_RGB565_TO_R8(src_pixel); + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = pixel24232(color_palette[COLOR_RGB565_TO_R8(src_pixel)]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = pixel24232(color_palette[COLOR_RGB565_TO_R8(src_pixel)]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } + } else if (data->rgb_channel == 1) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_G8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel_y); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_G8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel_y); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_G8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_G8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_G8(pixel); + *dst24++ = pixel32224(COLOR_Y_TO_RGB565(pixel)); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst24++ = color_palette[COLOR_RGB565_TO_G8(pixel)]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = COLOR_RGB565_TO_G8(src_pixel); + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = COLOR_RGB565_TO_G8(src_pixel); + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = pixel24232(color_palette[COLOR_RGB565_TO_G8(src_pixel)]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = pixel24232(color_palette[COLOR_RGB565_TO_G8(src_pixel)]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } + } else if (data->rgb_channel == 2) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_B8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel_y); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_B8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB565(src_pixel_y); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_B8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + int src_pixel_y = COLOR_RGB565_TO_B8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + pixel = COLOR_RGB565_TO_B8(pixel); + *dst24++ = pixel32224(COLOR_Y_TO_RGB565(pixel)); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = *src16++; + *dst24++ = color_palette[COLOR_RGB565_TO_B8(pixel)]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = COLOR_RGB565_TO_B8(src_pixel); + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = COLOR_RGB565_TO_B8(src_pixel); + src_pixel = COLOR_Y_TO_RGB565(src_pixel); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = pixel24232(color_palette[COLOR_RGB565_TO_B8(src_pixel)]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = *src16++; + src_pixel = pixel24232(color_palette[COLOR_RGB565_TO_B8(src_pixel)]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *src24 = ((pixel24_t *) data->row_buffer[!data->toggle]) + x_start; + if (data->rgb_channel < 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + long smuad_alpha = smuad_alpha_palette[COLOR_RGB888_TO_Y(src_pixel)]; + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + long smuad_alpha = smuad_alpha_palette[COLOR_RGB888_TO_Y(src_pixel)]; + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_Y(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_Y(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + unaligned_memcpy(dst24, src24, (x_end - x_start) * sizeof(pixel24_t)); + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + *dst24++ = color_palette[COLOR_RGB888_TO_Y(pixel)]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { +#ifdef IMLIB_ENABLE_DMA2D + if (data->dma2d_enabled) { + if (!data->callback) HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + #if defined(MCU_SERIES_F7) || defined(MCU_SERIES_H7) + SCB_CleanDCache_by_Addr((uint32_t *) src16, (x_end - x_start) * sizeof(uint16_t)); + SCB_CleanInvalidateDCache_by_Addr((uint32_t *) dst24, (x_end - x_start) * sizeof(uint16_t)); + #endif + HAL_DMA2D_BlendingStart(&data->dma2d, (uint32_t) src16, (uint32_t) dst24, (uint32_t) dst24, x_end - x_start, 1); + if (data->callback) HAL_DMA2D_PollForTransfer(&data->dma2d, 1000); + } else if (!data->black_background) { +#else + if (!data->black_background) { +#endif + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = pixel24232(color_palette[COLOR_RGB888_TO_Y(src_pixel)]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = pixel24232(color_palette[COLOR_RGB888_TO_Y(src_pixel)]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } + } else if (data->rgb_channel == 0) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_R8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB888(src_pixel_y); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_R8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB888(src_pixel_y); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_R8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_R8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB565_TO_R8(pixel); + *dst24++ = pixel32224(COLOR_Y_TO_RGB888(pixel)); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + *dst24++ = color_palette[COLOR_RGB888_TO_R8(pixel)]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = COLOR_RGB888_TO_R8(src_pixel); + src_pixel = COLOR_Y_TO_RGB888(src_pixel); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = COLOR_RGB888_TO_R8(src_pixel); + src_pixel = COLOR_Y_TO_RGB888(src_pixel); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = pixel24232(color_palette[COLOR_RGB888_TO_R8(src_pixel)]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = pixel24232(color_palette[COLOR_RGB888_TO_R8(src_pixel)]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } + } else if (data->rgb_channel == 1) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_G8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB888(src_pixel_y); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_G8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB888(src_pixel_y); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_G8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_G8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_G8(pixel); + *dst24++ = pixel32224(COLOR_Y_TO_RGB888(pixel)); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + *dst24++ = color_palette[COLOR_RGB888_TO_G8(pixel)]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = COLOR_RGB888_TO_G8(src_pixel); + src_pixel = COLOR_Y_TO_RGB888(src_pixel); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = COLOR_RGB888_TO_G8(src_pixel); + src_pixel = COLOR_Y_TO_RGB888(src_pixel); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = pixel24232(color_palette[COLOR_RGB888_TO_G8(src_pixel)]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = pixel24232(color_palette[COLOR_RGB888_TO_G8(src_pixel)]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } + } else if (data->rgb_channel == 2) { + if (data->smuad_alpha_palette) { + const uint32_t *smuad_alpha_palette = data->smuad_alpha_palette; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_B8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB888(src_pixel_y); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_B8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = COLOR_Y_TO_RGB888(src_pixel_y); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_B8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + int src_pixel_y = COLOR_RGB888_TO_B8(src_pixel); + long smuad_alpha = smuad_alpha_palette[src_pixel_y]; + src_pixel = pixel24232(color_palette[src_pixel_y]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } else if (data->alpha == 256) { + if (!data->color_palette) { + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + pixel = COLOR_RGB888_TO_B8(pixel); + *dst24++ = pixel32224(COLOR_Y_TO_RGB888(pixel)); + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + for (int x = x_start; x < x_end; x++) { + int pixel = pixel24232(*src24++); + *dst24++ = color_palette[COLOR_RGB888_TO_B8(pixel)]; + } + } + } else { + long smuad_alpha = data->smuad_alpha; + if (!data->color_palette) { + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = COLOR_RGB888_TO_B8(src_pixel); + src_pixel = COLOR_Y_TO_RGB888(src_pixel); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = COLOR_RGB888_TO_B8(src_pixel); + src_pixel = COLOR_Y_TO_RGB888(src_pixel); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } else { + const pixel24_t *color_palette = (pixel24_t*)data->color_palette; + if (!data->black_background) { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = pixel24232(color_palette[COLOR_RGB888_TO_B8(src_pixel)]); + int dst_pixel = pixel24232(*dst24); + *dst24++ = pixel32224(BLEND_RGB888(src_pixel, dst_pixel, smuad_alpha)); + } + } else { + for (int x = x_start; x < x_end; x++) { + int src_pixel = pixel24232(*src24++); + src_pixel = pixel24232(color_palette[COLOR_RGB888_TO_B8(src_pixel)]); + *dst24++ = pixel32224(BLEND_RGB888_0(src_pixel, smuad_alpha)); + } + } + } + } + } + break; + } + default: { + break; + } + } + break; + } + // Only bayer copying/cropping is supported. + case PIXFORMAT_BAYER_ANY: { + uint8_t *dst8 = (data->dst_row_override + ? ((uint8_t *) data->dst_row_override) + : IMAGE_COMPUTE_BAYER_PIXEL_ROW_PTR(data->dst_img, y_row)) + x_start; + uint8_t *src8 = ((uint8_t *) data->row_buffer[!data->toggle]) + x_start; + unaligned_memcpy(dst8, src8, (x_end - x_start) * sizeof(uint8_t)); + break; + } + // Only yuv422 copying/cropping is supported. + case PIXFORMAT_YUV_ANY: { + uint16_t *dst16 = (data->dst_row_override + ? ((uint16_t *) data->dst_row_override) + : IMAGE_COMPUTE_YUV_PIXEL_ROW_PTR(data->dst_img, y_row)) + x_start; + uint16_t *src16 = ((uint16_t *) data->row_buffer[!data->toggle]) + x_start; + unaligned_memcpy(dst16, src16, (x_end - x_start) * sizeof(uint16_t)); + break; + } + default: { + break; + } + } + + if (data->callback) { + ((imlib_draw_row_callback_t) data->callback)(x_start, x_end, y_row, data); + } + + #undef COLOR_GRAYSCALE_BINARY_MIN_LSL16 + #undef COLOR_GRAYSCALE_BINARY_MAX_LSL16 + #undef BLEND_RGB888_0 + #undef BLEND_RGB888 + #undef BLEND_RGB566_0 + #undef BLEND_RGB566 +} + +// False == Image is black, True == rect valid +//是lcd的函数,暂时可以不处理 +bool imlib_draw_image_rectangle(image_t *dst_img, image_t *src_img, int dst_x_start, int dst_y_start, float x_scale, float y_scale, rectangle_t *roi, + int alpha, const uint8_t *alpha_palette, image_hint_t hint, + int *x0, int *x1, int *y0, int *y1) +{ + if (!alpha) { + return false; + } + + if (alpha_palette) { + int i = 0; + while ((i < 256) && (!alpha_palette[i])) i++; + if (i == 256) { // zero alpha palette + return false; + } + } + + int src_width_scaled = fast_floorf(fast_fabsf(x_scale) * (roi ? roi->w : src_img->w)); + int src_height_scaled = fast_floorf(fast_fabsf(y_scale) * (roi ? roi->h : src_img->h)); + + // Center src if hint is set. + if (hint & IMAGE_HINT_CENTER) { + dst_x_start -= src_width_scaled / 2; + dst_y_start -= src_height_scaled / 2; + } + + // Clamp start x to image bounds. + int src_x_start = 0; + if (dst_x_start < 0) { + src_x_start -= dst_x_start; // this is an add because dst_x_start is negative + dst_x_start = 0; + } + + if (dst_x_start >= dst_img->w) { + return false; + } + + int src_x_dst_width = src_width_scaled - src_x_start; + + if (src_x_dst_width <= 0) { + return false; + } + + // Clamp start y to image bounds. + int src_y_start = 0; + if (dst_y_start < 0) { + src_y_start -= dst_y_start; // this is an add because dst_y_start is negative + dst_y_start = 0; + } + + if (dst_y_start >= dst_img->h) { + return false; + } + + int src_y_dst_height = src_height_scaled - src_y_start; + + if (src_y_dst_height <= 0) { + return false; + } + + // Clamp end x to image bounds. + int dst_x_end = dst_x_start + src_x_dst_width; + if (dst_x_end > dst_img->w) dst_x_end = dst_img->w; + + // Clamp end y to image bounds. + int dst_y_end = dst_y_start + src_y_dst_height; + if (dst_y_end > dst_img->h) dst_y_end = dst_img->h; + + *x0 = dst_x_start; + *x1 = dst_x_end; + *y0 = dst_y_start; + *y1 = dst_y_end; + + return true; +} + +void imlib_draw_image_rgb565(image_t *img, image_t *other, int x_off, int y_off, float x_scale, float y_scale, float alpha, image_t *mask) +{ + float over_xscale = IM_DIV(1.0, x_scale), over_yscale = IM_DIV(1.0f, y_scale); + + for (int y = 0, yy = fast_roundf(other->h * y_scale); y < yy; y++) { + int other_y = fast_roundf(y * over_yscale); + + for (int x = 0, xx = fast_roundf(other->w * x_scale); x < xx; x++) { + int other_x = fast_roundf(x * over_xscale); + + if ((!mask) || image_get_mask_pixel(mask, other_x, other_y)) { + int pixel = imlib_get_pixel(other, other_x, other_y); + if(alpha == 1) { + uint8_t r = COLOR_RGB565_TO_R8(pixel); + uint8_t g = COLOR_RGB565_TO_G8(pixel); + uint8_t b = COLOR_RGB565_TO_B8(pixel); + uint16_t c=COLOR_R8_G8_B8_TO_RGB565(r>255?255:r,g>255?255:g, b>255?255:b); + imlib_set_pixel(img, x_off + x, y_off + y, c); + } else { + int pixel2=imlib_get_pixel(img, x_off + x, y_off + y); + + uint8_t t_r = COLOR_RGB565_TO_R8(pixel); + uint8_t t_g = COLOR_RGB565_TO_G8(pixel); + uint8_t t_b = COLOR_RGB565_TO_B8(pixel); + + float tmp = ((uint16_t)(((t_r)) | (t_g) | (t_b)) / (256.0)); + + float t_alpha = alpha * tmp; // 0.01 ~ 0.00001 + + float beta = 1 - t_alpha; + uint16_t r=(uint16_t)(((float)(uint16_t)COLOR_RGB565_TO_R8(pixel))*t_alpha+((float)(uint16_t)COLOR_RGB565_TO_R8(pixel2))*beta); + uint16_t g=(uint16_t)(((float)(uint16_t)COLOR_RGB565_TO_G8(pixel))*t_alpha+((float)(uint16_t)COLOR_RGB565_TO_G8(pixel2))*beta); + uint16_t b=(uint16_t)(((float)(uint16_t)COLOR_RGB565_TO_B8(pixel))*t_alpha+((float)(uint16_t)COLOR_RGB565_TO_B8(pixel2))*beta); + uint16_t c=COLOR_R8_G8_B8_TO_RGB565(r>255?255:r,g>255?255:g,b>255?255:b); + imlib_set_pixel(img, x_off + x, y_off + y,c); + + } + } + } + } +} +/** + * @brief 快速贴图融合算法,贴图与被贴图像需要相同,使用临近插值算法 + * + * @param img 被贴图像 + * @param other 贴图图像 + * @param x_off x 轴起始位置 + * @param y_off y 轴起始位置 + * @param x_scale 缩放系数 + * @param y_scale 缩放系数 + * @param alpha 贴图融合系数 + * @param mask 掩码图像 + */ +void imlib_draw_image_fast(image_t *img, image_t *other, int x_off, int y_off, float x_scale, float y_scale, float alpha, image_t *mask) +{ + float over_xscale = IM_DIV(1.0, x_scale), over_yscale = IM_DIV(1.0f, y_scale); + int alpha_int = (int)(alpha * 2048), alpha_int_2 = 2048 - alpha_int; + int other_y, other_x; + + switch (img->pixfmt) + { + case PIXFORMAT_BINARY: { + for (int y = 0, yy = fast_roundf(other->h * y_scale); y < yy; y++) { + other_y = fast_roundf(y * over_yscale); + + for (int x = 0, xx = fast_roundf(other->w * x_scale); x < xx; x++) { + other_x = fast_roundf(x * over_xscale); + + if ((!mask) || image_get_mask_pixel(mask, other_x, other_y)) { + uint32_t pixel_32 = IMAGE_GET_BINARY_PIXEL(other, other_x, other_y); + if(alpha == 1) { + if ((0 <= (x_off + x)) && ((x_off + x) < img->w) && (0 <= (y_off + y)) && ((y_off + y) < img->h)) { + IMAGE_PUT_BINARY_PIXEL(img, (x_off + x), (y_off + y), pixel_32); + } + } else { + uint32_t pixel2_32 = IMAGE_GET_BINARY_PIXEL(img, (x_off + x), (y_off + y)); + pixel_32 = (uint32_t)(pixel_32 * alpha_int + pixel2_32 * alpha_int_2) >> 11; + pixel_32 = pixel_32 > 1024 ? COLOR_BINARY_MAX : COLOR_BINARY_MIN; + if ((0 <= (x_off + x)) && ((x_off + x) < img->w) && (0 <= (y_off + y)) && ((y_off + y) < img->h)) { + IMAGE_PUT_BINARY_PIXEL(img, (x_off + x), (y_off + y), pixel_32); + } + } + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = 0, yy = fast_roundf(other->h * y_scale); y < yy; y++) { + other_y = fast_roundf(y * over_yscale); + + for (int x = 0, xx = fast_roundf(other->w * x_scale); x < xx; x++) { + other_x = fast_roundf(x * over_xscale); + + if ((!mask) || image_get_mask_pixel(mask, other_x, other_y)) { + uint8_t pixel_8 = IMAGE_GET_GRAYSCALE_PIXEL(other, other_x, other_y); + if(alpha == 1) { + if ((0 <= (x_off + x)) && ((x_off + x) < img->w) && (0 <= (y_off + y)) && ((y_off + y) < img->h)) { + IMAGE_PUT_GRAYSCALE_PIXEL(img, (x_off + x), (y_off + y), pixel_8); + } + } else { + uint8_t pixel2_8 = IMAGE_GET_GRAYSCALE_PIXEL(img, (x_off + x), (y_off + y)); + pixel_8 = (uint32_t)(pixel_8 * alpha_int + pixel2_8 * alpha_int_2) >> 11; + if ((0 <= (x_off + x)) && ((x_off + x) < img->w) && (0 <= (y_off + y)) && ((y_off + y) < img->h)) { + IMAGE_PUT_GRAYSCALE_PIXEL(img, (x_off + x), (y_off + y), pixel_8); + } + } + } + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = 0, yy = fast_roundf(other->h * y_scale); y < yy; y++) { + other_y = fast_roundf(y * over_yscale); + + for (int x = 0, xx = fast_roundf(other->w * x_scale); x < xx; x++) { + other_x = fast_roundf(x * over_xscale); + + if ((!mask) || image_get_mask_pixel(mask, other_x, other_y)) { + uint16_t pixel_16 = IMAGE_GET_RGB565_PIXEL(other, other_x, other_y); + if(alpha == 1) { + if ((0 <= (x_off + x)) && ((x_off + x) < img->w) && (0 <= (y_off + y)) && ((y_off + y) < img->h)) { + IMAGE_PUT_RGB565_PIXEL(img, (x_off + x), (y_off + y), pixel_16); + } + } else { + uint16_t pixel2_16 = IMAGE_GET_RGB565_PIXEL(img, (x_off + x), (y_off + y)); + + uint8_t r = ((uint32_t)((uint8_t)COLOR_RGB565_TO_R8(pixel_16) * alpha_int + (uint8_t)COLOR_RGB565_TO_R8(pixel2_16) * alpha_int_2)) >> 11; + uint8_t g = ((uint32_t)((uint8_t)COLOR_RGB565_TO_G8(pixel_16) * alpha_int + (uint8_t)COLOR_RGB565_TO_G8(pixel2_16) * alpha_int_2)) >> 11; + uint8_t b = ((uint32_t)((uint8_t)COLOR_RGB565_TO_B8(pixel_16) * alpha_int + (uint8_t)COLOR_RGB565_TO_B8(pixel2_16) * alpha_int_2)) >> 11; + + pixel_16 = COLOR_R8_G8_B8_TO_RGB565(r, g, b); + if ((0 <= (x_off + x)) && ((x_off + x) < img->w) && (0 <= (y_off + y)) && ((y_off + y) < img->h)) { + IMAGE_PUT_RGB565_PIXEL(img, (x_off + x), (y_off + y), pixel_16); + } + } + } + } + } + break; + } + case PIXFORMAT_RGB888: { + for (int y = 0, yy = fast_roundf(other->h * y_scale); y < yy; y++) { + other_y = fast_roundf(y * over_yscale); + + for (int x = 0, xx = fast_roundf(other->w * x_scale); x < xx; x++) { + other_x = fast_roundf(x * over_xscale); + + if ((!mask) || image_get_mask_pixel(mask, other_x, other_y)) { + pixel24_t pixel_24 = IMAGE_GET_RGB888_PIXEL_(other, other_x, other_y); + if(alpha == 1) { + if ((0 <= (x_off + x)) && ((x_off + x) < img->w) && (0 <= (y_off + y)) && ((y_off + y) < img->h)) { + IMAGE_PUT_RGB888_PIXEL_(img, (x_off + x), (y_off + y), pixel_24); + } + } else { + pixel24_t pixel2_24 = IMAGE_GET_RGB888_PIXEL_(img, (x_off + x), (y_off + y)); + + pixel_24.red = ((uint32_t)(pixel_24.red * alpha_int + pixel2_24.red * alpha_int_2)) >> 11; + pixel_24.green = ((uint32_t)(pixel_24.green * alpha_int + pixel2_24.green * alpha_int_2)) >> 11; + pixel_24.blue = ((uint32_t)(pixel_24.blue * alpha_int + pixel2_24.blue * alpha_int_2)) >> 11; + + if ((0 <= (x_off + x)) && ((x_off + x) < img->w) && (0 <= (y_off + y)) && ((y_off + y) < img->h)) { + IMAGE_PUT_RGB888_PIXEL_(img, (x_off + x), (y_off + y), pixel_24); + } + } + } + } + } + break; + } + default: { + break; + } + } +} + +/** + * @brief 绘制一个 image ,其左上角从位置x,y开始。 您可以单独传递x,y,也可以传递给元组(x,y)。 + * 这种方法非常灵活,不需要在目标图像上绘制的图像具有相同的宽度/高度/或像素格式(灰度/RGB565)。 该方法还可以自动处理从目标图像边缘绘制的裁剪像素。 + * + * @param dst_img 目标图像 + * @param src_img 传入图像 + * @param dst_x_start 开始的x位置 + * @param dst_y_start 开始的y位置 + * @param x_scale 是一个浮点值,通过它可以在x方向上缩放图像。如果此值为负,则图像将水平翻转。 + * @param y_scale 是一个浮点值,通过它可以在y方向上缩放图像。如果此值为负,则图像将垂直翻转。 + * @param roi 是要绘制的源图像的感兴趣区域矩形元组 (x, y, w, h)。 这允许您仅提取 ROI 中的像素以在目标图像上进行缩放和绘制。 + * @param rgb_channel 是从 RGB565 图像(如果传递)中提取并渲染到目标图像上的 RGB 通道(0=R,G=1,B=2)。 + * 例如,如果您传递 rgb_channel=1 这将提取源 RGB565 图像的绿色通道并在目标图像上以灰度方式绘制该通道。 + * @param alpha 控制另一幅图像与这幅图像的混合程度。 alpha 应该是0到256之间的一个整数值。接近零的值会将更多其他图像混合到该图像中,而接近256的值则相反。 + * @param color_palette 如果不是 -1 可以是 sensor.PALETTE_RAINBOW、sensor.PALETTE_IRONBOW,或总共 256 像素的 RGB565 图像,用作任何源图像的灰度值的颜色查找表。 如果使用,这将在 rgb_channel 提取之后应用。 + * @param alpha_palette 如果不是 -1 可以是总共 256 像素的 GRAYSCALE 图像,用作 alpha 调色板, 以像素级别调制正在绘制的源图像的``alpha``值,允许您根据像素的灰度值精确控制像素的 alpha 值。 alpha 查找表中的 255 像素值是不透明的,任何小于 255 的像素值都会变成更透明,直到 0。 如果使用,这将在 rgb_channel 提取之后应用。 + * @param hint 可以是标志的OR逻辑: + * image.AREA: 与最近邻的默认值相比,在缩小时使用区域缩放。 + * image.BILINEAR: 使用双线性缩放而不是最近邻缩放的默认值。 + * image.BICUBIC: 使用双三次缩放而不是最近邻缩放的默认值。 + * image.CENTER: 将要绘制的图像图像居中 (x, y)。 + * image.EXTRACT_RGB_CHANNEL_FIRST: 在缩放之前进行 rgb_channel 提取。 + * image.APPLY_COLOR_PALETTE_FIRST: 在缩放之前应用调色板。 + * image.BLACK_BACKGROUND: 假设目标图像是黑色的。 这加快了绘图速度。 + * + * @param callback 回调函数 + * @param dst_row_override 回调参数 + */ +void imlib_draw_image(image_t *dst_img, image_t *src_img, int dst_x_start, int dst_y_start, + float x_scale, float y_scale, rectangle_t *roi,int rgb_channel, int alpha, const uint16_t *color_palette, + const uint8_t *alpha_palette, image_hint_t hint, imlib_draw_row_callback_t callback, void *dst_row_override) +{ + int dst_delta_x = 1; // positive direction + if (x_scale < 0.f) { // flip X + dst_delta_x = -1; + x_scale = -x_scale; + } + + int dst_delta_y = 1; // positive direction + if (y_scale < 0.f) { // flip Y + dst_delta_y = -1; + y_scale = -y_scale; + } + + int src_img_w = roi ? roi->w : src_img->w, w_limit = src_img_w - 1, w_limit_m_1 = w_limit - 1; + int src_img_h = roi ? roi->h : src_img->h, h_limit = src_img_h - 1, h_limit_m_1 = h_limit - 1; + + int src_width_scaled = fast_floorf(x_scale * src_img_w); + int src_height_scaled = fast_floorf(y_scale * src_img_h); + + // Nothing to draw + if ((src_width_scaled < 1) || (src_height_scaled < 1)) return; + + // If alpha is 0 then nothing changes. + if (alpha == 0) return; + + if (alpha_palette) { + int i = 0; + while ((i < 256) && (!alpha_palette[i])) i++; + if (i == 256) return; // zero alpha palette + } + + // Center src if hint is set. + if (hint & IMAGE_HINT_CENTER) { + dst_x_start -= src_width_scaled / 2; + dst_y_start -= src_height_scaled / 2; + } + + // Clamp start x to image bounds. + int src_x_start = 0; + if (dst_x_start < 0) { + src_x_start -= dst_x_start; // this is an add becasue dst_x_start is negative + dst_x_start = 0; + } + + if (dst_x_start >= dst_img->w) return; + int src_x_dst_width = src_width_scaled - src_x_start; + if (src_x_dst_width <= 0) return; + + // Clamp start y to image bounds. + int src_y_start = 0; + if (dst_y_start < 0) { + src_y_start -= dst_y_start; // this is an add becasue dst_y_start is negative + dst_y_start = 0; + } + + if (dst_y_start >= dst_img->h) return; + int src_y_dst_height = src_height_scaled - src_y_start; + if (src_y_dst_height <= 0) return; + + // Clamp end x to image bounds. + int dst_x_end = dst_x_start + src_x_dst_width; + if (dst_x_end > dst_img->w) dst_x_end = dst_img->w; + + // Clamp end y to image bounds. + int dst_y_end = dst_y_start + src_y_dst_height; + if (dst_y_end > dst_img->h) dst_y_end = dst_img->h; + + if (dst_delta_x < 0) { + // Since we are drawing backwards we have to slide our drawing offset forward by an amount + // limited by the size of the drawing area left. E.g. when we hit the right edge we have + // advance the offset to prevent the image from sliding. + int allowed_offset_width = src_width_scaled - (dst_x_end - dst_x_start); + src_x_start = IM_MIN(dst_x_start, allowed_offset_width); + } + + // Apply roi offset + if (roi) src_x_start += fast_floorf(roi->x * x_scale); + + if (dst_delta_y < 0) { + // Since we are drawing backwards we have to slide our drawing offset forward by an amount + // limited by the size of the drawing area left. E.g. when we hit the bottom edge we have + // advance the offset to prevent the image from sliding. + int allowed_offset_height = src_height_scaled - (dst_y_end - dst_y_start); + src_y_start = IM_MIN(dst_y_start, allowed_offset_height); + } + + // Apply roi offset + if (roi) src_y_start += fast_floorf(roi->y * y_scale); + + // For all of the scaling algorithms (nearest neighbor, bilinear, bicubic, and area) + // we use a 32-bit fraction instead of a floating point value for iteration. Below, + // we calculate an increment which fits in 32-bits. We can then add this value + // successively as we loop over the destination pixels and then shift this sum + // right by 16 to get the corresponding source pixel. If we want the fractional + // position we just have to look at the bottom 16-bits. + // + // top 16-bits = whole part, bottom 16-bits = fractional part. + + int dst_x_reset = (dst_delta_x < 0) ? (dst_x_end - 1) : dst_x_start; + long src_x_frac = fast_floorf(65536.0f / x_scale), src_x_frac_size = (src_x_frac + 0xFFFF) >> 16; + long src_x_accum_reset = fast_floorf((src_x_start << 16) / x_scale); + + int dst_y_reset = (dst_delta_y < 0) ? (dst_y_end - 1) : dst_y_start; + long src_y_frac = fast_floorf(65536.0f / y_scale), src_y_frac_size = (src_y_frac + 0xFFFF) >> 16; + long src_y_accum_reset = fast_floorf((src_y_start << 16) / y_scale); + + // Nearest Neighbor + if ((src_x_frac == 65536) && (src_y_frac == 65536)) hint &= ~(IMAGE_HINT_AREA | IMAGE_HINT_BICUBIC | IMAGE_HINT_BILINEAR); + + // Nearest Neighbor + if ((hint & IMAGE_HINT_AREA) && (x_scale >= 1.f) && (y_scale >= 1.f)) hint &= ~(IMAGE_HINT_AREA | IMAGE_HINT_BICUBIC | IMAGE_HINT_BILINEAR); + + // Cannot interpolate. + if ((src_img_w <= 3) || (src_img_h <= 3)) { + if (hint & IMAGE_HINT_BICUBIC) hint |= IMAGE_HINT_BILINEAR; + hint &= ~IMAGE_HINT_BICUBIC; + } + + // Cannot interpolate. + if ((src_img_w <= 1) || (src_img_h <= 1)) hint &= ~(IMAGE_HINT_AREA | IMAGE_HINT_BILINEAR); + + // Bicbuic and bilinear both shift the image right by (0.5, 0.5) so we have to undo that. + if (hint & (IMAGE_HINT_BICUBIC | IMAGE_HINT_BILINEAR)) { + src_x_accum_reset -= 0x8000; + src_y_accum_reset -= 0x8000; + } + + // rgb_channel extracted / color_palette applied image + image_t new_src_img; + + if (((hint & IMAGE_HINT_EXTRACT_RGB_CHANNEL_FIRST) && (rgb_channel != -1) && src_img->is_color) + || ((hint & IMAGE_HINT_APPLY_COLOR_PALETTE_FIRST) && color_palette)) { + new_src_img.w = src_img_w; // same width as source image + new_src_img.h = src_img_h; // same height as source image + if(color_palette){ + if(src_img->pixfmt == PIXFORMAT_RGB888){ + new_src_img.pixfmt = PIXFORMAT_RGB888; + }else{ + new_src_img.pixfmt = PIXFORMAT_RGB565; + } + }else{ + new_src_img.pixfmt = PIXFORMAT_GRAYSCALE; + } + new_src_img.data = fb_alloc(image_size(&new_src_img), FB_ALLOC_NO_HINT); + imlib_draw_image(&new_src_img, src_img, 0, 0, 1.f, 1.f, NULL, rgb_channel, 256, color_palette, NULL, 0, NULL, NULL); + src_img = &new_src_img; + rgb_channel = -1; + color_palette = NULL; + } + + // Special destination? + bool is_jpeg = src_img->pixfmt == PIXFORMAT_JPEG; + bool is_png = src_img->pixfmt == PIXFORMAT_PNG; + int new_not_mutable_pixfmt = 0; + // Best format to convert yuv/bayer/jpeg image to. + // int new_not_mutable_pixfmt = (rgb_channel != -1) ? PIXFORMAT_RGB565 : + // (color_palette ? PIXFORMAT_GRAYSCALE : + // dst_img->pixfmt); + if(rgb_channel != -1) + { + if(src_img->pixfmt == PIXFORMAT_RGB888) + { + new_not_mutable_pixfmt = PIXFORMAT_RGB888; + }else{ + new_not_mutable_pixfmt = PIXFORMAT_RGB565; + } + }else{ + if(color_palette) + { + new_not_mutable_pixfmt = PIXFORMAT_GRAYSCALE; + }else{ + new_not_mutable_pixfmt = dst_img->pixfmt; + } + } + + bool no_scaling_nearest_neighbor = (dst_delta_x == 1) + && (dst_x_start == 0) && (src_x_start == 0) + && (src_x_frac == 65536) && (src_y_frac == 65536); + + // If we are scaling just make a deep copy. + bool is_scaling = (hint & (IMAGE_HINT_AREA | IMAGE_HINT_BICUBIC | IMAGE_HINT_BILINEAR)) + || (!no_scaling_nearest_neighbor); + + // Otherwise, we only have to do a deep copy if the image is growing. + size_t src_img_row_bytes = image_size(src_img) / src_img->h; + size_t dst_img_row_bytes = image_size(dst_img) / dst_img->h; + + // Do we need to convert the image? + bool is_bayer_color_conversion = src_img->is_bayer && !dst_img->is_bayer; + bool is_yuv_color_conversion = src_img->is_yuv && !dst_img->is_yuv; + bool is_color_conversion = is_bayer_color_conversion || is_yuv_color_conversion; + + // Force a deep copy if we cannot use the image in-place. + bool need_deep_copy = (dst_img->data == src_img->data) + && (is_scaling || (src_img_row_bytes < dst_img_row_bytes) || is_color_conversion); + + // Force a deep copy if we are scaling. + bool is_color_conversion_scaling = is_color_conversion && is_scaling; + + // Make a deep copy of the source image. + if (need_deep_copy || is_color_conversion_scaling || is_jpeg || is_png) { + new_src_img.w = src_img->w; // same width as source image + new_src_img.h = src_img->h; // same height as source image + + if (!src_img->is_mutable) { + new_src_img.pixfmt = new_not_mutable_pixfmt; + size_t size = image_size(&new_src_img); + new_src_img.data = fb_alloc(size, FB_ALLOC_NO_HINT); + + switch (new_src_img.pixfmt) { + case PIXFORMAT_BINARY: { + if (src_img->is_bayer) { + imlib_debayer_image(&new_src_img, src_img); + } else if (src_img->is_yuv) { + imlib_deyuv_image(&new_src_img, src_img); + } else if (is_jpeg) { + jpeg_decompress(&new_src_img, src_img); + } else if (is_png) { + png_decompress(&new_src_img, src_img); + } + break; + } + case PIXFORMAT_GRAYSCALE: { + if (src_img->is_bayer) { + imlib_debayer_image(&new_src_img, src_img); + } else if (src_img->is_yuv) { + imlib_deyuv_image(&new_src_img, src_img); + } else if (is_jpeg) { + jpeg_decompress(&new_src_img, src_img); + } else if (is_png) { + png_decompress(&new_src_img, src_img); + } + break; + } + case PIXFORMAT_RGB565: { + if (src_img->is_bayer) { + imlib_debayer_image(&new_src_img, src_img); + } else if (src_img->is_yuv) { + imlib_deyuv_image(&new_src_img, src_img); + } else if (is_jpeg) { + jpeg_decompress(&new_src_img, src_img); + } else if (is_png) { + png_decompress(&new_src_img, src_img); + } + break; + } + case PIXFORMAT_RGB888: { + if (src_img->is_bayer) { + imlib_debayer_image(&new_src_img, src_img); + } else if (src_img->is_yuv) { + imlib_deyuv_image(&new_src_img, src_img); + } else if (is_jpeg) { + jpeg_decompress(&new_src_img, src_img); + } else if (is_png) { + png_decompress(&new_src_img, src_img); + } + break; + } + case PIXFORMAT_BAYER_ANY: + case PIXFORMAT_YUV_ANY: { + memcpy(new_src_img.data, src_img->data, size); + break; + } + default : { + if (is_png) { + png_decompress(&new_src_img, src_img); + } + break; + } + } + } else { + new_src_img.pixfmt = src_img->pixfmt; + size_t size = image_size(&new_src_img); + new_src_img.data = fb_alloc(size, FB_ALLOC_NO_HINT); + memcpy(new_src_img.data, src_img->data, size); + } + + src_img = &new_src_img; + } + + imlib_draw_row_data_t imlib_draw_row_data; + imlib_draw_row_data.dst_img = dst_img; + imlib_draw_row_data.src_img_pixfmt = (!src_img->is_mutable) ? new_not_mutable_pixfmt : src_img->pixfmt; + imlib_draw_row_data.rgb_channel = rgb_channel; + imlib_draw_row_data.alpha = alpha; + imlib_draw_row_data.color_palette = color_palette; + imlib_draw_row_data.alpha_palette = alpha_palette; + imlib_draw_row_data.black_background = hint & IMAGE_HINT_BLACK_BACKGROUND; + imlib_draw_row_data.callback = callback; + imlib_draw_row_data.dst_row_override = dst_row_override; + #ifdef IMLIB_ENABLE_DMA2D + imlib_draw_row_data.dma2d_request = (alpha != 256) || alpha_palette || + (hint & (IMAGE_HINT_AREA | IMAGE_HINT_BICUBIC | IMAGE_HINT_BILINEAR)); + #endif + + imlib_draw_row_setup(&imlib_draw_row_data); + + // Y loop iteration variables + int dst_y = dst_y_reset; + long src_y_accum = src_y_accum_reset; + int next_src_y_index = src_y_accum >> 16; + int y = dst_y_start; + bool y_not_done = y < dst_y_end; + + if (hint & IMAGE_HINT_AREA) { + // The area scaling algorithm runs in fast mode if the image is being scaled down by + // 1, 2, 3, 4, 5, etc. or slow mode if it's a fractional scale. + + // In fast mode area scaling is just the sum of the specified area. No weighting of pixels + // is required to get the job done. + + // In slow mode we need to weight pixels that lie on the edges of the area scale rectangle. + // This prevents making the inner loop of the algorithm tight. + // + if ((!(src_x_frac & 0xFFFF)) && (!(src_y_frac & 0xFFFF))) { // fast + switch (src_img->pixfmt) { + case PIXFORMAT_BINARY: { + while (y_not_done) { + int src_y_index = next_src_y_index; + int src_y_index_end = src_y_index + src_y_frac_size; + if (src_y_index_end > src_img_h) src_y_index_end = src_img_h; + int height = src_y_index_end - src_y_index; + + // Must be called per loop to get the address of the temp buffer to blend with + uint32_t *dst_row_ptr = (uint32_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int src_x_index_end = src_x_index + src_x_frac_size; + if (src_x_index_end > src_img_w) src_x_index_end = src_img_w; + int width = src_x_index_end - src_x_index; + + uint32_t area = width * height; + uint32_t acc = 0; + + for (int i = src_y_index; i < src_y_index_end; i++) { + uint32_t *src_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, i); + for (int j = src_x_index; j < src_x_index_end; j++) { + acc += IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, j); + } + } + + int pixel = (acc + (area >> 1)) / area; + + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_GRAYSCALE: { + while (y_not_done) { + int src_y_index = next_src_y_index; + int src_y_index_end = src_y_index + src_y_frac_size; + if (src_y_index_end > src_img_h) src_y_index_end = src_img_h; + int height = src_y_index_end - src_y_index; + + // Must be called per loop to get the address of the temp buffer to blend with + uint8_t *dst_row_ptr = (uint8_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int src_x_index_end = src_x_index + src_x_frac_size; + if (src_x_index_end > src_img_w) src_x_index_end = src_img_w; + int width = src_x_index_end - src_x_index; + + uint32_t area = width * height; + uint32_t acc = 0; + + if (width < 4) { + for (int i = src_y_index; i < src_y_index_end; i++) { + uint8_t *src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, i) + src_x_index; + int n = width; +#if defined(ARM_MATH_DSP) + uint16_t *src_row_ptr16 = (uint16_t *) src_row_ptr; + + for (; n > 1; n -= 2) { + uint16_t pixels = *src_row_ptr16++; + acc = __USADA8(pixels, 0, acc); + } + + src_row_ptr = (uint8_t *) src_row_ptr16; +#endif + for (; n > 0; n -= 1) { + acc += *src_row_ptr++; + } + } + } else { + for (int i = src_y_index; i < src_y_index_end; i++) { + uint8_t *src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, i) + src_x_index; + int n = width; +#if defined(ARM_MATH_DSP) + uint32_t *src_row_ptr32 = (uint32_t *) src_row_ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *src_row_ptr32++; + acc = __USADA8(pixels, 0, acc); + } + + src_row_ptr = (uint8_t *) src_row_ptr32; +#endif + for (; n > 0; n -= 1) { + acc += *src_row_ptr++; + } + } + } + + int pixel = (acc + (area >> 1)) / area; + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_RGB565: { + while (y_not_done) { + int src_y_index = next_src_y_index; + int src_y_index_end = src_y_index + src_y_frac_size; + if (src_y_index_end > src_img_h) src_y_index_end = src_img_h; + int height = src_y_index_end - src_y_index; + + // Must be called per loop to get the address of the temp buffer to blend with + uint16_t *dst_row_ptr = (uint16_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int src_x_index_end = src_x_index + src_x_frac_size; + if (src_x_index_end > src_img_w) src_x_index_end = src_img_w; + int width = src_x_index_end - src_x_index; + + uint32_t area = width * height; + uint32_t r_acc = 0, g_acc = 0, b_acc = 0; + + for (int i = src_y_index; i < src_y_index_end; i++) { + uint16_t *src_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, i) + src_x_index; + int n = width; +#if defined(ARM_MATH_DSP) + uint32_t *src_row_ptr32 = (uint32_t *) src_row_ptr; + + for (; n > 1; n -= 2) { + uint32_t pixels = *src_row_ptr32++; + + long r = (pixels >> 11) & 0x1F001F; + r_acc = __USADA8(r, 0, r_acc); + + long g = (pixels >> 5) & 0x3F003F; + g_acc = __USADA8(g, 0, g_acc); + + long b = pixels & 0x1F001F; + b_acc = __USADA8(b, 0, b_acc); + } + + src_row_ptr = (uint16_t *) src_row_ptr32; +#endif + for (; n > 0; n -= 1) { + int pixel = *src_row_ptr++; + r_acc += COLOR_RGB565_TO_R5(pixel); + g_acc += COLOR_RGB565_TO_G6(pixel); + b_acc += COLOR_RGB565_TO_B5(pixel); + } + } + + r_acc = (r_acc + (area >> 1)) / area; + g_acc = (g_acc + (area >> 1)) / area; + b_acc = (b_acc + (area >> 1)) / area; + + int pixel = COLOR_R5_G6_B5_TO_RGB565(r_acc, g_acc, b_acc); + + IMAGE_PUT_RGB565_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_RGB888: { + while (y_not_done) { + int src_y_index = next_src_y_index; + int src_y_index_end = src_y_index + src_y_frac_size; + if (src_y_index_end > src_img_h) src_y_index_end = src_img_h; + int height = src_y_index_end - src_y_index; + + // Must be called per loop to get the address of the temp buffer to blend with + pixel24_t *dst_row_ptr = (pixel24_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int src_x_index_end = src_x_index + src_x_frac_size; + if (src_x_index_end > src_img_w) src_x_index_end = src_img_w; + int width = src_x_index_end - src_x_index; + + uint32_t area = width * height; + uint32_t r_acc = 0, g_acc = 0, b_acc = 0; + + for (int i = src_y_index; i < src_y_index_end; i++) { + pixel24_t *src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, i) + src_x_index; + int n = width; +#if defined(ARM_MATH_DSP) + uint32_t *src_row_ptr32 = (uint32_t *) src_row_ptr; + + for (; n > 1; n -= 2) { + uint32_t pixels = *src_row_ptr32++; + + long r = (pixels >> 11) & 0x1F001F; + r_acc = __USADA8(r, 0, r_acc); + + long g = (pixels >> 5) & 0x3F003F; + g_acc = __USADA8(g, 0, g_acc); + + long b = pixels & 0x1F001F; + b_acc = __USADA8(b, 0, b_acc); + } + + src_row_ptr = (uint16_t *) src_row_ptr32; +#endif + for (; n > 0; n -= 1) { + int pixel = pixel24232(*src_row_ptr++); + r_acc += COLOR_RGB888_TO_R8(pixel); + g_acc += COLOR_RGB888_TO_G8(pixel); + b_acc += COLOR_RGB888_TO_B8(pixel); + } + } + + r_acc = (r_acc + (area >> 1)) / area; + g_acc = (g_acc + (area >> 1)) / area; + b_acc = (b_acc + (area >> 1)) / area; + + int pixel = COLOR_R8_G8_B8_TO_RGB888(r_acc, g_acc, b_acc); + + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + default: { + break; + } + } + } else { // slow + switch (src_img->pixfmt) { + case PIXFORMAT_BINARY: { + while (y_not_done) { + int src_y_index = next_src_y_index, src_y_index_p_1 = src_y_index + 1; + int src_y_index_end = src_y_index + src_y_frac_size - 1; // inclusive end + + int t_y_weight = 256 - ((src_y_accum >> 8) & 0xFF); + int b_y_weight = ((src_y_accum + src_y_frac) >> 8) & 0xFF; + // Since src_y_index_end is inclusive this should be 256 when there's perfect overlap. + if (!b_y_weight) b_y_weight = 256; + + // Handle end being off the edge. + if (src_y_index_end > h_limit) { + src_y_index_end = h_limit; + // Either we don't need end of we chopped off the last part. + if (src_y_index_end == src_y_index) b_y_weight = 0; + else b_y_weight = 256; // max out if we chopped off + } + + int y_height_m_2 = src_y_index_end - src_y_index - 1; + + uint32_t *t_src_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, src_y_index); + uint32_t *b_src_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, src_y_index_end); + + // Must be called per loop to get the address of the temp buffer to blend with + uint32_t *dst_row_ptr = (uint32_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index, src_x_index_p_1 = src_x_index + 1; + int src_x_index_end = src_x_index + src_x_frac_size - 1; // inclusive end + + int l_x_weight = 256 - ((src_x_accum >> 8) & 0xFF); + int r_x_weight = ((src_x_accum + src_x_frac) >> 8) & 0xFF; + // Since src_x_index_end is inclusive this should be 256 when there's perfect overlap. + if (!r_x_weight) r_x_weight = 256; + + // Handle end being off the edge. + if (src_x_index_end > w_limit) { + src_x_index_end = w_limit; + // Either we don't need end of we chopped off the last part. + if (src_x_index_end == src_x_index) r_x_weight = 0; + else r_x_weight = 256; // max out if we chopped off + } + + int x_width_m_2 = src_x_index_end - src_x_index - 1; + + int t_l_weight = t_y_weight * l_x_weight; + int t_r_weight = t_y_weight * r_x_weight; + int b_l_weight = b_y_weight * l_x_weight; + int b_r_weight = b_y_weight * r_x_weight; + + uint32_t area = t_l_weight + t_r_weight + b_l_weight + b_r_weight; + uint32_t acc = 0; + + // sum corners + + acc += IMAGE_GET_BINARY_PIXEL_FAST(t_src_row_ptr, src_x_index) * t_l_weight; + acc += IMAGE_GET_BINARY_PIXEL_FAST(t_src_row_ptr, src_x_index_end) * t_r_weight; + acc += IMAGE_GET_BINARY_PIXEL_FAST(b_src_row_ptr, src_x_index) * b_l_weight; + acc += IMAGE_GET_BINARY_PIXEL_FAST(b_src_row_ptr, src_x_index_end) * b_r_weight; + + area = (area + 255) >> 8; + acc = (acc + 128) >> 8; + + if (x_width_m_2 > 0) { // sum top/bot + area += x_width_m_2 * (t_y_weight + b_y_weight); + for (int i = src_x_index_p_1; i < src_x_index_end; i++) { + acc += IMAGE_GET_BINARY_PIXEL_FAST(t_src_row_ptr, i) * t_y_weight; + acc += IMAGE_GET_BINARY_PIXEL_FAST(b_src_row_ptr, i) * b_y_weight; + } + } + + if (y_height_m_2 > 0) { // sum left/right + area += y_height_m_2 * (l_x_weight + r_x_weight); + for (int i = src_y_index_p_1; i < src_y_index_end; i++) { + uint32_t *src_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, i); + acc += IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, src_x_index) * l_x_weight; + acc += IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, src_x_index_end) * r_x_weight; + } + } + + area = (area + 255) >> 8; + acc = (acc + 128) >> 8; + + if ((x_width_m_2 > 0) && (y_height_m_2 > 0)) { // sum middle + area += x_width_m_2 * y_height_m_2; + for (int i = src_y_index_p_1; i < src_y_index_end; i++) { + uint32_t *src_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, i); + for (int j = src_x_index_p_1; j < src_x_index_end; j++) { + acc += IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, j); + } + } + } + + int pixel = (acc + (area >> 1)) / area; + + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_GRAYSCALE: { + while (y_not_done) { + int src_y_index = next_src_y_index, src_y_index_p_1 = src_y_index + 1; + int src_y_index_end = src_y_index + src_y_frac_size - 1; // inclusive end + + int t_y_weight = 256 - ((src_y_accum >> 8) & 0xFF); + int b_y_weight = ((src_y_accum + src_y_frac) >> 8) & 0xFF; + // Since src_y_index_end is inclusive this should be 256 when there's perfect overlap. + if (!b_y_weight) b_y_weight = 256; + + // Handle end being off the edge. + if (src_y_index_end > h_limit) { + src_y_index_end = h_limit; + // Either we don't need end of we chopped off the last part. + if (src_y_index_end == src_y_index) b_y_weight = 0; + else b_y_weight = 256; // max out if we chopped off + } + + int y_height_m_2 = src_y_index_end - src_y_index - 1; + + uint8_t *t_src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, src_y_index); + uint8_t *b_src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, src_y_index_end); + + // Must be called per loop to get the address of the temp buffer to blend with + uint8_t *dst_row_ptr = (uint8_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index, src_x_index_p_1 = src_x_index + 1; + int src_x_index_end = src_x_index + src_x_frac_size - 1; // inclusive end + + int l_x_weight = 256 - ((src_x_accum >> 8) & 0xFF); + int r_x_weight = ((src_x_accum + src_x_frac) >> 8) & 0xFF; + // Since src_x_index_end is inclusive this should be 256 when there's perfect overlap. + if (!r_x_weight) r_x_weight = 256; + + // Handle end being off the edge. + if (src_x_index_end > w_limit) { + src_x_index_end = w_limit; + // Either we don't need end of we chopped off the last part. + if (src_x_index_end == src_x_index) r_x_weight = 0; + else r_x_weight = 256; // max out if we chopped off + } + + int x_width_m_2 = src_x_index_end - src_x_index - 1; + + int t_l_weight = t_y_weight * l_x_weight; + int t_r_weight = t_y_weight * r_x_weight; + int b_l_weight = b_y_weight * l_x_weight; + int b_r_weight = b_y_weight * r_x_weight; + + uint32_t area = t_l_weight + t_r_weight + b_l_weight + b_r_weight; + uint32_t acc = 0; + + // sum corners + + acc += IMAGE_GET_GRAYSCALE_PIXEL_FAST(t_src_row_ptr, src_x_index) * t_l_weight; + acc += IMAGE_GET_GRAYSCALE_PIXEL_FAST(t_src_row_ptr, src_x_index_end) * t_r_weight; + acc += IMAGE_GET_GRAYSCALE_PIXEL_FAST(b_src_row_ptr, src_x_index) * b_l_weight; + acc += IMAGE_GET_GRAYSCALE_PIXEL_FAST(b_src_row_ptr, src_x_index_end) * b_r_weight; + + area = (area + 255) >> 8; + acc = (acc + 128) >> 8; + + if (x_width_m_2 > 0) { // sum top/bot + area += x_width_m_2 * (t_y_weight + b_y_weight); + uint8_t *t_src_row_ptr_tmp = t_src_row_ptr + src_x_index_p_1; + uint8_t *b_src_row_ptr_tmp = b_src_row_ptr + src_x_index_p_1; + for (int i = src_x_index_p_1; i < src_x_index_end; i++) { + acc += *t_src_row_ptr_tmp++ * t_y_weight; + acc += *b_src_row_ptr_tmp++ * b_y_weight; + } + } + + if (y_height_m_2 > 0) { // sum left/right + area += y_height_m_2 * (l_x_weight + r_x_weight); + for (int i = src_y_index_p_1; i < src_y_index_end; i++) { + uint8_t *src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, i); + acc += IMAGE_GET_GRAYSCALE_PIXEL_FAST(src_row_ptr, src_x_index) * l_x_weight; + acc += IMAGE_GET_GRAYSCALE_PIXEL_FAST(src_row_ptr, src_x_index_end) * r_x_weight; + } + } + + area = (area + 255) >> 8; + acc = (acc + 128) >> 8; + + if ((x_width_m_2 > 0) && (y_height_m_2 > 0)) { // sum middle + area += x_width_m_2 * y_height_m_2; + if (x_width_m_2 < 4) { + for (int i = src_y_index_p_1; i < src_y_index_end; i++) { + uint8_t *src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, i) + src_x_index_p_1; + int n = x_width_m_2; +#if defined(ARM_MATH_DSP) + uint16_t *src_row_ptr16 = (uint16_t *) src_row_ptr; + + for (; n > 1; n -= 2) { + uint16_t pixels = *src_row_ptr16++; + acc = __USADA8(pixels, 0, acc); + } + + src_row_ptr = (uint8_t *) src_row_ptr16; +#endif + for (; n > 0; n -= 1) { + acc += *src_row_ptr++; + } + } + } else { + for (int i = src_y_index_p_1; i < src_y_index_end; i++) { + uint8_t *src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, i) + src_x_index_p_1; + int n = x_width_m_2; +#if defined(ARM_MATH_DSP) + uint32_t *src_row_ptr32 = (uint32_t *) src_row_ptr; + + for (; n > 4; n -= 4) { + uint32_t pixels = *src_row_ptr32++; + acc = __USADA8(pixels, 0, acc); + } + + src_row_ptr = (uint8_t *) src_row_ptr32; +#endif + for (; n > 0; n -= 1) { + acc += *src_row_ptr++; + } + } + } + } + + int pixel = (acc + (area >> 1)) / area; + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_RGB565: { + while (y_not_done) { + int src_y_index = next_src_y_index, src_y_index_p_1 = src_y_index + 1; + int src_y_index_end = src_y_index + src_y_frac_size - 1; // inclusive end + + int t_y_weight = 128 - ((src_y_accum >> 9) & 0x7F); + int b_y_weight = ((src_y_accum + src_y_frac) >> 9) & 0x7F; + // Since src_y_index_end is inclusive this should be 128 when there's perfect overlap. + if (!b_y_weight) b_y_weight = 128; + + // Handle end being off the edge. + if (src_y_index_end > h_limit) { + src_y_index_end = h_limit; + // Either we don't need end of we chopped off the last part. + if (src_y_index_end == src_y_index) b_y_weight = 0; + else b_y_weight = 128; // max out if we chopped off + } + + int y_height_m_2 = src_y_index_end - src_y_index - 1; + long smlad_y_weight = (t_y_weight << 16) | b_y_weight; + + uint16_t *t_src_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, src_y_index); + uint16_t *b_src_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, src_y_index_end); + + // Must be called per loop to get the address of the temp buffer to blend with + uint16_t *dst_row_ptr = (uint16_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index, src_x_index_p_1 = src_x_index + 1; + int src_x_index_end = src_x_index + src_x_frac_size - 1; // inclusive end + + int l_x_weight = 128 - ((src_x_accum >> 9) & 0x7F); + int r_x_weight = ((src_x_accum + src_x_frac) >> 9) & 0x7F; + // Since src_x_index_end is inclusive this should be 128 when there's perfect overlap. + if (!r_x_weight) r_x_weight = 128; + + // Handle end being off the edge. + if (src_x_index_end > w_limit) { + src_x_index_end = w_limit; + // Either we don't need end of we chopped off the last part. + if (src_x_index_end == src_x_index) r_x_weight = 0; + else r_x_weight = 128; // max out if we chopped off + } + + int x_width_m_2 = src_x_index_end - src_x_index - 1; + long smlad_x_weight = (l_x_weight << 16) | r_x_weight; + + long t_smlad_x_weight = smlad_x_weight * t_y_weight; + long b_smlad_x_weight = smlad_x_weight * b_y_weight; + long t_b_smlad_x_weight_sum = __QADD16(t_smlad_x_weight, b_smlad_x_weight); + + uint32_t area = __SMUAD(t_b_smlad_x_weight_sum, 0x0010001); + uint32_t r_acc = 0, g_acc = 0, b_acc = 0; + + // sum corners + + int t_l_pixel = IMAGE_GET_RGB565_PIXEL_FAST(t_src_row_ptr, src_x_index); + int t_r_pixel = IMAGE_GET_RGB565_PIXEL_FAST(t_src_row_ptr, src_x_index_end); + int t_pixels = (t_l_pixel << 16) | t_r_pixel; + + long t_r = (t_pixels >> 11) & 0x1F001F; + r_acc = __SMLAD(t_r, t_smlad_x_weight, r_acc); + + long t_g = (t_pixels >> 5) & 0x3F003F; + g_acc = __SMLAD(t_g, t_smlad_x_weight, g_acc); + + long t_b = t_pixels & 0x1F001F; + b_acc = __SMLAD(t_b, t_smlad_x_weight, b_acc); + + int b_l_pixel = IMAGE_GET_RGB565_PIXEL_FAST(b_src_row_ptr, src_x_index); + int b_r_pixel = IMAGE_GET_RGB565_PIXEL_FAST(b_src_row_ptr, src_x_index_end); + int b_pixels = (b_l_pixel << 16) | b_r_pixel; + + long b_r = (b_pixels >> 11) & 0x1F001F; + r_acc = __SMLAD(b_r, b_smlad_x_weight, r_acc); + + long b_g = (b_pixels >> 5) & 0x3F003F; + g_acc = __SMLAD(b_g, b_smlad_x_weight, g_acc); + + long b_b = b_pixels & 0x1F001F; + b_acc = __SMLAD(b_b, b_smlad_x_weight, b_acc); + + area = (area + 127) >> 7; + r_acc = (r_acc + 64) >> 7; + g_acc = (g_acc + 64) >> 7; + b_acc = (b_acc + 64) >> 7; + + if (x_width_m_2 > 0) { // sum top/bot + area += x_width_m_2 * (t_y_weight + b_y_weight); + uint16_t *t_src_row_ptr_tmp = t_src_row_ptr + src_x_index_p_1; + uint16_t *b_src_row_ptr_tmp = b_src_row_ptr + src_x_index_p_1; + for (int i = src_x_index_p_1; i < src_x_index_end; i++) { + int t_y_pixel = *t_src_row_ptr_tmp++; + int b_y_pixel = *b_src_row_ptr_tmp++; + int pixels = (t_y_pixel << 16) | b_y_pixel; + + long r = (pixels >> 11) & 0x1F001F; + r_acc = __SMLAD(r, smlad_y_weight, r_acc); + + long g = (pixels >> 5) & 0x3F003F; + g_acc = __SMLAD(g, smlad_y_weight, g_acc); + + long b = pixels & 0x1F001F; + b_acc = __SMLAD(b, smlad_y_weight, b_acc); + } + } + + if (y_height_m_2 > 0) { // sum left/right + area += y_height_m_2 * (l_x_weight + r_x_weight); + for (int i = src_y_index_p_1; i < src_y_index_end; i++) { + uint16_t *src_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, i); + int l_x_pixel = IMAGE_GET_RGB565_PIXEL_FAST(src_row_ptr, src_x_index); + int r_x_pixel = IMAGE_GET_RGB565_PIXEL_FAST(src_row_ptr, src_x_index_end); + int pixels = (l_x_pixel << 16) | r_x_pixel; + + long r = (pixels >> 11) & 0x1F001F; + r_acc = __SMLAD(r, smlad_x_weight, r_acc); + + long g = (pixels >> 5) & 0x3F003F; + g_acc = __SMLAD(g, smlad_x_weight, g_acc); + + long b = pixels & 0x1F001F; + b_acc = __SMLAD(b, smlad_x_weight, b_acc); + } + } + + area = (area + 127) >> 7; + r_acc = (r_acc + 64) >> 7; + g_acc = (g_acc + 64) >> 7; + b_acc = (b_acc + 64) >> 7; + + if ((x_width_m_2 > 0) && (y_height_m_2 > 0)) { // sum middle + area += x_width_m_2 * y_height_m_2; + for (int i = src_y_index_p_1; i < src_y_index_end; i++) { + uint16_t *src_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, i) + src_x_index_p_1; + int n = x_width_m_2; +#if defined(ARM_MATH_DSP) + uint32_t *src_row_ptr32 = (uint32_t *) src_row_ptr; + + for (; n > 1; n -= 2) { + uint32_t pixels = *src_row_ptr32++; + + long r = (pixels >> 11) & 0x1F001F; + r_acc = __USADA8(r, 0, r_acc); + + long g = (pixels >> 5) & 0x3F003F; + g_acc = __USADA8(g, 0, g_acc); + + long b = pixels & 0x1F001F; + b_acc = __USADA8(b, 0, b_acc); + } + + src_row_ptr = (uint16_t *) src_row_ptr32; +#endif + for (; n > 0; n -= 1) { + int pixel = *src_row_ptr++; + r_acc += COLOR_RGB565_TO_R5(pixel); + g_acc += COLOR_RGB565_TO_G6(pixel); + b_acc += COLOR_RGB565_TO_B5(pixel); + } + } + } + + r_acc = (r_acc + (area >> 1)) / area; + g_acc = (g_acc + (area >> 1)) / area; + b_acc = (b_acc + (area >> 1)) / area; + + int pixel = COLOR_R5_G6_B5_TO_RGB565(r_acc, g_acc, b_acc); + + IMAGE_PUT_RGB565_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_RGB888: { + while (y_not_done) { +#if 1 + int src_y_index = next_src_y_index, src_y_index_p_1 = src_y_index + 1; + int src_y_index_end = src_y_index + src_y_frac_size - 1; // inclusive end + + int t_y_weight = 256 - ((src_y_accum >> 8) & 0xFF); + int b_y_weight = ((src_y_accum + src_y_frac) >> 8) & 0xFF; + // Since src_y_index_end is inclusive this should be 128 when there's perfect overlap. + if (!b_y_weight) b_y_weight = 256; + + // Handle end being off the edge. + if (src_y_index_end > h_limit) { + src_y_index_end = h_limit; + // Either we don't need end of we chopped off the last part. + if (src_y_index_end == src_y_index) b_y_weight = 0; + else b_y_weight = 256; // max out if we chopped off + } + + int y_height_m_2 = src_y_index_end - src_y_index - 1; + // long smlad_y_weight = (t_y_weight << 16) | b_y_weight; + + pixel24_t *t_src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index); + pixel24_t *b_src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_end); + + // Must be called per loop to get the address of the temp buffer to blend with + pixel24_t *dst_row_ptr = (pixel24_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index, src_x_index_p_1 = src_x_index + 1; + int src_x_index_end = src_x_index + src_x_frac_size - 1; // inclusive end + + int l_x_weight = 256 - ((src_x_accum >> 8) & 0xFF); + int r_x_weight = ((src_x_accum + src_x_frac) >> 8) & 0xFF; + // Since src_x_index_end is inclusive this should be 128 when there's perfect overlap. + if (!r_x_weight) r_x_weight = 256; + + // Handle end being off the edge. + if (src_x_index_end > w_limit) { + src_x_index_end = w_limit; + // Either we don't need end of we chopped off the last part. + if (src_x_index_end == src_x_index) r_x_weight = 0; + else r_x_weight = 256; // max out if we chopped off + } + + int x_width_m_2 = src_x_index_end - src_x_index - 1; + long smlad_x_weight0 = l_x_weight; + long smlad_x_weight1 = r_x_weight; + // long smlad_x_weight = (l_x_weight << 16) | r_x_weight; + + long t_smlad_x_weight0 = smlad_x_weight0 * t_y_weight; + long t_smlad_x_weight1 = smlad_x_weight1 * t_y_weight; + + long b_smlad_x_weight0 = smlad_x_weight0 * b_y_weight; + long b_smlad_x_weight1 = smlad_x_weight1 * b_y_weight; + + long t_b_smlad_x_weight_sum0 = t_smlad_x_weight0 + b_smlad_x_weight0; + long t_b_smlad_x_weight_sum1 = t_smlad_x_weight1 + b_smlad_x_weight1; + + uint32_t area = t_b_smlad_x_weight_sum0 + t_b_smlad_x_weight_sum1; + + uint32_t r_acc = 0, g_acc = 0, b_acc = 0; + + // sum corners + + int t_l_pixel = IMAGE_GET_RGB888_PIXEL_FAST(t_src_row_ptr, src_x_index); + int t_r_pixel = IMAGE_GET_RGB888_PIXEL_FAST(t_src_row_ptr, src_x_index_end); + + r_acc = COLOR_RGB888_TO_R8(t_l_pixel) * t_smlad_x_weight0 + COLOR_RGB888_TO_R8(t_r_pixel) * t_smlad_x_weight1 + r_acc; + g_acc = COLOR_RGB888_TO_G8(t_l_pixel) * t_smlad_x_weight0 + COLOR_RGB888_TO_G8(t_r_pixel) * t_smlad_x_weight1 + g_acc; + b_acc = COLOR_RGB888_TO_B8(t_l_pixel) * t_smlad_x_weight0 + COLOR_RGB888_TO_B8(t_r_pixel) * t_smlad_x_weight1 + b_acc; + + int b_l_pixel = IMAGE_GET_RGB888_PIXEL_FAST(b_src_row_ptr, src_x_index); + int b_r_pixel = IMAGE_GET_RGB888_PIXEL_FAST(b_src_row_ptr, src_x_index_end); + + r_acc = COLOR_RGB888_TO_R8(b_l_pixel) * b_smlad_x_weight0 + COLOR_RGB888_TO_R8(b_r_pixel) * b_smlad_x_weight1 + r_acc; + g_acc = COLOR_RGB888_TO_G8(b_l_pixel) * b_smlad_x_weight0 + COLOR_RGB888_TO_G8(b_r_pixel) * b_smlad_x_weight1 + g_acc; + b_acc = COLOR_RGB888_TO_B8(b_l_pixel) * b_smlad_x_weight0 + COLOR_RGB888_TO_B8(b_r_pixel) * b_smlad_x_weight1 + b_acc; + area = (area + 127) >> 7; + r_acc = (r_acc + 64) >> 7; + g_acc = (g_acc + 64) >> 7; + b_acc = (b_acc + 64) >> 7; + + if (x_width_m_2 > 0) { // sum top/bot + area += x_width_m_2 * (t_y_weight + b_y_weight); + pixel24_t *t_src_row_ptr_tmp = t_src_row_ptr + src_x_index_p_1; + pixel24_t *b_src_row_ptr_tmp = b_src_row_ptr + src_x_index_p_1; + for (int i = src_x_index_p_1; i < src_x_index_end; i++) { + int t_y_pixel = pixel24232(*t_src_row_ptr_tmp++); + int b_y_pixel = pixel24232(*b_src_row_ptr_tmp++); + r_acc = COLOR_RGB888_TO_R8(t_y_pixel) * t_y_weight + COLOR_RGB888_TO_R8(b_y_pixel) * b_y_weight + r_acc; + g_acc = COLOR_RGB888_TO_G8(t_y_pixel) * t_y_weight + COLOR_RGB888_TO_G8(b_y_pixel) * b_y_weight + g_acc; + b_acc = COLOR_RGB888_TO_B8(t_y_pixel) * t_y_weight + COLOR_RGB888_TO_B8(b_y_pixel) * b_y_weight + b_acc; + + } + } + + if (y_height_m_2 > 0) { // sum left/right + area += y_height_m_2 * (l_x_weight + r_x_weight); + for (int i = src_y_index_p_1; i < src_y_index_end; i++) { + pixel24_t *src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, i); + int l_x_pixel = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr, src_x_index); + int r_x_pixel = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr, src_x_index_end); + r_acc = COLOR_RGB888_TO_R8(l_x_pixel) * l_x_weight + COLOR_RGB888_TO_R8(r_x_pixel) * r_x_weight + r_acc; + g_acc = COLOR_RGB888_TO_G8(l_x_pixel) * l_x_weight + COLOR_RGB888_TO_G8(r_x_pixel) * r_x_weight + g_acc; + b_acc = COLOR_RGB888_TO_B8(l_x_pixel) * l_x_weight + COLOR_RGB888_TO_B8(r_x_pixel) * r_x_weight + b_acc; + } + } + + area = (area + 127) >> 7; + r_acc = (r_acc + 64) >> 7; + g_acc = (g_acc + 64) >> 7; + b_acc = (b_acc + 64) >> 7; + + if ((x_width_m_2 > 0) && (y_height_m_2 > 0)) { // sum middle + area += x_width_m_2 * y_height_m_2; + for (int i = src_y_index_p_1; i < src_y_index_end; i++) { + pixel24_t *src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, i) + src_x_index_p_1; + int n = x_width_m_2; +#if defined(ARM_MATH_DSP) + uint32_t *src_row_ptr32 = (uint32_t *) src_row_ptr; + + for (; n > 1; n -= 2) { + uint32_t pixels = *src_row_ptr32++; + + long r = (pixels >> 11) & 0x1F001F; + r_acc = __USADA8(r, 0, r_acc); + + long g = (pixels >> 5) & 0x3F003F; + g_acc = __USADA8(g, 0, g_acc); + + long b = pixels & 0x1F001F; + b_acc = __USADA8(b, 0, b_acc); + } + + src_row_ptr = (uint16_t *) src_row_ptr32; +#endif + for (; n > 0; n -= 1) { + int pixel = pixel24232(*src_row_ptr++); + r_acc += COLOR_RGB888_TO_R8(pixel); + g_acc += COLOR_RGB888_TO_G8(pixel); + b_acc += COLOR_RGB888_TO_B8(pixel); + } + } + } + + r_acc = (r_acc + (area >> 1)) / area; + g_acc = (g_acc + (area >> 1)) / area; + b_acc = (b_acc + (area >> 1)) / area; + + int pixel = COLOR_R8_G8_B8_TO_RGB888(r_acc, g_acc, b_acc); + + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; +#else + int src_y_index = next_src_y_index, src_y_index_p_1 = src_y_index + 1; + int src_y_index_end = src_y_index + src_y_frac_size - 1; // inclusive end + + int t_y_weight = 128 - ((src_y_accum >> 9) & 0x7F); + int b_y_weight = ((src_y_accum + src_y_frac) >> 9) & 0x7F; + // Since src_y_index_end is inclusive this should be 128 when there's perfect overlap. + if (!b_y_weight) b_y_weight = 128; + + // Handle end being off the edge. + if (src_y_index_end > h_limit) { + src_y_index_end = h_limit; + // Either we don't need end of we chopped off the last part. + if (src_y_index_end == src_y_index) b_y_weight = 0; + else b_y_weight = 128; // max out if we chopped off + } + + int y_height_m_2 = src_y_index_end - src_y_index - 1; + // long smlad_y_weight = (t_y_weight << 16) | b_y_weight; + + pixel24_t *t_src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index); + pixel24_t *b_src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_end); + + // Must be called per loop to get the address of the temp buffer to blend with + pixel24_t *dst_row_ptr = (pixel24_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index, src_x_index_p_1 = src_x_index + 1; + int src_x_index_end = src_x_index + src_x_frac_size - 1; // inclusive end + + int l_x_weight = 128 - ((src_x_accum >> 9) & 0x7F); + int r_x_weight = ((src_x_accum + src_x_frac) >> 9) & 0x7F; + // Since src_x_index_end is inclusive this should be 128 when there's perfect overlap. + if (!r_x_weight) r_x_weight = 128; + + // Handle end being off the edge. + if (src_x_index_end > w_limit) { + src_x_index_end = w_limit; + // Either we don't need end of we chopped off the last part. + if (src_x_index_end == src_x_index) r_x_weight = 0; + else r_x_weight = 128; // max out if we chopped off + } + + int x_width_m_2 = src_x_index_end - src_x_index - 1; + long smlad_x_weight = (l_x_weight << 16) | r_x_weight; + + long t_smlad_x_weight = smlad_x_weight * t_y_weight; + long b_smlad_x_weight = smlad_x_weight * b_y_weight; + long t_b_smlad_x_weight_sum = __QADD16(t_smlad_x_weight, b_smlad_x_weight); + + uint32_t area = __SMUAD(t_b_smlad_x_weight_sum, 0x10001); + uint32_t r_acc = 0, g_acc = 0, b_acc = 0; + + // sum corners + + int t_l_pixel = IMAGE_GET_RGB888_PIXEL_FAST(t_src_row_ptr, src_x_index); + int t_r_pixel = IMAGE_GET_RGB888_PIXEL_FAST(t_src_row_ptr, src_x_index_end); + int t_pixels = (t_l_pixel << 16) | t_r_pixel; + + long t_r = (t_pixels >> 11) & 0x1F001F; + r_acc = __SMLAD(t_r, t_smlad_x_weight, r_acc); + + long t_g = (t_pixels >> 5) & 0x3F003F; + g_acc = __SMLAD(t_g, t_smlad_x_weight, g_acc); + + long t_b = t_pixels & 0x1F001F; + b_acc = __SMLAD(t_b, t_smlad_x_weight, b_acc); + + int b_l_pixel = IMAGE_GET_RGB888_PIXEL_FAST(b_src_row_ptr, src_x_index); + int b_r_pixel = IMAGE_GET_RGB888_PIXEL_FAST(b_src_row_ptr, src_x_index_end); + int b_pixels = (b_l_pixel << 16) | b_r_pixel; + + long b_r = (b_pixels >> 11) & 0x1F001F; + r_acc = __SMLAD(b_r, b_smlad_x_weight, r_acc); + + long b_g = (b_pixels >> 5) & 0x3F003F; + g_acc = __SMLAD(b_g, b_smlad_x_weight, g_acc); + + long b_b = b_pixels & 0x1F001F; + b_acc = __SMLAD(b_b, b_smlad_x_weight, b_acc); + + area = (area + 127) >> 7; + r_acc = (r_acc + 64) >> 7; + g_acc = (g_acc + 64) >> 7; + b_acc = (b_acc + 64) >> 7; + + if (x_width_m_2 > 0) { // sum top/bot + area += x_width_m_2 * (t_y_weight + b_y_weight); + pixel24_t *t_src_row_ptr_tmp = t_src_row_ptr + src_x_index_p_1; + pixel24_t *b_src_row_ptr_tmp = b_src_row_ptr + src_x_index_p_1; + for (int i = src_x_index_p_1; i < src_x_index_end; i++) { + int t_y_pixel = pixel24232(*t_src_row_ptr_tmp++); + int b_y_pixel = pixel24232(*b_src_row_ptr_tmp++); + + int pixels = (t_y_pixel << 16) | b_y_pixel; + + long r = (pixels >> 11) & 0x1F001F; + r_acc = __SMLAD(r, smlad_y_weight, r_acc); + + long g = (pixels >> 5) & 0x3F003F; + g_acc = __SMLAD(g, smlad_y_weight, g_acc); + + long b = pixels & 0x1F001F; + b_acc = __SMLAD(b, smlad_y_weight, b_acc); + } + } + + if (y_height_m_2 > 0) { // sum left/right + area += y_height_m_2 * (l_x_weight + r_x_weight); + for (int i = src_y_index_p_1; i < src_y_index_end; i++) { + pixel24_t *src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, i); + int l_x_pixel = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr, src_x_index); + int r_x_pixel = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr, src_x_index_end); + + int pixels = (l_x_pixel << 16) | r_x_pixel; + + long r = (pixels >> 11) & 0x1F001F; + r_acc = __SMLAD(r, smlad_x_weight, r_acc); + + long g = (pixels >> 5) & 0x3F003F; + g_acc = __SMLAD(g, smlad_x_weight, g_acc); + + long b = pixels & 0x1F001F; + b_acc = __SMLAD(b, smlad_x_weight, b_acc); + } + } + + area = (area + 127) >> 7; + r_acc = (r_acc + 64) >> 7; + g_acc = (g_acc + 64) >> 7; + b_acc = (b_acc + 64) >> 7; + + if ((x_width_m_2 > 0) && (y_height_m_2 > 0)) { // sum middle + area += x_width_m_2 * y_height_m_2; + for (int i = src_y_index_p_1; i < src_y_index_end; i++) { + pixel24_t *src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, i) + src_x_index_p_1; + int n = x_width_m_2; +#if defined(ARM_MATH_DSP) + uint32_t *src_row_ptr32 = (uint32_t *) src_row_ptr; + + for (; n > 1; n -= 2) { + uint32_t pixels = *src_row_ptr32++; + + long r = (pixels >> 11) & 0x1F001F; + r_acc = __USADA8(r, 0, r_acc); + + long g = (pixels >> 5) & 0x3F003F; + g_acc = __USADA8(g, 0, g_acc); + + long b = pixels & 0x1F001F; + b_acc = __USADA8(b, 0, b_acc); + } + + src_row_ptr = (uint16_t *) src_row_ptr32; +#endif + for (; n > 0; n -= 1) { + int pixel = pixel24232(*src_row_ptr++); + r_acc += COLOR_RGB888_TO_R8(pixel); + g_acc += COLOR_RGB888_TO_G8(pixel); + b_acc += COLOR_RGB888_TO_B8(pixel); + } + } + } + + r_acc = (r_acc + (area >> 1)) / area; + g_acc = (g_acc + (area >> 1)) / area; + b_acc = (b_acc + (area >> 1)) / area; + + int pixel = COLOR_R8_G8_B8_TO_RGB888(r_acc, g_acc, b_acc); + + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; +#endif + } // while y + break; + } + default: { + break; + } + } + } + } else if (hint & IMAGE_HINT_BICUBIC) { + // Implements the traditional bicubic interpolation algorithm which uses + // a 4x4 filter block with the current pixel centered at (1,1) (C below). + // However, instead of floating point math, it uses integer (fixed point). + // The Cortex-M4/M7 has a hardware floating point unit, so doing FP math + // doesn't take any extra time, but it does take extra time to convert + // the integer pixels to floating point and back to integers again. + // So this allows it to execute more quickly in pure integer math. + // + // +---+---+---+---+ + // | x | x | x | x | + // +---+---+---+---+ + // | x | C | x | x | + // +---+---+---+---+ + // | x | x | x | x | + // +---+---+---+---+ + // | x | x | x | x | + // +---+---+---+---+ + // + switch (src_img->pixfmt) { + case PIXFORMAT_BINARY: { + while (y_not_done) { + int src_y_index = next_src_y_index; + uint32_t *src_row_ptr_0, *src_row_ptr_1, *src_row_ptr_2, *src_row_ptr_3; + + // keep row pointers in bounds + if (src_y_index < 0) { + src_row_ptr_0 = src_row_ptr_1 = src_row_ptr_2 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, 0); + src_row_ptr_3 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, 1); + } else if (src_y_index == 0) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, 0); + src_row_ptr_2 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, 1); + src_row_ptr_3 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, 2); + } else if (src_y_index == h_limit_m_1) { + int src_y_index_m_1 = src_y_index - 1; + src_row_ptr_0 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, h_limit_m_1); + src_row_ptr_2 = src_row_ptr_3 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, h_limit); + } else if (src_y_index >= h_limit) { + int src_y_index_m_1 = src_y_index - 1; + src_row_ptr_0 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = src_row_ptr_2 = src_row_ptr_3 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, h_limit); + } else { // get 4 neighboring rows + int src_y_index_m_1 = src_y_index - 1; + int src_y_index_p_1 = src_y_index + 1; + int src_y_index_p_2 = src_y_index + 2; + src_row_ptr_0 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, src_y_index); + src_row_ptr_2 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, src_y_index_p_1); + src_row_ptr_3 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, src_y_index_p_2); + } + + do { // Cache the results of getting the source rows + // 15-bit fraction to fit a square of it in 32-bits + // pre-calculate the ^1, ^2, and ^3 of the fraction + int dy = ((src_y_accum >> 1) & 0x7FFF); + int dy2 = (dy * dy) >> 15; + int dy3 = (dy2 * dy) >> 15; + long smuad_dy_dy2 = (dy << 16) | dy2; + + // Must be called per loop to get the address of the temp buffer to blend with + uint32_t *dst_row_ptr = (uint32_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int src_x_index_m_1 = src_x_index - 1; + int src_x_index_p_1 = src_x_index + 1; + int src_x_index_p_2 = src_x_index + 2; + int pixel_x_offests[4]; + + // keep pixels in bounds + if (src_x_index < 0) { + pixel_x_offests[0] = pixel_x_offests[1] = pixel_x_offests[2] = 0; + pixel_x_offests[3] = 1; + } else if (src_x_index == 0) { + pixel_x_offests[0] = pixel_x_offests[1] = 0; + pixel_x_offests[2] = 1; + pixel_x_offests[3] = 2; + } else if (src_x_index == w_limit_m_1) { + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = w_limit_m_1; + pixel_x_offests[2] = pixel_x_offests[3] = w_limit; + } else if (src_x_index >= w_limit) { + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = pixel_x_offests[2] = pixel_x_offests[3] = w_limit; + } else { // get 4 neighboring rows + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = src_x_index; + pixel_x_offests[2] = src_x_index_p_1; + pixel_x_offests[3] = src_x_index_p_2; + } + + int d[4]; + + for (int z = 0; z < 4; z++) { // bicubic x step (-1 to +2) + int pixel_0 = IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr_0, pixel_x_offests[z]) * 0xFF; // more res + int pixel_1 = IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr_1, pixel_x_offests[z]) * 0xFF; // more res + int pixel_2 = IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr_2, pixel_x_offests[z]) * 0xFF; // more res + int pixel_3 = IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr_3, pixel_x_offests[z]) * 0xFF; // more res + + int a0 = pixel_2 - pixel_0; + int a1 = (pixel_0 << 1) + (pixel_2 << 2) - (5 * pixel_1) - pixel_3; + int a2 = (3 * (pixel_1 - pixel_2)) + pixel_3 - pixel_0; + long smuad_a0_a1 = __PKHBT(a1, a0, 16); + int pixel_1_avg = (pixel_1 << 16) | 0x8000; + + d[z] = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_a0_a1, (dy3 * a2) + pixel_1_avg)) >> 16; + } // for z + + int d0 = d[0], d1 = d[1], d2 = d[2], d3 = d[3]; + int a0 = d2 - d0; + int a1 = (d0 << 1) + (d2 << 2) - (5 * d1) - d3; + int a2 = (3 * (d1 - d2)) + d3 - d0; + long smuad_a0_a1 = __PKHBT(a1, a0, 16); + int d1_avg = (d1 << 16) | 0x8000; + + do { // Cache the results of getting the source pixels + // 15-bit fraction to fit a square of it in 32-bits + // pre-calculate the ^1, ^2, and ^3 of the fraction + int dx = ((src_x_accum >> 1) & 0x7FFF); + int dx2 = (dx * dx) >> 15; + int dx3 = (dx2 * dx) >> 15; + long smuad_dx_dx2 = (dx << 16) | dx2; + + int pixel = __SMLAD(smuad_dx_dx2, smuad_a0_a1, (dx3 * a2) + d1_avg); + + // clamp output + pixel = __USAT_ASR(pixel, 1, 23); + + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); + } // while y + break; + } + case PIXFORMAT_GRAYSCALE: { + while (y_not_done) { + int src_y_index = next_src_y_index; + uint8_t *src_row_ptr_0, *src_row_ptr_1, *src_row_ptr_2, *src_row_ptr_3; + + // keep row pointers in bounds + if (src_y_index < 0) { + src_row_ptr_0 = src_row_ptr_1 = src_row_ptr_2 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, 0); + src_row_ptr_3 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, 1); + } else if (src_y_index == 0) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, 0); + src_row_ptr_2 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, 1); + src_row_ptr_3 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, 2); + } else if (src_y_index == h_limit_m_1) { + int src_y_index_m_1 = src_y_index - 1; + src_row_ptr_0 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, h_limit_m_1); + src_row_ptr_2 = src_row_ptr_3 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, h_limit); + } else if (src_y_index >= h_limit) { + int src_y_index_m_1 = src_y_index - 1; + src_row_ptr_0 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = src_row_ptr_2 = src_row_ptr_3 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, h_limit); + } else { // get 4 neighboring rows + int src_y_index_m_1 = src_y_index - 1; + int src_y_index_p_1 = src_y_index + 1; + int src_y_index_p_2 = src_y_index + 2; + src_row_ptr_0 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, src_y_index); + src_row_ptr_2 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, src_y_index_p_1); + src_row_ptr_3 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, src_y_index_p_2); + } + + do { // Cache the results of getting the source rows + // 15-bit fraction to fit a square of it in 32-bits + // pre-calculate the ^1, ^2, and ^3 of the fraction + int dy = ((src_y_accum >> 1) & 0x7FFF); + int dy2 = (dy * dy) >> 15; + int dy3 = (dy2 * dy) >> 15; + long smuad_dy_dy2 = (dy << 16) | dy2; + + // Must be called per loop to get the address of the temp buffer to blend with + uint8_t *dst_row_ptr = (uint8_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int src_x_index_m_1 = src_x_index - 1; +// Concept code showing off how to do 4 operations in parallel. Not useful however because of overflows +// in the 8-bit accumulators - the final image looks bad. Might be workable for lower bit-depth images. +#if 0 + int pixel_row_0, pixel_row_1, pixel_row_2, pixel_row_3; + // Column 0 = Bits[7:0] + // Column 1 = Bits[15:8] + // Column 2 = Bits[23:16] + // Column 3 = Bits[31:24] + + if (src_x_index < 0) { + pixel_row_0 = ((*src_row_ptr_0) * 0x010101) | ((*(src_row_ptr_0 + 1)) << 24); + pixel_row_1 = ((*src_row_ptr_1) * 0x010101) | ((*(src_row_ptr_1 + 1)) << 24); + pixel_row_2 = ((*src_row_ptr_2) * 0x010101) | ((*(src_row_ptr_2 + 1)) << 24); + pixel_row_3 = ((*src_row_ptr_3) * 0x010101) | ((*(src_row_ptr_3 + 1)) << 24); + } else if (src_x_index == 0) { + pixel_row_0 = ((*src_row_ptr_0) * 0x0101) | ((*((uint16_t *) (src_row_ptr_0 + 1))) << 16); + pixel_row_1 = ((*src_row_ptr_1) * 0x0101) | ((*((uint16_t *) (src_row_ptr_1 + 1))) << 16); + pixel_row_2 = ((*src_row_ptr_2) * 0x0101) | ((*((uint16_t *) (src_row_ptr_2 + 1))) << 16); + pixel_row_3 = ((*src_row_ptr_3) * 0x0101) | ((*((uint16_t *) (src_row_ptr_3 + 1))) << 16); + } else if (src_x_index == w_limit_m_1) { + pixel_row_0 = (*((uint16_t *) (src_row_ptr_0 + src_x_index_m_1))) | ((*(src_row_ptr_0 + w_limit)) * 0x01010000); + pixel_row_1 = (*((uint16_t *) (src_row_ptr_1 + src_x_index_m_1))) | ((*(src_row_ptr_1 + w_limit)) * 0x01010000); + pixel_row_2 = (*((uint16_t *) (src_row_ptr_2 + src_x_index_m_1))) | ((*(src_row_ptr_2 + w_limit)) * 0x01010000); + pixel_row_3 = (*((uint16_t *) (src_row_ptr_3 + src_x_index_m_1))) | ((*(src_row_ptr_3 + w_limit)) * 0x01010000); + } else if (src_x_index >= w_limit) { + pixel_row_0 = (*(src_row_ptr_0 + src_x_index_m_1)) | ((*(src_row_ptr_0 + w_limit)) * 0x01010100); + pixel_row_1 = (*(src_row_ptr_1 + src_x_index_m_1)) | ((*(src_row_ptr_1 + w_limit)) * 0x01010100); + pixel_row_2 = (*(src_row_ptr_2 + src_x_index_m_1)) | ((*(src_row_ptr_2 + w_limit)) * 0x01010100); + pixel_row_3 = (*(src_row_ptr_3 + src_x_index_m_1)) | ((*(src_row_ptr_3 + w_limit)) * 0x01010100); + } else { // get 4 neighboring rows + pixel_row_0 = *((uint32_t *) (src_row_ptr_0 + src_x_index_m_1)); + pixel_row_1 = *((uint32_t *) (src_row_ptr_1 + src_x_index_m_1)); + pixel_row_2 = *((uint32_t *) (src_row_ptr_2 + src_x_index_m_1)); + pixel_row_3 = *((uint32_t *) (src_row_ptr_3 + src_x_index_m_1)); + } + + // Need 8-bit signed (0x7F max). + pixel_row_0 = __UHADD8(pixel_row_0, 0); + pixel_row_1 = __UHADD8(pixel_row_1, 0); + pixel_row_2 = __UHADD8(pixel_row_2, 0); + pixel_row_3 = __UHADD8(pixel_row_3, 0); + + // Need 1/3 gaurd bits. + pixel_row_0 = __UHADD8(pixel_row_0, 0); + pixel_row_1 = __UHADD8(pixel_row_1, 0); + pixel_row_2 = __UHADD8(pixel_row_2, 0); + pixel_row_3 = __UHADD8(pixel_row_3, 0); + + // Need 2/3 gaurd bits. + pixel_row_0 = __UHADD8(pixel_row_0, 0); + pixel_row_1 = __UHADD8(pixel_row_1, 0); + pixel_row_2 = __UHADD8(pixel_row_2, 0); + pixel_row_3 = __UHADD8(pixel_row_3, 0); + + // Need 3/3 gaurd bits. + pixel_row_0 = __UHADD8(pixel_row_0, 0); + pixel_row_1 = __UHADD8(pixel_row_1, 0); + pixel_row_2 = __UHADD8(pixel_row_2, 0); + pixel_row_3 = __UHADD8(pixel_row_3, 0); + + long temp0 = __QADD8(pixel_row_2, pixel_row_2); + long temp1 = __QADD8(pixel_row_1, pixel_row_1); + long temp2 = __QSUB8(pixel_row_1, pixel_row_2); + + long a0_col = __QSUB8(pixel_row_2, pixel_row_0); + long a1_col = __QSUB8(__QSUB8(__QADD8(__QADD8(pixel_row_0, pixel_row_0), __QADD8(temp0, temp0)), __QADD8(__QADD8(temp1, temp1), pixel_row_1)), pixel_row_3); + long a2_col = __QSUB8(__QADD8(__QADD8(__QADD8(temp2, temp2), temp2), pixel_row_3), pixel_row_0); + + long a0_col_2_0 = __SXTB16(a0_col); + long a1_col_2_0 = __SXTB16(a1_col); + long a2_col_2_0 = __SXTB16(a2_col); + + long smuad_a0_a1_0 = __PKHBT(a1_col_2_0, a0_col_2_0, 16); + long pixel_1_avg_0 = ((pixel_row_1 & 0xff) << 16) | 0x8000; + int d0 = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_a0_a1_0, __SMLAD(dy3, a2_col_2_0, pixel_1_avg_0))) >> 16; + + long smuad_a0_a1_2 = __PKHTB(a0_col_2_0, a1_col_2_0, 16); + long pixel_1_avg_2 = (pixel_row_1 & 0xff0000) | 0x8000; + int d2 = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_a0_a1_2, __SMLADX(dy3, a2_col_2_0, pixel_1_avg_2))) >> 16; + + long a0_col_3_1 = __SXTB16_RORn(a0_col, 8); + long a1_col_3_1 = __SXTB16_RORn(a1_col, 8); + long a2_col_3_1 = __SXTB16_RORn(a2_col, 8); + + long smuad_a0_a1_1 = __PKHBT(a1_col_3_1, a0_col_3_1, 16); + long pixel_1_avg_1 = ((pixel_row_1 << 8) & 0xff0000) | 0x8000; + int d1 = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_a0_a1_1, __SMLAD(dy3, a2_col_3_1, pixel_1_avg_1))) >> 16; + + long smuad_a0_a1_3 = __PKHTB(a0_col_3_1, a1_col_3_1, 16); + long pixel_1_avg_3 = ((pixel_row_1 >> 8) & 0xff0000) | 0x8000; + int d3 = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_a0_a1_3, __SMLADX(dy3, a2_col_3_1, pixel_1_avg_3))) >> 16; +#else + int src_x_index_p_1 = src_x_index + 1; + int src_x_index_p_2 = src_x_index + 2; + int pixel_x_offests[4]; + + // keep pixels in bounds + if (src_x_index < 0) { + pixel_x_offests[0] = pixel_x_offests[1] = pixel_x_offests[2] = 0; + pixel_x_offests[3] = 1; + } else if (src_x_index == 0) { + pixel_x_offests[0] = pixel_x_offests[1] = 0; + pixel_x_offests[2] = 1; + pixel_x_offests[3] = 2; + } else if (src_x_index == w_limit_m_1) { + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = w_limit_m_1; + pixel_x_offests[2] = pixel_x_offests[3] = w_limit; + } else if (src_x_index >= w_limit) { + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = pixel_x_offests[2] = pixel_x_offests[3] = w_limit; + } else { // get 4 neighboring rows + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = src_x_index; + pixel_x_offests[2] = src_x_index_p_1; + pixel_x_offests[3] = src_x_index_p_2; + } + + int d[4]; + + for (int z = 0; z < 4; z++) { // bicubic x step (-1 to +2) + int pixel_0 = IMAGE_GET_GRAYSCALE_PIXEL_FAST(src_row_ptr_0, pixel_x_offests[z]); + int pixel_1 = IMAGE_GET_GRAYSCALE_PIXEL_FAST(src_row_ptr_1, pixel_x_offests[z]); + int pixel_2 = IMAGE_GET_GRAYSCALE_PIXEL_FAST(src_row_ptr_2, pixel_x_offests[z]); + int pixel_3 = IMAGE_GET_GRAYSCALE_PIXEL_FAST(src_row_ptr_3, pixel_x_offests[z]); + + int a0 = pixel_2 - pixel_0; + int a1 = (pixel_0 << 1) + (pixel_2 << 2) - (5 * pixel_1) - pixel_3; + int a2 = (3 * (pixel_1 - pixel_2)) + pixel_3 - pixel_0; + long smuad_a0_a1 = __PKHBT(a1, a0, 16); + int pixel_1_avg = (pixel_1 << 16) | 0x8000; + + d[z] = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_a0_a1, (dy3 * a2) + pixel_1_avg)) >> 16; + } // for z + + int d0 = d[0], d1 = d[1], d2 = d[2], d3 = d[3]; +#endif + int a0 = d2 - d0; + int a1 = (d0 << 1) + (d2 << 2) - (5 * d1) - d3; + int a2 = (3 * (d1 - d2)) + d3 - d0; + long smuad_a0_a1 = __PKHBT(a1, a0, 16); + int d1_avg = (d1 << 16) | 0x8000; + + do { // Cache the results of getting the source pixels + // 15-bit fraction to fit a square of it in 32-bits + // pre-calculate the ^1, ^2, and ^3 of the fraction + int dx = ((src_x_accum >> 1) & 0x7FFF); + int dx2 = (dx * dx) >> 15; + int dx3 = (dx2 * dx) >> 15; + long smuad_dx_dx2 = (dx << 16) | dx2; + + int pixel = __SMLAD(smuad_dx_dx2, smuad_a0_a1, (dx3 * a2) + d1_avg); + + // clamp output + pixel = __USAT_ASR(pixel, 8, 16); + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); + } // while y + break; + } + case PIXFORMAT_RGB565: { + while (y_not_done) { + int src_y_index = next_src_y_index; + uint16_t *src_row_ptr_0, *src_row_ptr_1, *src_row_ptr_2, *src_row_ptr_3; + + // keep row pointers in bounds + if (src_y_index < 0) { + src_row_ptr_0 = src_row_ptr_1 = src_row_ptr_2 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, 0); + src_row_ptr_3 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, 1); + } else if (src_y_index == 0) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, 0); + src_row_ptr_2 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, 1); + src_row_ptr_3 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, 2); + } else if (src_y_index == h_limit_m_1) { + int src_y_index_m_1 = src_y_index - 1; + src_row_ptr_0 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, h_limit_m_1); + src_row_ptr_2 = src_row_ptr_3 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, h_limit); + } else if (src_y_index >= h_limit) { + int src_y_index_m_1 = src_y_index - 1; + src_row_ptr_0 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = src_row_ptr_2 = src_row_ptr_3 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, h_limit); + } else { // get 4 neighboring rows + int src_y_index_m_1 = src_y_index - 1; + int src_y_index_p_1 = src_y_index + 1; + int src_y_index_p_2 = src_y_index + 2; + src_row_ptr_0 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, src_y_index); + src_row_ptr_2 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, src_y_index_p_1); + src_row_ptr_3 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, src_y_index_p_2); + } + + do { // Cache the results of getting the source rows + // 15-bit fraction to fit a square of it in 32-bits + // pre-calculate the ^1, ^2, and ^3 of the fraction + int dy = ((src_y_accum >> 1) & 0x7FFF); + int dy2 = (dy * dy) >> 15; + int dy3 = (dy2 * dy) >> 15; + long smuad_dy_dy2 = (dy << 16) | dy2; + + // Must be called per loop to get the address of the temp buffer to blend with + uint16_t *dst_row_ptr = (uint16_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int src_x_index_m_1 = src_x_index - 1; + int src_x_index_p_1 = src_x_index + 1; +#if defined(ARM_MATH_DSP) + uint32_t pixel_row_0[2], pixel_row_1[2], pixel_row_2[2], pixel_row_3[2]; + // Column 0 = Bits[15:0] + // Column 1 = Bits[31:16] + + if (src_x_index < 0) { + pixel_row_0[0] = (*src_row_ptr_0) * 0x10001; + pixel_row_0[1] = __PKHBT(pixel_row_0[0], *(src_row_ptr_0 + 1), 16); + pixel_row_1[0] = (*src_row_ptr_1) * 0x10001; + pixel_row_1[1] = __PKHBT(pixel_row_1[0], *(src_row_ptr_1 + 1), 16); + pixel_row_2[0] = (*src_row_ptr_2) * 0x10001; + pixel_row_2[1] = __PKHBT(pixel_row_2[0], *(src_row_ptr_2 + 1), 16); + pixel_row_3[0] = (*src_row_ptr_3) * 0x10001; + pixel_row_3[1] = __PKHBT(pixel_row_3[0], *(src_row_ptr_3 + 1), 16); + } else if (src_x_index == 0) { + pixel_row_0[0] = (*src_row_ptr_0) * 0x10001; + pixel_row_0[1] = *((uint32_t *) (src_row_ptr_0 + 1)); + pixel_row_1[0] = (*src_row_ptr_1) * 0x10001; + pixel_row_1[1] = *((uint32_t *) (src_row_ptr_1 + 1)); + pixel_row_2[0] = (*src_row_ptr_2) * 0x10001; + pixel_row_2[1] = *((uint32_t *) (src_row_ptr_2 + 1)); + pixel_row_3[0] = (*src_row_ptr_3) * 0x10001; + pixel_row_3[1] = *((uint32_t *) (src_row_ptr_3 + 1)); + } else if (src_x_index == w_limit_m_1) { + pixel_row_0[0] = *((uint32_t *) (src_row_ptr_0 + src_x_index_m_1)); + pixel_row_0[1] = (*(src_row_ptr_0 + w_limit)) * 0x10001; + pixel_row_1[0] = *((uint32_t *) (src_row_ptr_1 + src_x_index_m_1)); + pixel_row_1[1] = (*(src_row_ptr_1 + w_limit)) * 0x10001; + pixel_row_2[0] = *((uint32_t *) (src_row_ptr_2 + src_x_index_m_1)); + pixel_row_2[1] = (*(src_row_ptr_2 + w_limit)) * 0x10001; + pixel_row_3[0] = *((uint32_t *) (src_row_ptr_3 + src_x_index_m_1)); + pixel_row_3[1] = (*(src_row_ptr_3 + w_limit)) * 0x10001; + } else if (src_x_index >= w_limit) { + pixel_row_0[0] = *((uint32_t *) (src_row_ptr_0 + src_x_index_m_1)); + pixel_row_0[1] = (pixel_row_0[0] >> 16) * 0x10001; + pixel_row_1[0] = *((uint32_t *) (src_row_ptr_1 + src_x_index_m_1)); + pixel_row_1[1] = (pixel_row_1[0] >> 16) * 0x10001; + pixel_row_2[0] = *((uint32_t *) (src_row_ptr_2 + src_x_index_m_1)); + pixel_row_2[1] = (pixel_row_2[0] >> 16) * 0x10001; + pixel_row_3[0] = *((uint32_t *) (src_row_ptr_3 + src_x_index_m_1)); + pixel_row_3[1] = (pixel_row_3[0] >> 16) * 0x10001; + } else { // get 4 neighboring rows + pixel_row_0[0] = *((uint32_t *) (src_row_ptr_0 + src_x_index_m_1)); + pixel_row_0[1] = *((uint32_t *) (src_row_ptr_0 + src_x_index_p_1)); + pixel_row_1[0] = *((uint32_t *) (src_row_ptr_1 + src_x_index_m_1)); + pixel_row_1[1] = *((uint32_t *) (src_row_ptr_1 + src_x_index_p_1)); + pixel_row_2[0] = *((uint32_t *) (src_row_ptr_2 + src_x_index_m_1)); + pixel_row_2[1] = *((uint32_t *) (src_row_ptr_2 + src_x_index_p_1)); + pixel_row_3[0] = *((uint32_t *) (src_row_ptr_3 + src_x_index_m_1)); + pixel_row_3[1] = *((uint32_t *) (src_row_ptr_3 + src_x_index_p_1)); + } + + int r_d[4], g_d[4], b_d[4]; + + for (int z = 0; z < 2; z++) { // dual bicubic x step (-1 to +2) + + long r_pixel_row_0 = (pixel_row_0[z] >> 11) & 0x1f001f; + long r_pixel_row_1 = (pixel_row_1[z] >> 11) & 0x1f001f; + long r_pixel_row_2 = (pixel_row_2[z] >> 11) & 0x1f001f; + long r_pixel_row_3 = (pixel_row_3[z] >> 11) & 0x1f001f; + + uint32_t r_a0_col = __QSUB16(r_pixel_row_2, r_pixel_row_0); + uint32_t r_a1_col = __QSUB16(__QSUB16(__QADD16(r_pixel_row_0 << 1, r_pixel_row_2 << 2), r_pixel_row_1 * 5), r_pixel_row_3); + uint32_t r_a2_col = __QSUB16(__QADD16(__QSUB16(r_pixel_row_1 * 3, r_pixel_row_2 * 3), r_pixel_row_3), r_pixel_row_0); + + long r_smuad_a0_a1_0 = __PKHBT(r_a1_col, r_a0_col, 16); + long r_pixel_1_avg_0 = (r_pixel_row_1 << 16) | 0x8000; + r_d[z*2] = ((int32_t) __SMLAD(smuad_dy_dy2, r_smuad_a0_a1_0, __SMLAD(dy3, r_a2_col, r_pixel_1_avg_0))) >> 16; + + long r_smuad_a0_a1_1 = __PKHTB(r_a0_col, r_a1_col, 16); + long r_pixel_1_avg_1 = __PKHTB(r_pixel_row_1, 0x8000, 0); + r_d[(z*2)+1] = ((int32_t) __SMLAD(smuad_dy_dy2, r_smuad_a0_a1_1, __SMLADX(dy3, r_a2_col, r_pixel_1_avg_1))) >> 16; + + long g_pixel_row_0 = (pixel_row_0[z] >> 5) & 0x3f003f; + long g_pixel_row_1 = (pixel_row_1[z] >> 5) & 0x3f003f; + long g_pixel_row_2 = (pixel_row_2[z] >> 5) & 0x3f003f; + long g_pixel_row_3 = (pixel_row_3[z] >> 5) & 0x3f003f; + + uint32_t g_a0_col = __QSUB16(g_pixel_row_2, g_pixel_row_0); + uint32_t g_a1_col = __QSUB16(__QSUB16(__QADD16(g_pixel_row_0 << 1, g_pixel_row_2 << 2), g_pixel_row_1 * 5), g_pixel_row_3); + uint32_t g_a2_col = __QSUB16(__QADD16(__QSUB16(g_pixel_row_1 * 3, g_pixel_row_2 * 3), g_pixel_row_3), g_pixel_row_0); + + long g_smuad_a0_a1_0 = __PKHBT(g_a1_col, g_a0_col, 16); + long g_pixel_1_avg_0 = (g_pixel_row_1 << 16) | 0x8000; + g_d[z*2] = ((int32_t) __SMLAD(smuad_dy_dy2, g_smuad_a0_a1_0, __SMLAD(dy3, g_a2_col, g_pixel_1_avg_0))) >> 16; + + long g_smuad_a0_a1_1 = __PKHTB(g_a0_col, g_a1_col, 16); + long g_pixel_1_avg_1 = __PKHTB(g_pixel_row_1, 0x8000, 0); + g_d[(z*2)+1] = ((int32_t) __SMLAD(smuad_dy_dy2, g_smuad_a0_a1_1, __SMLADX(dy3, g_a2_col, g_pixel_1_avg_1))) >> 16; + + long b_pixel_row_0 = pixel_row_0[z] & 0x1f001f; + long b_pixel_row_1 = pixel_row_1[z] & 0x1f001f; + long b_pixel_row_2 = pixel_row_2[z] & 0x1f001f; + long b_pixel_row_3 = pixel_row_3[z] & 0x1f001f; + + uint32_t b_a0_col = __QSUB16(b_pixel_row_2, b_pixel_row_0); + uint32_t b_a1_col = __QSUB16(__QSUB16(__QADD16(b_pixel_row_0 << 1, b_pixel_row_2 << 2), b_pixel_row_1 * 5), b_pixel_row_3); + uint32_t b_a2_col = __QSUB16(__QADD16(__QSUB16(b_pixel_row_1 * 3, b_pixel_row_2 * 3), b_pixel_row_3), b_pixel_row_0); + + long b_smuad_a0_a1_0 = __PKHBT(b_a1_col, b_a0_col, 16); + long b_pixel_1_avg_0 = (b_pixel_row_1 << 16) | 0x8000; + b_d[z*2] = ((int32_t) __SMLAD(smuad_dy_dy2, b_smuad_a0_a1_0, __SMLAD(dy3, b_a2_col, b_pixel_1_avg_0))) >> 16; + + long b_smuad_a0_a1_1 = __PKHTB(b_a0_col, b_a1_col, 16); + long b_pixel_1_avg_1 = __PKHTB(b_pixel_row_1, 0x8000, 0); + b_d[(z*2)+1] = ((int32_t) __SMLAD(smuad_dy_dy2, b_smuad_a0_a1_1, __SMLADX(dy3, b_a2_col, b_pixel_1_avg_1))) >> 16; + } // for z +#else + int src_x_index_p_2 = src_x_index + 2; + int pixel_x_offests[4]; + + // keep pixels in bounds + if (src_x_index < 0) { + pixel_x_offests[0] = pixel_x_offests[1] = pixel_x_offests[2] = 0; + pixel_x_offests[3] = 1; + } else if (src_x_index == 0) { + pixel_x_offests[0] = pixel_x_offests[1] = 0; + pixel_x_offests[2] = 1; + pixel_x_offests[3] = 2; + } else if (src_x_index == w_limit_m_1) { + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = w_limit_m_1; + pixel_x_offests[2] = pixel_x_offests[3] = w_limit; + } else if (src_x_index >= w_limit) { + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = pixel_x_offests[2] = pixel_x_offests[3] = w_limit; + } else { // get 4 neighboring rows + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = src_x_index; + pixel_x_offests[2] = src_x_index_p_1; + pixel_x_offests[3] = src_x_index_p_2; + } + + int r_d[4], g_d[4], b_d[4]; + + for (int z = 0; z < 4; z++) { // bicubic x step (-1 to +2) + int pixel_0 = IMAGE_GET_RGB565_PIXEL_FAST(src_row_ptr_0, pixel_x_offests[z]); + int pixel_1 = IMAGE_GET_RGB565_PIXEL_FAST(src_row_ptr_1, pixel_x_offests[z]); + int pixel_2 = IMAGE_GET_RGB565_PIXEL_FAST(src_row_ptr_2, pixel_x_offests[z]); + int pixel_3 = IMAGE_GET_RGB565_PIXEL_FAST(src_row_ptr_3, pixel_x_offests[z]); + + int r0 = pixel_0 >> 11; + int r1 = pixel_1 >> 11; + int r2 = pixel_2 >> 11; + int r3 = pixel_3 >> 11; + + int r_a0 = r2 - r0; + int r_a1 = (r0 << 1) + (r2 << 2) - (5 * r1) - r3; + int r_a2 = (3 * (r1 - r2)) + r3 - r0; + long smuad_r_a0_r_a1 = __PKHBT(r_a1, r_a0, 16); + int r1_avg = (r1 << 16) | 0x8000; + + r_d[z] = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_r_a0_r_a1, (dy3 * r_a2) + r1_avg)) >> 16; + + int g0 = (pixel_0 >> 5) & 0x3F; + int g1 = (pixel_1 >> 5) & 0x3F; + int g2 = (pixel_2 >> 5) & 0x3F; + int g3 = (pixel_3 >> 5) & 0x3F; + + int g_a0 = g2 - g0; + int g_a1 = (g0 << 1) + (g2 << 2) - (5 * g1) - g3; + int g_a2 = (3 * (g1 - g2)) + g3 - g0; + long smuad_g_a0_g_a1 = __PKHBT(g_a1, g_a0, 16); + int g1_avg = (g1 << 16) | 0x8000; + + g_d[z] = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_g_a0_g_a1, (dy3 * g_a2) + g1_avg)) >> 16; + + int b0 = pixel_0 & 0x1F; + int b1 = pixel_1 & 0x1F; + int b2 = pixel_2 & 0x1F; + int b3 = pixel_3 & 0x1F; + + int b_a0 = b2 - b0; + int b_a1 = (b0 << 1) + (b2 << 2) - (5 * b1) - b3; + int b_a2 = (3 * (b1 - b2)) + b3 - b0; + long smuad_b_a0_b_a1 = __PKHBT(b_a1, b_a0, 16); + int b1_avg = (b1 << 16) | 0x8000; + + b_d[z] = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_b_a0_b_a1, (dy3 * b_a2) + b1_avg)) >> 16; + } // for z +#endif + int r_d0 = r_d[0], r_d1 = r_d[1], r_d2 = r_d[2], r_d3 = r_d[3]; + int r_a0 = r_d2 - r_d0; + int r_a1 = (r_d0 << 1) + (r_d2 << 2) - (5 * r_d1) - r_d3; + int r_a2 = (3 * (r_d1 - r_d2)) + r_d3 - r_d0; + long smuad_r_a0_r_a1 = __PKHBT(r_a1, r_a0, 16); + int r_d1_avg = (r_d1 << 16) | 0x8000; + + int g_d0 = g_d[0], g_d1 = g_d[1], g_d2 = g_d[2], g_d3 = g_d[3]; + int g_a0 = g_d2 - g_d0; + int g_a1 = (g_d0 << 1) + (g_d2 << 2) - (5 * g_d1) - g_d3; + int g_a2 = (3 * (g_d1 - g_d2)) + g_d3 - g_d0; + long smuad_g_a0_g_a1 = __PKHBT(g_a1, g_a0, 16); + int g_d1_avg = (g_d1 << 16) | 0x8000; + + int b_d0 = b_d[0], b_d1 = b_d[1], b_d2 = b_d[2], b_d3 = b_d[3]; + int b_a0 = b_d2 - b_d0; + int b_a1 = (b_d0 << 1) + (b_d2 << 2) - (5 * b_d1) - b_d3; + int b_a2 = (3 * (b_d1 - b_d2)) + b_d3 - b_d0; + long smuad_b_a0_b_a1 = __PKHBT(b_a1, b_a0, 16); + int b_d1_avg = (b_d1 << 16) | 0x8000; + + do { // Cache the results of getting the source pixels + // 15-bit fraction to fit a square of it in 32-bits + // pre-calculate the ^1, ^2, and ^3 of the fraction + int dx = ((src_x_accum >> 1) & 0x7FFF); + int dx2 = (dx * dx) >> 15; + int dx3 = (dx2 * dx) >> 15; + long smuad_dx_dx2 = (dx << 16) | dx2; + + long r_pixel = __SMLAD(smuad_dx_dx2, smuad_r_a0_r_a1, (dx3 * r_a2) + r_d1_avg); + + // clamp output + r_pixel = __USAT_ASR(r_pixel, 5, 16); + + long g_pixel = __SMLAD(smuad_dx_dx2, smuad_g_a0_g_a1, (dx3 * g_a2) + g_d1_avg); + + // clamp output + g_pixel = __USAT_ASR(g_pixel, 6, 16); + + long b_pixel = __SMLAD(smuad_dx_dx2, smuad_b_a0_b_a1, (dx3 * b_a2) + b_d1_avg); + + // clamp output + b_pixel = __USAT_ASR(b_pixel, 5, 16); + + int pixel = COLOR_R5_G6_B5_TO_RGB565(r_pixel, g_pixel, b_pixel); + + IMAGE_PUT_RGB565_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); + } // while y + break; + } + case PIXFORMAT_RGB888: { + while (y_not_done) { +#if 1 + int src_y_index = next_src_y_index; + pixel24_t *src_row_ptr_0, *src_row_ptr_1, *src_row_ptr_2, *src_row_ptr_3; + + // keep row pointers in bounds + if (src_y_index < 0) { + src_row_ptr_0 = src_row_ptr_1 = src_row_ptr_2 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, 0); + src_row_ptr_3 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, 1); + } else if (src_y_index == 0) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, 0); + src_row_ptr_2 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, 1); + src_row_ptr_3 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, 2); + } else if (src_y_index == h_limit_m_1) { + int src_y_index_m_1 = src_y_index - 1; + src_row_ptr_0 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, h_limit_m_1); + src_row_ptr_2 = src_row_ptr_3 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, h_limit); + } else if (src_y_index >= h_limit) { + int src_y_index_m_1 = src_y_index - 1; + src_row_ptr_0 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = src_row_ptr_2 = src_row_ptr_3 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, h_limit); + } else { // get 4 neighboring rows + int src_y_index_m_1 = src_y_index - 1; + int src_y_index_p_1 = src_y_index + 1; + int src_y_index_p_2 = src_y_index + 2; + src_row_ptr_0 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index); + src_row_ptr_2 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_p_1); + src_row_ptr_3 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_p_2); + } + + do { // Cache the results of getting the source rows + // 15-bit fraction to fit a square of it in 32-bits + // pre-calculate the ^1, ^2, and ^3 of the fraction + int dy = ((src_y_accum >> 1) & 0x7FFF); + int dy2 = (dy * dy) >> 15; + int dy3 = (dy2 * dy) >> 15; + // long smuad_dy_dy2 = (dy << 16) | dy2; + + // Must be called per loop to get the address of the temp buffer to blend with + pixel24_t *dst_row_ptr = (pixel24_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int src_x_index_m_1 = src_x_index - 1; + int src_x_index_p_1 = src_x_index + 1; +#if defined(ARM_MATH_DSP) + uint32_t pixel_row_0[2], pixel_row_1[2], pixel_row_2[2], pixel_row_3[2]; + // Column 0 = Bits[15:0] + // Column 1 = Bits[31:16] + + if (src_x_index < 0) { + pixel_row_0[0] = (*src_row_ptr_0) * 0x10001; + pixel_row_0[1] = __PKHBT(pixel_row_0[0], *(src_row_ptr_0 + 1), 16); + pixel_row_1[0] = (*src_row_ptr_1) * 0x10001; + pixel_row_1[1] = __PKHBT(pixel_row_1[0], *(src_row_ptr_1 + 1), 16); + pixel_row_2[0] = (*src_row_ptr_2) * 0x10001; + pixel_row_2[1] = __PKHBT(pixel_row_2[0], *(src_row_ptr_2 + 1), 16); + pixel_row_3[0] = (*src_row_ptr_3) * 0x10001; + pixel_row_3[1] = __PKHBT(pixel_row_3[0], *(src_row_ptr_3 + 1), 16); + } else if (src_x_index == 0) { + pixel_row_0[0] = (*src_row_ptr_0) * 0x10001; + pixel_row_0[1] = *((uint32_t *) (src_row_ptr_0 + 1)); + pixel_row_1[0] = (*src_row_ptr_1) * 0x10001; + pixel_row_1[1] = *((uint32_t *) (src_row_ptr_1 + 1)); + pixel_row_2[0] = (*src_row_ptr_2) * 0x10001; + pixel_row_2[1] = *((uint32_t *) (src_row_ptr_2 + 1)); + pixel_row_3[0] = (*src_row_ptr_3) * 0x10001; + pixel_row_3[1] = *((uint32_t *) (src_row_ptr_3 + 1)); + } else if (src_x_index == w_limit_m_1) { + pixel_row_0[0] = *((uint32_t *) (src_row_ptr_0 + src_x_index_m_1)); + pixel_row_0[1] = (*(src_row_ptr_0 + w_limit)) * 0x10001; + pixel_row_1[0] = *((uint32_t *) (src_row_ptr_1 + src_x_index_m_1)); + pixel_row_1[1] = (*(src_row_ptr_1 + w_limit)) * 0x10001; + pixel_row_2[0] = *((uint32_t *) (src_row_ptr_2 + src_x_index_m_1)); + pixel_row_2[1] = (*(src_row_ptr_2 + w_limit)) * 0x10001; + pixel_row_3[0] = *((uint32_t *) (src_row_ptr_3 + src_x_index_m_1)); + pixel_row_3[1] = (*(src_row_ptr_3 + w_limit)) * 0x10001; + } else if (src_x_index >= w_limit) { + pixel_row_0[0] = *((uint32_t *) (src_row_ptr_0 + src_x_index_m_1)); + pixel_row_0[1] = (pixel_row_0[0] >> 16) * 0x10001; + pixel_row_1[0] = *((uint32_t *) (src_row_ptr_1 + src_x_index_m_1)); + pixel_row_1[1] = (pixel_row_1[0] >> 16) * 0x10001; + pixel_row_2[0] = *((uint32_t *) (src_row_ptr_2 + src_x_index_m_1)); + pixel_row_2[1] = (pixel_row_2[0] >> 16) * 0x10001; + pixel_row_3[0] = *((uint32_t *) (src_row_ptr_3 + src_x_index_m_1)); + pixel_row_3[1] = (pixel_row_3[0] >> 16) * 0x10001; + } else { // get 4 neighboring rows + pixel_row_0[0] = *((uint32_t *) (src_row_ptr_0 + src_x_index_m_1)); + pixel_row_0[1] = *((uint32_t *) (src_row_ptr_0 + src_x_index_p_1)); + pixel_row_1[0] = *((uint32_t *) (src_row_ptr_1 + src_x_index_m_1)); + pixel_row_1[1] = *((uint32_t *) (src_row_ptr_1 + src_x_index_p_1)); + pixel_row_2[0] = *((uint32_t *) (src_row_ptr_2 + src_x_index_m_1)); + pixel_row_2[1] = *((uint32_t *) (src_row_ptr_2 + src_x_index_p_1)); + pixel_row_3[0] = *((uint32_t *) (src_row_ptr_3 + src_x_index_m_1)); + pixel_row_3[1] = *((uint32_t *) (src_row_ptr_3 + src_x_index_p_1)); + } + + int r_d[4], g_d[4], b_d[4]; + + for (int z = 0; z < 2; z++) { // dual bicubic x step (-1 to +2) + + long r_pixel_row_0 = (pixel_row_0[z] >> 11) & 0x1f001f; + long r_pixel_row_1 = (pixel_row_1[z] >> 11) & 0x1f001f; + long r_pixel_row_2 = (pixel_row_2[z] >> 11) & 0x1f001f; + long r_pixel_row_3 = (pixel_row_3[z] >> 11) & 0x1f001f; + + uint32_t r_a0_col = __QSUB16(r_pixel_row_2, r_pixel_row_0); + uint32_t r_a1_col = __QSUB16(__QSUB16(__QADD16(r_pixel_row_0 << 1, r_pixel_row_2 << 2), r_pixel_row_1 * 5), r_pixel_row_3); + uint32_t r_a2_col = __QSUB16(__QADD16(__QSUB16(r_pixel_row_1 * 3, r_pixel_row_2 * 3), r_pixel_row_3), r_pixel_row_0); + + long r_smuad_a0_a1_0 = __PKHBT(r_a1_col, r_a0_col, 16); + long r_pixel_1_avg_0 = (r_pixel_row_1 << 16) | 0x8000; + r_d[z*2] = ((int32_t) __SMLAD(smuad_dy_dy2, r_smuad_a0_a1_0, __SMLAD(dy3, r_a2_col, r_pixel_1_avg_0))) >> 16; + + long r_smuad_a0_a1_1 = __PKHTB(r_a0_col, r_a1_col, 16); + long r_pixel_1_avg_1 = __PKHTB(r_pixel_row_1, 0x8000, 0); + r_d[(z*2)+1] = ((int32_t) __SMLAD(smuad_dy_dy2, r_smuad_a0_a1_1, __SMLADX(dy3, r_a2_col, r_pixel_1_avg_1))) >> 16; + + long g_pixel_row_0 = (pixel_row_0[z] >> 5) & 0x3f003f; + long g_pixel_row_1 = (pixel_row_1[z] >> 5) & 0x3f003f; + long g_pixel_row_2 = (pixel_row_2[z] >> 5) & 0x3f003f; + long g_pixel_row_3 = (pixel_row_3[z] >> 5) & 0x3f003f; + + uint32_t g_a0_col = __QSUB16(g_pixel_row_2, g_pixel_row_0); + uint32_t g_a1_col = __QSUB16(__QSUB16(__QADD16(g_pixel_row_0 << 1, g_pixel_row_2 << 2), g_pixel_row_1 * 5), g_pixel_row_3); + uint32_t g_a2_col = __QSUB16(__QADD16(__QSUB16(g_pixel_row_1 * 3, g_pixel_row_2 * 3), g_pixel_row_3), g_pixel_row_0); + + long g_smuad_a0_a1_0 = __PKHBT(g_a1_col, g_a0_col, 16); + long g_pixel_1_avg_0 = (g_pixel_row_1 << 16) | 0x8000; + g_d[z*2] = ((int32_t) __SMLAD(smuad_dy_dy2, g_smuad_a0_a1_0, __SMLAD(dy3, g_a2_col, g_pixel_1_avg_0))) >> 16; + + long g_smuad_a0_a1_1 = __PKHTB(g_a0_col, g_a1_col, 16); + long g_pixel_1_avg_1 = __PKHTB(g_pixel_row_1, 0x8000, 0); + g_d[(z*2)+1] = ((int32_t) __SMLAD(smuad_dy_dy2, g_smuad_a0_a1_1, __SMLADX(dy3, g_a2_col, g_pixel_1_avg_1))) >> 16; + + long b_pixel_row_0 = pixel_row_0[z] & 0x1f001f; + long b_pixel_row_1 = pixel_row_1[z] & 0x1f001f; + long b_pixel_row_2 = pixel_row_2[z] & 0x1f001f; + long b_pixel_row_3 = pixel_row_3[z] & 0x1f001f; + + uint32_t b_a0_col = __QSUB16(b_pixel_row_2, b_pixel_row_0); + uint32_t b_a1_col = __QSUB16(__QSUB16(__QADD16(b_pixel_row_0 << 1, b_pixel_row_2 << 2), b_pixel_row_1 * 5), b_pixel_row_3); + uint32_t b_a2_col = __QSUB16(__QADD16(__QSUB16(b_pixel_row_1 * 3, b_pixel_row_2 * 3), b_pixel_row_3), b_pixel_row_0); + + long b_smuad_a0_a1_0 = __PKHBT(b_a1_col, b_a0_col, 16); + long b_pixel_1_avg_0 = (b_pixel_row_1 << 16) | 0x8000; + b_d[z*2] = ((int32_t) __SMLAD(smuad_dy_dy2, b_smuad_a0_a1_0, __SMLAD(dy3, b_a2_col, b_pixel_1_avg_0))) >> 16; + + long b_smuad_a0_a1_1 = __PKHTB(b_a0_col, b_a1_col, 16); + long b_pixel_1_avg_1 = __PKHTB(b_pixel_row_1, 0x8000, 0); + b_d[(z*2)+1] = ((int32_t) __SMLAD(smuad_dy_dy2, b_smuad_a0_a1_1, __SMLADX(dy3, b_a2_col, b_pixel_1_avg_1))) >> 16; + } // for z +#else + int src_x_index_p_2 = src_x_index + 2; + int pixel_x_offests[4]; + + // keep pixels in bounds + if (src_x_index < 0) { + pixel_x_offests[0] = pixel_x_offests[1] = pixel_x_offests[2] = 0; + pixel_x_offests[3] = 1; + } else if (src_x_index == 0) { + pixel_x_offests[0] = pixel_x_offests[1] = 0; + pixel_x_offests[2] = 1; + pixel_x_offests[3] = 2; + } else if (src_x_index == w_limit_m_1) { + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = w_limit_m_1; + pixel_x_offests[2] = pixel_x_offests[3] = w_limit; + } else if (src_x_index >= w_limit) { + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = pixel_x_offests[2] = pixel_x_offests[3] = w_limit; + } else { // get 4 neighboring rows + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = src_x_index; + pixel_x_offests[2] = src_x_index_p_1; + pixel_x_offests[3] = src_x_index_p_2; + } + + int r_d[4], g_d[4], b_d[4]; + + for (int z = 0; z < 4; z++) { // bicubic x step (-1 to +2) + int pixel_0 = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr_0, pixel_x_offests[z]); + int pixel_1 = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr_1, pixel_x_offests[z]); + int pixel_2 = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr_2, pixel_x_offests[z]); + int pixel_3 = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr_3, pixel_x_offests[z]); + + int r0 = COLOR_RGB888_TO_R8(pixel_0); + int r1 = COLOR_RGB888_TO_R8(pixel_1); + int r2 = COLOR_RGB888_TO_R8(pixel_2); + int r3 = COLOR_RGB888_TO_R8(pixel_3); + + int r_a0 = r2 - r0; + int r_a1 = (r0 << 1) + (r2 << 2) - (5 * r1) - r3; + int r_a2 = (3 * (r1 - r2)) + r3 - r0; + // long smuad_r_a0_r_a1 = __PKHBT(r_a1, r_a0, 16); + int r1_avg = (r1 << 16) | 0x8000; + + r_d[z] = (dy * r_a0 + dy2 * r_a1 + ((dy3 * r_a2) + r1_avg)) >> 16; + // r_d[z] = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_r_a0_r_a1, (dy3 * r_a2) + r1_avg)) >> 16; + + int g0 = COLOR_RGB888_TO_G8(pixel_0); + int g1 = COLOR_RGB888_TO_G8(pixel_1); + int g2 = COLOR_RGB888_TO_G8(pixel_2); + int g3 = COLOR_RGB888_TO_G8(pixel_3); + + int g_a0 = g2 - g0; + int g_a1 = (g0 << 1) + (g2 << 2) - (5 * g1) - g3; + int g_a2 = (3 * (g1 - g2)) + g3 - g0; + // long smuad_g_a0_g_a1 = __PKHBT(g_a1, g_a0, 16); + int g1_avg = (g1 << 16) | 0x8000; + + g_d[z] = (dy * g_a0 + dy2 * g_a1 + ((dy3 * g_a2) + g1_avg)) >> 16; + // g_d[z] = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_g_a0_g_a1, (dy3 * g_a2) + g1_avg)) >> 16; + + int b0 = COLOR_RGB888_TO_B8(pixel_0); + int b1 = COLOR_RGB888_TO_B8(pixel_1); + int b2 = COLOR_RGB888_TO_B8(pixel_2); + int b3 = COLOR_RGB888_TO_B8(pixel_3); + + int b_a0 = b2 - b0; + int b_a1 = (b0 << 1) + (b2 << 2) - (5 * b1) - b3; + int b_a2 = (3 * (b1 - b2)) + b3 - b0; + // long smuad_b_a0_b_a1 = __PKHBT(b_a1, b_a0, 16); + int b1_avg = (b1 << 16) | 0x8000; + + b_d[z] = (dy * b_a0 + dy2 * b_a1 + ((dy3 * b_a2) + b1_avg)) >> 16; + // b_d[z] = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_b_a0_b_a1, (dy3 * b_a2) + b1_avg)) >> 16; + } // for z +#endif + int r_d0 = r_d[0], r_d1 = r_d[1], r_d2 = r_d[2], r_d3 = r_d[3]; + int r_a0 = r_d2 - r_d0; + int r_a1 = (r_d0 << 1) + (r_d2 << 2) - (5 * r_d1) - r_d3; + int r_a2 = (3 * (r_d1 - r_d2)) + r_d3 - r_d0; + // long smuad_r_a0_r_a1 = __PKHBT(r_a1, r_a0, 16); + int r_d1_avg = (r_d1 << 16) | 0x8000; + + int g_d0 = g_d[0], g_d1 = g_d[1], g_d2 = g_d[2], g_d3 = g_d[3]; + int g_a0 = g_d2 - g_d0; + int g_a1 = (g_d0 << 1) + (g_d2 << 2) - (5 * g_d1) - g_d3; + int g_a2 = (3 * (g_d1 - g_d2)) + g_d3 - g_d0; + // long smuad_g_a0_g_a1 = __PKHBT(g_a1, g_a0, 16); + int g_d1_avg = (g_d1 << 16) | 0x8000; + + int b_d0 = b_d[0], b_d1 = b_d[1], b_d2 = b_d[2], b_d3 = b_d[3]; + int b_a0 = b_d2 - b_d0; + int b_a1 = (b_d0 << 1) + (b_d2 << 2) - (5 * b_d1) - b_d3; + int b_a2 = (3 * (b_d1 - b_d2)) + b_d3 - b_d0; + // long smuad_b_a0_b_a1 = __PKHBT(b_a1, b_a0, 16); + int b_d1_avg = (b_d1 << 16) | 0x8000; + + do { // Cache the results of getting the source pixels + // 15-bit fraction to fit a square of it in 32-bits + // pre-calculate the ^1, ^2, and ^3 of the fraction + int dx = ((src_x_accum >> 1) & 0x7FFF); + int dx2 = (dx * dx) >> 15; + int dx3 = (dx2 * dx) >> 15; + // long smuad_dx_dx2 = (dx << 16) | dx2; + + long r_pixel = dx * r_a0 + dx2 * r_a1 + (dx3 * r_a2) + r_d1_avg; + // long r_pixel = __SMLAD(smuad_dx_dx2, smuad_r_a0_r_a1, (dx3 * r_a2) + r_d1_avg); + + // clamp output + r_pixel = __USAT_ASR(r_pixel, 8, 16); + + long g_pixel = dx * g_a0 + dx2 * g_a1 + (dx3 * g_a2) + g_d1_avg; + // long g_pixel = __SMLAD(smuad_dx_dx2, smuad_g_a0_g_a1, (dx3 * g_a2) + g_d1_avg); + + // clamp output + g_pixel = __USAT_ASR(g_pixel, 8, 16); + + long b_pixel = dx * b_a0 + dx2 * b_a1 + (dx3 * b_a2) + b_d1_avg; + // long b_pixel = __SMLAD(smuad_dx_dx2, smuad_b_a0_b_a1, (dx3 * b_a2) + b_d1_avg); + + // clamp output + b_pixel = __USAT_ASR(b_pixel, 8, 16); + + int pixel = COLOR_R8_G8_B8_TO_RGB888(r_pixel, g_pixel, b_pixel); + + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); +#else + int src_y_index = next_src_y_index; + pixel24_t *src_row_ptr_0, *src_row_ptr_1, *src_row_ptr_2, *src_row_ptr_3; + + // keep row pointers in bounds + if (src_y_index < 0) { + src_row_ptr_0 = src_row_ptr_1 = src_row_ptr_2 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, 0); + src_row_ptr_3 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, 1); + } else if (src_y_index == 0) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, 0); + src_row_ptr_2 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, 1); + src_row_ptr_3 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, 2); + } else if (src_y_index == h_limit_m_1) { + int src_y_index_m_1 = src_y_index - 1; + src_row_ptr_0 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, h_limit_m_1); + src_row_ptr_2 = src_row_ptr_3 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, h_limit); + } else if (src_y_index >= h_limit) { + int src_y_index_m_1 = src_y_index - 1; + src_row_ptr_0 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = src_row_ptr_2 = src_row_ptr_3 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, h_limit); + } else { // get 4 neighboring rows + int src_y_index_m_1 = src_y_index - 1; + int src_y_index_p_1 = src_y_index + 1; + int src_y_index_p_2 = src_y_index + 2; + src_row_ptr_0 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_m_1); + src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index); + src_row_ptr_2 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_p_1); + src_row_ptr_3 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_p_2); + } + + do { // Cache the results of getting the source rows + // 15-bit fraction to fit a square of it in 32-bits + // pre-calculate the ^1, ^2, and ^3 of the fraction + int dy = ((src_y_accum >> 1) & 0x7FFF); + int dy2 = (dy * dy) >> 15; + int dy3 = (dy2 * dy) >> 15; + long smuad_dy_dy2 = (dy << 16) | dy2; + + // Must be called per loop to get the address of the temp buffer to blend with + pixel24_t *dst_row_ptr = (pixel24_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int src_x_index_m_1 = src_x_index - 1; + int src_x_index_p_1 = src_x_index + 1; +#if defined(ARM_MATH_DSP) + uint32_t pixel_row_0[2], pixel_row_1[2], pixel_row_2[2], pixel_row_3[2]; + // Column 0 = Bits[15:0] + // Column 1 = Bits[31:16] + + if (src_x_index < 0) { + pixel_row_0[0] = (*src_row_ptr_0) * 0x10001; + pixel_row_0[1] = __PKHBT(pixel_row_0[0], *(src_row_ptr_0 + 1), 16); + pixel_row_1[0] = (*src_row_ptr_1) * 0x10001; + pixel_row_1[1] = __PKHBT(pixel_row_1[0], *(src_row_ptr_1 + 1), 16); + pixel_row_2[0] = (*src_row_ptr_2) * 0x10001; + pixel_row_2[1] = __PKHBT(pixel_row_2[0], *(src_row_ptr_2 + 1), 16); + pixel_row_3[0] = (*src_row_ptr_3) * 0x10001; + pixel_row_3[1] = __PKHBT(pixel_row_3[0], *(src_row_ptr_3 + 1), 16); + } else if (src_x_index == 0) { + pixel_row_0[0] = (*src_row_ptr_0) * 0x10001; + pixel_row_0[1] = *((uint32_t *) (src_row_ptr_0 + 1)); + pixel_row_1[0] = (*src_row_ptr_1) * 0x10001; + pixel_row_1[1] = *((uint32_t *) (src_row_ptr_1 + 1)); + pixel_row_2[0] = (*src_row_ptr_2) * 0x10001; + pixel_row_2[1] = *((uint32_t *) (src_row_ptr_2 + 1)); + pixel_row_3[0] = (*src_row_ptr_3) * 0x10001; + pixel_row_3[1] = *((uint32_t *) (src_row_ptr_3 + 1)); + } else if (src_x_index == w_limit_m_1) { + pixel_row_0[0] = *((uint32_t *) (src_row_ptr_0 + src_x_index_m_1)); + pixel_row_0[1] = (*(src_row_ptr_0 + w_limit)) * 0x10001; + pixel_row_1[0] = *((uint32_t *) (src_row_ptr_1 + src_x_index_m_1)); + pixel_row_1[1] = (*(src_row_ptr_1 + w_limit)) * 0x10001; + pixel_row_2[0] = *((uint32_t *) (src_row_ptr_2 + src_x_index_m_1)); + pixel_row_2[1] = (*(src_row_ptr_2 + w_limit)) * 0x10001; + pixel_row_3[0] = *((uint32_t *) (src_row_ptr_3 + src_x_index_m_1)); + pixel_row_3[1] = (*(src_row_ptr_3 + w_limit)) * 0x10001; + } else if (src_x_index >= w_limit) { + pixel_row_0[0] = *((uint32_t *) (src_row_ptr_0 + src_x_index_m_1)); + pixel_row_0[1] = (pixel_row_0[0] >> 16) * 0x10001; + pixel_row_1[0] = *((uint32_t *) (src_row_ptr_1 + src_x_index_m_1)); + pixel_row_1[1] = (pixel_row_1[0] >> 16) * 0x10001; + pixel_row_2[0] = *((uint32_t *) (src_row_ptr_2 + src_x_index_m_1)); + pixel_row_2[1] = (pixel_row_2[0] >> 16) * 0x10001; + pixel_row_3[0] = *((uint32_t *) (src_row_ptr_3 + src_x_index_m_1)); + pixel_row_3[1] = (pixel_row_3[0] >> 16) * 0x10001; + } else { // get 4 neighboring rows + pixel_row_0[0] = *((uint32_t *) (src_row_ptr_0 + src_x_index_m_1)); + pixel_row_0[1] = *((uint32_t *) (src_row_ptr_0 + src_x_index_p_1)); + pixel_row_1[0] = *((uint32_t *) (src_row_ptr_1 + src_x_index_m_1)); + pixel_row_1[1] = *((uint32_t *) (src_row_ptr_1 + src_x_index_p_1)); + pixel_row_2[0] = *((uint32_t *) (src_row_ptr_2 + src_x_index_m_1)); + pixel_row_2[1] = *((uint32_t *) (src_row_ptr_2 + src_x_index_p_1)); + pixel_row_3[0] = *((uint32_t *) (src_row_ptr_3 + src_x_index_m_1)); + pixel_row_3[1] = *((uint32_t *) (src_row_ptr_3 + src_x_index_p_1)); + } + + int r_d[4], g_d[4], b_d[4]; + + for (int z = 0; z < 2; z++) { // dual bicubic x step (-1 to +2) + + long r_pixel_row_0 = (pixel_row_0[z] >> 11) & 0x1f001f; + long r_pixel_row_1 = (pixel_row_1[z] >> 11) & 0x1f001f; + long r_pixel_row_2 = (pixel_row_2[z] >> 11) & 0x1f001f; + long r_pixel_row_3 = (pixel_row_3[z] >> 11) & 0x1f001f; + + uint32_t r_a0_col = __QSUB16(r_pixel_row_2, r_pixel_row_0); + uint32_t r_a1_col = __QSUB16(__QSUB16(__QADD16(r_pixel_row_0 << 1, r_pixel_row_2 << 2), r_pixel_row_1 * 5), r_pixel_row_3); + uint32_t r_a2_col = __QSUB16(__QADD16(__QSUB16(r_pixel_row_1 * 3, r_pixel_row_2 * 3), r_pixel_row_3), r_pixel_row_0); + + long r_smuad_a0_a1_0 = __PKHBT(r_a1_col, r_a0_col, 16); + long r_pixel_1_avg_0 = (r_pixel_row_1 << 16) | 0x8000; + r_d[z*2] = ((int32_t) __SMLAD(smuad_dy_dy2, r_smuad_a0_a1_0, __SMLAD(dy3, r_a2_col, r_pixel_1_avg_0))) >> 16; + + long r_smuad_a0_a1_1 = __PKHTB(r_a0_col, r_a1_col, 16); + long r_pixel_1_avg_1 = __PKHTB(r_pixel_row_1, 0x8000, 0); + r_d[(z*2)+1] = ((int32_t) __SMLAD(smuad_dy_dy2, r_smuad_a0_a1_1, __SMLADX(dy3, r_a2_col, r_pixel_1_avg_1))) >> 16; + + long g_pixel_row_0 = (pixel_row_0[z] >> 5) & 0x3f003f; + long g_pixel_row_1 = (pixel_row_1[z] >> 5) & 0x3f003f; + long g_pixel_row_2 = (pixel_row_2[z] >> 5) & 0x3f003f; + long g_pixel_row_3 = (pixel_row_3[z] >> 5) & 0x3f003f; + + uint32_t g_a0_col = __QSUB16(g_pixel_row_2, g_pixel_row_0); + uint32_t g_a1_col = __QSUB16(__QSUB16(__QADD16(g_pixel_row_0 << 1, g_pixel_row_2 << 2), g_pixel_row_1 * 5), g_pixel_row_3); + uint32_t g_a2_col = __QSUB16(__QADD16(__QSUB16(g_pixel_row_1 * 3, g_pixel_row_2 * 3), g_pixel_row_3), g_pixel_row_0); + + long g_smuad_a0_a1_0 = __PKHBT(g_a1_col, g_a0_col, 16); + long g_pixel_1_avg_0 = (g_pixel_row_1 << 16) | 0x8000; + g_d[z*2] = ((int32_t) __SMLAD(smuad_dy_dy2, g_smuad_a0_a1_0, __SMLAD(dy3, g_a2_col, g_pixel_1_avg_0))) >> 16; + + long g_smuad_a0_a1_1 = __PKHTB(g_a0_col, g_a1_col, 16); + long g_pixel_1_avg_1 = __PKHTB(g_pixel_row_1, 0x8000, 0); + g_d[(z*2)+1] = ((int32_t) __SMLAD(smuad_dy_dy2, g_smuad_a0_a1_1, __SMLADX(dy3, g_a2_col, g_pixel_1_avg_1))) >> 16; + + long b_pixel_row_0 = pixel_row_0[z] & 0x1f001f; + long b_pixel_row_1 = pixel_row_1[z] & 0x1f001f; + long b_pixel_row_2 = pixel_row_2[z] & 0x1f001f; + long b_pixel_row_3 = pixel_row_3[z] & 0x1f001f; + + uint32_t b_a0_col = __QSUB16(b_pixel_row_2, b_pixel_row_0); + uint32_t b_a1_col = __QSUB16(__QSUB16(__QADD16(b_pixel_row_0 << 1, b_pixel_row_2 << 2), b_pixel_row_1 * 5), b_pixel_row_3); + uint32_t b_a2_col = __QSUB16(__QADD16(__QSUB16(b_pixel_row_1 * 3, b_pixel_row_2 * 3), b_pixel_row_3), b_pixel_row_0); + + long b_smuad_a0_a1_0 = __PKHBT(b_a1_col, b_a0_col, 16); + long b_pixel_1_avg_0 = (b_pixel_row_1 << 16) | 0x8000; + b_d[z*2] = ((int32_t) __SMLAD(smuad_dy_dy2, b_smuad_a0_a1_0, __SMLAD(dy3, b_a2_col, b_pixel_1_avg_0))) >> 16; + + long b_smuad_a0_a1_1 = __PKHTB(b_a0_col, b_a1_col, 16); + long b_pixel_1_avg_1 = __PKHTB(b_pixel_row_1, 0x8000, 0); + b_d[(z*2)+1] = ((int32_t) __SMLAD(smuad_dy_dy2, b_smuad_a0_a1_1, __SMLADX(dy3, b_a2_col, b_pixel_1_avg_1))) >> 16; + } // for z +#else + int src_x_index_p_2 = src_x_index + 2; + int pixel_x_offests[4]; + + // keep pixels in bounds + if (src_x_index < 0) { + pixel_x_offests[0] = pixel_x_offests[1] = pixel_x_offests[2] = 0; + pixel_x_offests[3] = 1; + } else if (src_x_index == 0) { + pixel_x_offests[0] = pixel_x_offests[1] = 0; + pixel_x_offests[2] = 1; + pixel_x_offests[3] = 2; + } else if (src_x_index == w_limit_m_1) { + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = w_limit_m_1; + pixel_x_offests[2] = pixel_x_offests[3] = w_limit; + } else if (src_x_index >= w_limit) { + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = pixel_x_offests[2] = pixel_x_offests[3] = w_limit; + } else { // get 4 neighboring rows + pixel_x_offests[0] = src_x_index_m_1; + pixel_x_offests[1] = src_x_index; + pixel_x_offests[2] = src_x_index_p_1; + pixel_x_offests[3] = src_x_index_p_2; + } + + int r_d[4], g_d[4], b_d[4]; + + for (int z = 0; z < 4; z++) { // bicubic x step (-1 to +2) + int pixel_0 = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr_0, pixel_x_offests[z]); + int pixel_1 = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr_1, pixel_x_offests[z]); + int pixel_2 = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr_2, pixel_x_offests[z]); + int pixel_3 = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr_3, pixel_x_offests[z]); + + int r0 = pixel_0 >> 11; + int r1 = pixel_1 >> 11; + int r2 = pixel_2 >> 11; + int r3 = pixel_3 >> 11; + + int r_a0 = r2 - r0; + int r_a1 = (r0 << 1) + (r2 << 2) - (5 * r1) - r3; + int r_a2 = (3 * (r1 - r2)) + r3 - r0; + long smuad_r_a0_r_a1 = __PKHBT(r_a1, r_a0, 16); + int r1_avg = (r1 << 16) | 0x8000; + + r_d[z] = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_r_a0_r_a1, (dy3 * r_a2) + r1_avg)) >> 16; + + int g0 = (pixel_0 >> 5) & 0x3F; + int g1 = (pixel_1 >> 5) & 0x3F; + int g2 = (pixel_2 >> 5) & 0x3F; + int g3 = (pixel_3 >> 5) & 0x3F; + + int g_a0 = g2 - g0; + int g_a1 = (g0 << 1) + (g2 << 2) - (5 * g1) - g3; + int g_a2 = (3 * (g1 - g2)) + g3 - g0; + long smuad_g_a0_g_a1 = __PKHBT(g_a1, g_a0, 16); + int g1_avg = (g1 << 16) | 0x8000; + + g_d[z] = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_g_a0_g_a1, (dy3 * g_a2) + g1_avg)) >> 16; + + int b0 = pixel_0 & 0x1F; + int b1 = pixel_1 & 0x1F; + int b2 = pixel_2 & 0x1F; + int b3 = pixel_3 & 0x1F; + + int b_a0 = b2 - b0; + int b_a1 = (b0 << 1) + (b2 << 2) - (5 * b1) - b3; + int b_a2 = (3 * (b1 - b2)) + b3 - b0; + long smuad_b_a0_b_a1 = __PKHBT(b_a1, b_a0, 16); + int b1_avg = (b1 << 16) | 0x8000; + + b_d[z] = ((int32_t) __SMLAD(smuad_dy_dy2, smuad_b_a0_b_a1, (dy3 * b_a2) + b1_avg)) >> 16; + } // for z +#endif + int r_d0 = r_d[0], r_d1 = r_d[1], r_d2 = r_d[2], r_d3 = r_d[3]; + int r_a0 = r_d2 - r_d0; + int r_a1 = (r_d0 << 1) + (r_d2 << 2) - (5 * r_d1) - r_d3; + int r_a2 = (3 * (r_d1 - r_d2)) + r_d3 - r_d0; + long smuad_r_a0_r_a1 = __PKHBT(r_a1, r_a0, 16); + int r_d1_avg = (r_d1 << 16) | 0x8000; + + int g_d0 = g_d[0], g_d1 = g_d[1], g_d2 = g_d[2], g_d3 = g_d[3]; + int g_a0 = g_d2 - g_d0; + int g_a1 = (g_d0 << 1) + (g_d2 << 2) - (5 * g_d1) - g_d3; + int g_a2 = (3 * (g_d1 - g_d2)) + g_d3 - g_d0; + long smuad_g_a0_g_a1 = __PKHBT(g_a1, g_a0, 16); + int g_d1_avg = (g_d1 << 16) | 0x8000; + + int b_d0 = b_d[0], b_d1 = b_d[1], b_d2 = b_d[2], b_d3 = b_d[3]; + int b_a0 = b_d2 - b_d0; + int b_a1 = (b_d0 << 1) + (b_d2 << 2) - (5 * b_d1) - b_d3; + int b_a2 = (3 * (b_d1 - b_d2)) + b_d3 - b_d0; + long smuad_b_a0_b_a1 = __PKHBT(b_a1, b_a0, 16); + int b_d1_avg = (b_d1 << 16) | 0x8000; + + do { // Cache the results of getting the source pixels + // 15-bit fraction to fit a square of it in 32-bits + // pre-calculate the ^1, ^2, and ^3 of the fraction + int dx = ((src_x_accum >> 1) & 0x7FFF); + int dx2 = (dx * dx) >> 15; + int dx3 = (dx2 * dx) >> 15; + long smuad_dx_dx2 = (dx << 16) | dx2; + + long r_pixel = __SMLAD(smuad_dx_dx2, smuad_r_a0_r_a1, (dx3 * r_a2) + r_d1_avg); + + // clamp output + r_pixel = __USAT_ASR(r_pixel, 5, 16); + + long g_pixel = __SMLAD(smuad_dx_dx2, smuad_g_a0_g_a1, (dx3 * g_a2) + g_d1_avg); + + // clamp output + g_pixel = __USAT_ASR(g_pixel, 6, 16); + + long b_pixel = __SMLAD(smuad_dx_dx2, smuad_b_a0_b_a1, (dx3 * b_a2) + b_d1_avg); + + // clamp output + b_pixel = __USAT_ASR(b_pixel, 5, 16); + + int pixel = COLOR_R8_G8_B8_TO_RGB888(r_pixel, g_pixel, b_pixel); + + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); +#endif + } // while y + break; + } + default: { + break; + } + } + } else if (hint & IMAGE_HINT_BILINEAR) { + // Implements the traditional bilinear interpolation algorithm which uses + // a 2x2 filter block with the current pixel centered at (0,0) (C below). + // However, instead of floating point math, it uses integer (fixed point). + // The Cortex-M4/M7 has a hardware floating point unit, so doing FP math + // doesn't take any extra time, but it does take extra time to convert + // the integer pixels to floating point and back to integers again. + // So this allows it to execute more quickly in pure integer math. + // + // +---+---+ + // | C | x | + // +---+---+ + // | x | x | + // +---+---+ + // + switch (src_img->pixfmt) { + case PIXFORMAT_BINARY: { + while (y_not_done) { + int src_y_index = next_src_y_index; + uint32_t *src_row_ptr_0, *src_row_ptr_1; + + // keep row pointers in bounds + if (src_y_index < 0) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, 0); + } else if (src_y_index >= h_limit) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, h_limit); + } else { // get 2 neighboring rows + int src_y_index_p_1 = src_y_index + 1; + src_row_ptr_0 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, src_y_index); + src_row_ptr_1 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, src_y_index_p_1); + } + + do { // Cache the results of getting the source rows + uint32_t * src_row_ptr = ((src_y_accum >> 15) & 0x1) ? src_row_ptr_1 : src_row_ptr_0; + + // Must be called per loop to get the address of the temp buffer to blend with + uint32_t *dst_row_ptr = (uint32_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int pixel_0, pixel_1; + + // keep pixels in bounds + if (src_x_index < 0) { + pixel_0 = pixel_1 = IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, 0); + } else if (src_x_index >= w_limit) { + pixel_0 = pixel_1 = IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, w_limit); + } else { // get 4 neighboring pixels + int src_x_index_p_1 = src_x_index + 1; + pixel_0 = IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, src_x_index); + pixel_1 = IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, src_x_index_p_1); + } + + do { // Cache the results of getting the source pixels + int pixel = ((src_x_accum >> 15) & 0x1) ? pixel_1 : pixel_0; + + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); + } // while y + break; + } + case PIXFORMAT_GRAYSCALE: { + while (y_not_done) { + int src_y_index = next_src_y_index; + uint8_t *src_row_ptr_0, *src_row_ptr_1; + + // keep row pointers in bounds + if (src_y_index < 0) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, 0); + } else if (src_y_index >= h_limit) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, h_limit); + } else { // get 2 neighboring rows + int src_y_index_p_1 = src_y_index + 1; + src_row_ptr_0 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, src_y_index); + src_row_ptr_1 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, src_y_index_p_1); + } + + do { // Cache the results of getting the source rows + // used to mix pixels vertically + long smuad_y = (src_y_accum >> 8) & 0xff; + smuad_y |= (256 - smuad_y) << 16; + + // Must be called per loop to get the address of the temp buffer to blend with + uint8_t *dst_row_ptr = (uint8_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int pixel_00, pixel_10, pixel_01, pixel_11; + + // keep pixels in bounds + if (src_x_index < 0) { + pixel_00 = pixel_10 = src_row_ptr_0[0]; + pixel_01 = pixel_11 = src_row_ptr_1[0]; + } else if (src_x_index >= w_limit) { + pixel_00 = pixel_10 = src_row_ptr_0[w_limit]; + pixel_01 = pixel_11 = src_row_ptr_1[w_limit]; + } else { // get 4 neighboring pixels + int src_x_index_p_1 = src_x_index + 1; + pixel_00 = src_row_ptr_0[src_x_index]; pixel_10 = src_row_ptr_0[src_x_index_p_1]; + pixel_01 = src_row_ptr_1[src_x_index]; pixel_11 = src_row_ptr_1[src_x_index_p_1]; + } + + long vertical_avg_0 = (pixel_00 << 16) | pixel_01; + int pixel_l = __SMLAD(smuad_y, vertical_avg_0, 128) >> 8; // vertically average + + long vertical_avg_1 = (pixel_10 << 16) | pixel_11; + int pixel_r = __SMLAD(smuad_y, vertical_avg_1, 128) >> 8; // vertically average + + long horizontal_avg = (pixel_l << 16) | pixel_r; + + do { // Cache the results of getting the source pixels + // used to mix pixels horizontally + long smuad_x = (src_x_accum >> 8) & 0xff; + smuad_x |= (256 - smuad_x) << 16; + + int pixel = __SMLAD(smuad_x, horizontal_avg, 128) >> 8; // horizontally average + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); + } // while y + break; + } + case PIXFORMAT_RGB565: { + while (y_not_done) { + int src_y_index = next_src_y_index; + uint16_t *src_row_ptr_0, *src_row_ptr_1; + + // keep row pointers in bounds + if (src_y_index < 0) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, 0); + } else if (src_y_index >= h_limit) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, h_limit); + } else { // get 2 neighboring rows + int src_y_index_p_1 = src_y_index + 1; + src_row_ptr_0 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, src_y_index); + src_row_ptr_1 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, src_y_index_p_1); + } + + do { // Cache the results of getting the source rows + // used to mix pixels vertically + long smuad_y = (src_y_accum >> 11) & 0x1f; + smuad_y |= (32 - smuad_y) << 16; + + // Must be called per loop to get the address of the temp buffer to blend with + uint16_t *dst_row_ptr = (uint16_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int pixel_00, pixel_10, pixel_01, pixel_11; + + // keep pixels in bounds + if (src_x_index < 0) { + pixel_00 = pixel_10 = src_row_ptr_0[0]; + pixel_01 = pixel_11 = src_row_ptr_1[0]; + } else if (src_x_index >= w_limit) { + pixel_00 = pixel_10 = src_row_ptr_0[w_limit]; + pixel_01 = pixel_11 = src_row_ptr_1[w_limit]; + } else { // get 4 neighboring pixels + int src_x_index_p_1 = src_x_index + 1; + pixel_00 = src_row_ptr_0[src_x_index]; pixel_10 = src_row_ptr_0[src_x_index_p_1]; + pixel_01 = src_row_ptr_1[src_x_index]; pixel_11 = src_row_ptr_1[src_x_index_p_1]; + } + + const long mask_r = 0x7c007c00, mask_g = 0x07e007e0, mask_b = 0x001f001f; + const long avg_rb = 0x4010, avg_g = 0x200; + + uint32_t rgb_l = (pixel_00 << 16) | pixel_01; + long rb_l = ((rgb_l >> 1) & mask_r) | (rgb_l & mask_b); + long g_l = rgb_l & mask_g; + int rb_out_l = (__SMLAD(smuad_y, rb_l, avg_rb) >> 5) & 0x7c1f; + int g_out_l = (__SMLAD(smuad_y, g_l, avg_g) >> 5) & 0x07e0; + + uint32_t rgb_r = (pixel_10 << 16) | pixel_11; + long rb_r = ((rgb_r >> 1) & mask_r) | (rgb_r & mask_b); + long g_r = rgb_r & mask_g; + int rb_out_r = (__SMLAD(smuad_y, rb_r, avg_rb) >> 5) & 0x7c1f; + int g_out_r = (__SMLAD(smuad_y, g_r, avg_g) >> 5) & 0x07e0; + + long rb = (rb_out_l << 16) | rb_out_r; + long g = (g_out_l << 16) | g_out_r; + + do { // Cache the results of getting the source pixels + // used to mix pixels horizontally + long smuad_x = (src_x_accum >> 11) & 0x1f; + smuad_x |= (32 - smuad_x) << 16; + + int rb_out = __SMLAD(smuad_x, rb, avg_rb) >> 5; + int g_out = __SMLAD(smuad_x, g, avg_g) >> 5; + int pixel = ((rb_out << 1) & 0xf800) | (g_out & 0x07e0) | (rb_out & 0x001f); + + IMAGE_PUT_RGB565_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); + } // while y + break; + } + case PIXFORMAT_RGB888: { //add success! + while (y_not_done) { +#if 1 + int src_y_index = next_src_y_index; + pixel24_t *src_row_ptr_0, *src_row_ptr_1; + + // keep row pointers in bounds + if (src_y_index < 0) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, 0); + } else if (src_y_index >= h_limit) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, h_limit); + } else { // get 2 neighboring rows + int src_y_index_p_1 = src_y_index + 1; + src_row_ptr_0 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index); + src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_p_1); + } + + do { // Cache the results of getting the source rows + // used to mix pixels vertically + long smuad_y1 = (src_y_accum >> 8) & 0xff; + long smuad_y0 = (256 - smuad_y1); + + // Must be called per loop to get the address of the temp buffer to blend with + pixel24_t *dst_row_ptr = (pixel24_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { //先对y方向进行插值,然后再对x方向插值请 + int src_x_index = next_src_x_index; + int pixel_00, pixel_10, pixel_01, pixel_11; + + // keep pixels in bounds + if (src_x_index < 0) { + pixel_00 = pixel_10 = pixel24232(src_row_ptr_0[0]); + pixel_01 = pixel_11 = pixel24232(src_row_ptr_1[0]); + } else if (src_x_index >= w_limit) { + pixel_00 = pixel_10 = pixel24232(src_row_ptr_0[w_limit]); + pixel_01 = pixel_11 = pixel24232(src_row_ptr_1[w_limit]); + } else { // get 4 neighboring pixels + int src_x_index_p_1 = src_x_index + 1; + pixel_00 = pixel24232(src_row_ptr_0[src_x_index]); pixel_10 = pixel24232(src_row_ptr_0[src_x_index_p_1]); + pixel_01 = pixel24232(src_row_ptr_1[src_x_index]); pixel_11 = pixel24232(src_row_ptr_1[src_x_index_p_1]); + } + + const long avg_r = 128, avg_g = 128, avg_b = 128; + + uint8_t l_piex_r_tmp0 = COLOR_RGB888_TO_R8(pixel_00), l_piex_r_tmp1 = COLOR_RGB888_TO_R8(pixel_01); + uint8_t l_piex_g_tmp0 = COLOR_RGB888_TO_G8(pixel_00), l_piex_g_tmp1 = COLOR_RGB888_TO_G8(pixel_01); + uint8_t l_piex_b_tmp0 = COLOR_RGB888_TO_B8(pixel_00), l_piex_b_tmp1 = COLOR_RGB888_TO_B8(pixel_01); + + + uint8_t l_piex_r_tmp2 = ((smuad_y0 * l_piex_r_tmp0 + smuad_y1 * l_piex_r_tmp1 + avg_r) >> 8); + uint8_t l_piex_g_tmp2 = ((smuad_y0 * l_piex_g_tmp0 + smuad_y1 * l_piex_g_tmp1 + avg_g) >> 8); + uint8_t l_piex_b_tmp2 = ((smuad_y0 * l_piex_b_tmp0 + smuad_y1 * l_piex_b_tmp1 + avg_b) >> 8); + + uint8_t r_piex_r_tmp0 = COLOR_RGB888_TO_R8(pixel_10), r_piex_r_tmp1 = COLOR_RGB888_TO_R8(pixel_11); + uint8_t r_piex_g_tmp0 = COLOR_RGB888_TO_G8(pixel_10), r_piex_g_tmp1 = COLOR_RGB888_TO_G8(pixel_11); + uint8_t r_piex_b_tmp0 = COLOR_RGB888_TO_B8(pixel_10), r_piex_b_tmp1 = COLOR_RGB888_TO_B8(pixel_11); + + uint8_t r_piex_r_tmp2 = ((smuad_y0 * r_piex_r_tmp0 + smuad_y1 * r_piex_r_tmp1 + avg_r) >> 8); + uint8_t r_piex_g_tmp2 = ((smuad_y0 * r_piex_g_tmp0 + smuad_y1 * r_piex_g_tmp1 + avg_g) >> 8); + uint8_t r_piex_b_tmp2 = ((smuad_y0 * r_piex_b_tmp0 + smuad_y1 * r_piex_b_tmp1 + avg_b) >> 8); + + + do { // Cache the results of getting the source pixels + // used to mix pixels horizontally + long smuad_x1 = (src_x_accum >> 8) & 0xff; + long smuad_x0 = 256 - smuad_x0; + + uint8_t _piex_r_tmp3 = ((smuad_x0 * l_piex_r_tmp2 + smuad_x1 * r_piex_r_tmp2 + avg_r) >> 8); + uint8_t _piex_g_tmp3 = ((smuad_x0 * l_piex_g_tmp2 + smuad_x1 * r_piex_g_tmp2 + avg_g) >> 8); + uint8_t _piex_b_tmp3 = ((smuad_x0 * l_piex_b_tmp2 + smuad_x1 * r_piex_b_tmp2 + avg_b) >> 8); + + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr, dst_x, COLOR_R8_G8_B8_TO_RGB888(_piex_r_tmp3, _piex_g_tmp3, _piex_b_tmp3)); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x 结束完行 + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); +#else + int src_y_index = next_src_y_index; + pixel24_t *src_row_ptr_0, *src_row_ptr_1; + + // keep row pointers in bounds + if (src_y_index < 0) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, 0); + } else if (src_y_index >= h_limit) { + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, h_limit); + } else { // get 2 neighboring rows + int src_y_index_p_1 = src_y_index + 1; + src_row_ptr_0 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index); + src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index_p_1); + } + + do { // Cache the results of getting the source rows + // used to mix pixels vertically + long smuad_y = (src_y_accum >> 11) & 0x1f; + smuad_y |= (32 - smuad_y) << 16; + + // Must be called per loop to get the address of the temp buffer to blend with + pixel24_t *dst_row_ptr = (pixel24_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int pixel_00, pixel_10, pixel_01, pixel_11; + + // keep pixels in bounds + if (src_x_index < 0) { + pixel_00 = pixel_10 = pixel24232(src_row_ptr_0[0]); + pixel_01 = pixel_11 = pixel24232(src_row_ptr_1[0]); + } else if (src_x_index >= w_limit) { + pixel_00 = pixel_10 = pixel24232(src_row_ptr_0[w_limit]); + pixel_01 = pixel_11 = pixel24232(src_row_ptr_1[w_limit]); + } else { // get 4 neighboring pixels + int src_x_index_p_1 = src_x_index + 1; + pixel_00 = pixel24232(src_row_ptr_0[src_x_index]); pixel_10 = pixel24232(src_row_ptr_0[src_x_index_p_1]); + pixel_01 = pixel24232(src_row_ptr_1[src_x_index]); pixel_11 = pixel24232(src_row_ptr_1[src_x_index_p_1]); + } + + const long mask_r = 0x0000ff00, mask_g = 0x00ff0000, mask_b = 0xff000000; + const long avg_rb = 0x4010, avg_g = 0x200; + + uint32_t rgb_l = (pixel_00 << 16) | pixel_01; //左像素 + long rb_l = ((rgb_l >> 1) & mask_r) | (rgb_l & mask_b); + long g_l = rgb_l & mask_g; + int rb_out_l = (__SMLAD(smuad_y, rb_l, avg_rb) >> 5) & 0x7c1f; + int g_out_l = (__SMLAD(smuad_y, g_l, avg_g) >> 5) & 0x07e0; + + uint32_t rgb_r = (pixel_10 << 16) | pixel_11; //右像素 + long rb_r = ((rgb_r >> 1) & mask_r) | (rgb_r & mask_b); + long g_r = rgb_r & mask_g; + int rb_out_r = (__SMLAD(smuad_y, rb_r, avg_rb) >> 5) & 0x7c1f; + int g_out_r = (__SMLAD(smuad_y, g_r, avg_g) >> 5) & 0x07e0; + + long rb = (rb_out_l << 16) | rb_out_r; + long g = (g_out_l << 16) | g_out_r; + + do { // Cache the results of getting the source pixels + // used to mix pixels horizontally + long smuad_x = (src_x_accum >> 11) & 0x1f; + smuad_x |= (32 - smuad_x) << 16; + + int rb_out = __SMLAD(smuad_x, rb, avg_rb) >> 5; + int g_out = __SMLAD(smuad_x, g, avg_g) >> 5; + int pixel = ((rb_out << 1) & 0xf800) | (g_out & 0x07e0) | (rb_out & 0x001f); + + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); +#endif + } // while y + break; + } + default: { + break; + } + } + } else if (no_scaling_nearest_neighbor) { // copy + if (dst_img->data == src_img->data) { // In-Place + switch (src_img->pixfmt) { + case PIXFORMAT_BINARY: { + while (y_not_done) { + uint32_t *src_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, next_src_y_index); + // Must be called per loop to get the address of the temp buffer to blend with + uint32_t *dst_row_ptr = (uint32_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, next_src_x_index); + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_GRAYSCALE: + // Re-use grayscale for bayer. + case PIXFORMAT_BAYER_ANY: { + while (y_not_done) { + uint8_t *src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, next_src_y_index); + // Must be called per loop to get the address of the temp buffer to blend with + uint8_t *dst_row_ptr = (uint8_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(src_row_ptr, next_src_x_index); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_RGB565: + // Re-use RGB565 for yuv. + case PIXFORMAT_YUV_ANY: { + while (y_not_done) { + uint16_t *src_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, next_src_y_index); + // Must be called per loop to get the address of the temp buffer to blend with + uint16_t *dst_row_ptr = (uint16_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(src_row_ptr, next_src_x_index); + IMAGE_PUT_RGB565_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_RGB888: { + while (y_not_done) { + pixel24_t *src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, next_src_y_index); + // Must be called per loop to get the address of the temp buffer to blend with + pixel24_t *dst_row_ptr = (pixel24_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr, next_src_x_index); + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + default: { + break; + } + } + } else { // Out-of-Place + switch (src_img->pixfmt) { + case PIXFORMAT_BINARY: { + while (y_not_done) { + uint32_t *src_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, next_src_y_index); + imlib_draw_row_put_row_buffer(&imlib_draw_row_data, src_row_ptr); + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_GRAYSCALE: { + while (y_not_done) { + uint8_t *src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, next_src_y_index); + imlib_draw_row_put_row_buffer(&imlib_draw_row_data, src_row_ptr); + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_RGB565: { + while (y_not_done) { + uint16_t *src_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, next_src_y_index); + imlib_draw_row_put_row_buffer(&imlib_draw_row_data, src_row_ptr); + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_RGB888: { + while (y_not_done) { + pixel24_t *src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, next_src_y_index); + imlib_draw_row_put_row_buffer(&imlib_draw_row_data, src_row_ptr); + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_BAYER_ANY: { + while (y_not_done) { + switch (new_not_mutable_pixfmt) { + case PIXFORMAT_MUTABLE_ANY: { + imlib_debayer_line(dst_x_start, dst_x_end, next_src_y_index, + imlib_draw_row_get_row_buffer(&imlib_draw_row_data), + new_not_mutable_pixfmt, src_img); + break; + } + case PIXFORMAT_BAYER_ANY: { // Bayer images have the same shape as GRAYSCALE. + uint8_t *src_row_ptr = IMAGE_COMPUTE_BAYER_PIXEL_ROW_PTR(src_img, next_src_y_index); + imlib_draw_row_put_row_buffer(&imlib_draw_row_data, src_row_ptr); + break; + } + default : { + break; + } + } + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + case PIXFORMAT_YUV_ANY: { + while (y_not_done) { + switch (new_not_mutable_pixfmt) { + case PIXFORMAT_MUTABLE_ANY: { + imlib_deyuv_line(dst_x_start, dst_x_end, next_src_y_index, + imlib_draw_row_get_row_buffer(&imlib_draw_row_data), + new_not_mutable_pixfmt, src_img); + break; + } + case PIXFORMAT_YUV_ANY: { // YUV images have the same shape as RGB565. + uint16_t *src_row_ptr = IMAGE_COMPUTE_YUV_PIXEL_ROW_PTR(src_img, next_src_y_index); + imlib_draw_row_put_row_buffer(&imlib_draw_row_data, src_row_ptr); + break; + } + default : { + break; + } + } + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } // while y + break; + } + default: { + break; + } + } + } + } else { // nearest neighbor + switch (src_img->pixfmt) { + case PIXFORMAT_BINARY: { + while (y_not_done) { + int src_y_index = next_src_y_index; + uint32_t *src_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src_img, src_y_index); + + do { // Cache the results of getting the source row + // Must be called per loop to get the address of the temp buffer to blend with + uint32_t *dst_row_ptr = (uint32_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, src_x_index); + + do { // Cache the results of getting the source pixel + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); + } // while y + break; + } + case PIXFORMAT_GRAYSCALE: + // Re-use grayscale for bayer. + case PIXFORMAT_BAYER_ANY: { + while (y_not_done) { + int src_y_index = next_src_y_index; + uint8_t *src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src_img, src_y_index); + + do { // Cache the results of getting the source row + // Must be called per loop to get the address of the temp buffer to blend with + uint8_t *dst_row_ptr = (uint8_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(src_row_ptr, src_x_index); + + do { // Cache the results of getting the source pixel + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); + } // while y + break; + } + case PIXFORMAT_RGB565: + // Re-use RGB565 for yuv. + case PIXFORMAT_YUV_ANY: { + while (y_not_done) { + int src_y_index = next_src_y_index; + uint16_t *src_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src_img, src_y_index); + + do { // Cache the results of getting the source row + // Must be called per loop to get the address of the temp buffer to blend with + uint16_t *dst_row_ptr = (uint16_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(src_row_ptr, src_x_index); + + do { // Cache the results of getting the source pixel + IMAGE_PUT_RGB565_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); + } // while y + break; + } + case PIXFORMAT_RGB888: { + while (y_not_done) { + int src_y_index = next_src_y_index; + pixel24_t *src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src_img, src_y_index); + + do { // Cache the results of getting the source row + // Must be called per loop to get the address of the temp buffer to blend with + pixel24_t *dst_row_ptr = (pixel24_t *) imlib_draw_row_get_row_buffer(&imlib_draw_row_data); + + // X loop iteration variables + int dst_x = dst_x_reset; + long src_x_accum = src_x_accum_reset; + int next_src_x_index = src_x_accum >> 16; + int x = dst_x_start; + bool x_not_done = x < dst_x_end; + + while (x_not_done) { + int src_x_index = next_src_x_index; + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr, src_x_index); + + do { // Cache the results of getting the source pixel + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr, dst_x, pixel); + + // Increment offsets + dst_x += dst_delta_x; + src_x_accum += src_x_frac; + next_src_x_index = src_x_accum >> 16; + x_not_done = ++x < dst_x_end; + } while (x_not_done && (src_x_index == next_src_x_index)); + } // while x + + imlib_draw_row(dst_x_start, dst_x_end, dst_y, &imlib_draw_row_data); + + // Increment offsets + dst_y += dst_delta_y; + src_y_accum += src_y_frac; + next_src_y_index = src_y_accum >> 16; + y_not_done = ++y < dst_y_end; + } while (y_not_done && (src_y_index == next_src_y_index)); + } // while y + break; + } + default: { + break; + } + } + } + + imlib_draw_row_teardown(&imlib_draw_row_data); + if (&new_src_img == src_img) fb_free(new_src_img.data); +} + +#ifdef IMLIB_ENABLE_FLOOD_FILL +void imlib_flood_fill(image_t *img, int x, int y, + float seed_threshold, float floating_threshold, + int c, bool invert, bool clear_background, image_t *mask) +{ + if ((0 <= x) && (x < img->w) && (0 <= y) && (y < img->h)) { + image_t out; + out.w = img->w; + out.h = img->h; + out.pixfmt = PIXFORMAT_BINARY; + // out.data = fb_alloc0(image_size(&out), FB_ALLOC_NO_HINT); + out.data = xalloc0(image_size(&out)); + + if (mask) { + for (int y = 0, yy = out.h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&out, y); + for (int x = 0, xx = out.w; x < xx; x++) { + if (image_get_mask_pixel(mask, x, y)) IMAGE_SET_BINARY_PIXEL_FAST(row_ptr, x); + } + } + } + + int color_seed_threshold = 0; + int color_floating_threshold = 0; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + color_seed_threshold = fast_floorf(seed_threshold * COLOR_BINARY_MAX); + color_floating_threshold = fast_floorf(floating_threshold * COLOR_BINARY_MAX); + break; + } + case PIXFORMAT_GRAYSCALE: { + color_seed_threshold = fast_floorf(seed_threshold * COLOR_GRAYSCALE_MAX); + color_floating_threshold = fast_floorf(floating_threshold * COLOR_GRAYSCALE_MAX); + break; + } + case PIXFORMAT_RGB565: { + color_seed_threshold = COLOR_R5_G6_B5_TO_RGB565(fast_floorf(seed_threshold * COLOR_R5_MAX), + fast_floorf(seed_threshold * COLOR_G6_MAX), + fast_floorf(seed_threshold * COLOR_B5_MAX)); + color_floating_threshold = COLOR_R5_G6_B5_TO_RGB565(fast_floorf(floating_threshold * COLOR_R5_MAX), + fast_floorf(floating_threshold * COLOR_G6_MAX), + fast_floorf(floating_threshold * COLOR_B5_MAX)); + + break; + } + case PIXFORMAT_RGB888: { + color_seed_threshold = COLOR_R8_G8_B8_TO_RGB888(fast_roundf(seed_threshold * COLOR_R8_MAX), + fast_roundf(seed_threshold * COLOR_G8_MAX), + fast_roundf(seed_threshold * COLOR_B8_MAX)); + color_floating_threshold = COLOR_R8_G8_B8_TO_RGB888(fast_roundf(floating_threshold * COLOR_R8_MAX), + fast_roundf(floating_threshold * COLOR_G8_MAX), + fast_roundf(floating_threshold * COLOR_B8_MAX)); + break; + } + default: { + break; + } + } + + imlib_flood_fill_int(&out, img, x, y, color_seed_threshold, color_floating_threshold, NULL, NULL); + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = 0, yy = out.h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&out, y); + for (int x = 0, xx = out.w; x < xx; x++) { + if (IMAGE_GET_BINARY_PIXEL_FAST(out_row_ptr, x) ^ invert) { + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, c); + } else if (clear_background) { + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, 0); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = 0, yy = out.h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&out, y); + for (int x = 0, xx = out.w; x < xx; x++) { + if (IMAGE_GET_BINARY_PIXEL_FAST(out_row_ptr, x) ^ invert) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, c); + } else if (clear_background) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, 0); + } + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = 0, yy = out.h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&out, y); + for (int x = 0, xx = out.w; x < xx; x++) { + if (IMAGE_GET_BINARY_PIXEL_FAST(out_row_ptr, x) ^ invert) { + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, c); + } else if (clear_background) { + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, 0); + } + } + } + break; + } + case PIXFORMAT_RGB888: { + for (int y = 0, yy = out.h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&out, y); + for (int x = 0, xx = out.w; x < xx; x++) { + if (IMAGE_GET_BINARY_PIXEL_FAST(out_row_ptr, x) ^ invert) { + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr, x, c); + } else if (clear_background) { + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr, x, 0); + } + } + } + break; + } + default: { + break; + } + } + + // fb_free(out.data); + xfree(out.data); + } +} +#endif // IMLIB_ENABLE_FLOOD_FILL diff --git a/github_source/minicv2/src/edge.c b/github_source/minicv2/src/edge.c new file mode 100644 index 0000000..498d4ae --- /dev/null +++ b/github_source/minicv2/src/edge.c @@ -0,0 +1,157 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Edge Detection. + */ +#include +#include +#include +#include "imlib.h" +// #include "fb_alloc.h" +#ifdef IMLIB_ENABLE_BINARY_OPS + +typedef struct gvec { + uint16_t t; + uint16_t g; +} gvec_t; + +void imlib_edge_simple(image_t *src, rectangle_t *roi, int low_thresh, int high_thresh) +{ + imlib_morph(src, 1, kernel_high_pass_3, 1.0f, 0.0f, false, 0, false, NULL); + list_t thresholds; + imlib_list_init(&thresholds, sizeof(color_thresholds_list_lnk_data_t)); + color_thresholds_list_lnk_data_t lnk_data; + lnk_data.LMin=low_thresh; + lnk_data.LMax=high_thresh; + list_push_back(&thresholds, &lnk_data); + imlib_binary(src, src, &thresholds, false, false, NULL); + list_free(&thresholds); + imlib_erode(src, 1, 2, NULL); +} + +void imlib_edge_canny(image_t *src, rectangle_t *roi, int low_thresh, int high_thresh) +{ + int w = src->w; + + gvec_t *gm = xalloc(roi->w*roi->h*sizeof*gm); + + //1. Noise Reduction with a Gaussian filter + imlib_sepconv3(src, kernel_gauss_3, 1.0f/16.0f, 0.0f); + + //2. Finding Image Gradients + for (int gy=1, y=roi->y+1; yy+roi->h-1; y++, gy++) { + for (int gx=1, x=roi->x+1; xx+roi->w-1; x++, gx++) { + int vx=0, vy=0; + // sobel kernel in the horizontal direction + vx = src->data [(y-1)*w+x-1] + - src->data [(y-1)*w+x+1] + + (src->data[(y+0)*w+x-1]<<1) + - (src->data[(y+0)*w+x+1]<<1) + + src->data [(y+1)*w+x-1] + - src->data [(y+1)*w+x+1]; + + // sobel kernel in the vertical direction + vy = src->data [(y-1)*w+x-1] + + (src->data[(y-1)*w+x+0]<<1) + + src->data [(y-1)*w+x+1] + - src->data [(y+1)*w+x-1] + - (src->data[(y+1)*w+x+0]<<1) + - src->data [(y+1)*w+x+1]; + + // Find magnitude + int g = (int) fast_sqrtf(vx*vx + vy*vy); + // Find the direction and round angle to 0, 45, 90 or 135 + int t = (int) fast_fabsf((atan2f(vy, vx)*180.0f/M_PI)); + if (t < 22) { + t = 0; + } else if (t < 67) { + t = 45; + } else if (t < 112) { + t = 90; + } else if (t < 160) { + t = 135; + } else if (t <= 180) { + t = 0; + } + + gm[gy*roi->w+gx].t = t; + gm[gy*roi->w+gx].g = g; + } + } + + // 3. Hysteresis Thresholding + // 4. Non-maximum Suppression and output + for (int gy=0, y=roi->y; yy+roi->h; y++, gy++) { + for (int gx=0, x=roi->x; xx+roi->w; x++, gx++) { + int i = y*w+x; + gvec_t *va=NULL, *vb=NULL, *vc = &gm[gy*roi->w+gx]; + + // Clear the borders + if (y == (roi->y) || y == (roi->y+roi->h-1) || + x == (roi->x) || x == (roi->x+roi->w-1)) { + src->data[i] = 0; + continue; + } + + if (vc->g < low_thresh) { + // Not an edge + src->data[i] = 0; + continue; + // Check if strong or weak edge + } else if (vc->g >= high_thresh || + gm[(gy-1)*roi->w+(gx-1)].g >= high_thresh || + gm[(gy-1)*roi->w+(gx+0)].g >= high_thresh || + gm[(gy-1)*roi->w+(gx+1)].g >= high_thresh || + gm[(gy+0)*roi->w+(gx-1)].g >= high_thresh || + gm[(gy+0)*roi->w+(gx+1)].g >= high_thresh || + gm[(gy+1)*roi->w+(gx-1)].g >= high_thresh || + gm[(gy+1)*roi->w+(gx+0)].g >= high_thresh || + gm[(gy+1)*roi->w+(gx+1)].g >= high_thresh) { + vc->g = vc->g; + } else { // Not an edge + src->data[i] = 0; + continue; + } + + switch (vc->t) { + case 0: { + va = &gm[(gy+0)*roi->w+(gx-1)]; + vb = &gm[(gy+0)*roi->w+(gx+1)]; + break; + } + + case 45: { + va = &gm[(gy+1)*roi->w+(gx-1)]; + vb = &gm[(gy-1)*roi->w+(gx+1)]; + break; + } + + case 90: { + va = &gm[(gy+1)*roi->w+(gx+0)]; + vb = &gm[(gy-1)*roi->w+(gx+0)]; + break; + } + + case 135: { + va = &gm[(gy+1)*roi->w+(gx+1)]; + vb = &gm[(gy-1)*roi->w+(gx-1)]; + break; + } + } + + if (!(vc->g > va->g && vc->g > vb->g)) { + src->data[i] = 0; + } else { + src->data[i] = 255; + } + } + } + + xfree(gm); +} +#endif diff --git a/github_source/minicv2/src/eye.c b/github_source/minicv2/src/eye.c new file mode 100644 index 0000000..fc5e2b9 --- /dev/null +++ b/github_source/minicv2/src/eye.c @@ -0,0 +1,137 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Pupil localization using image gradients. See Fabian Timm's paper for details. + */ +#include "imlib.h" +#include "xalloc.h" +#include "fmath.h" + +static void find_gradients(image_t *src, array_t *gradients, int x_off, int y_off, int box_w, int box_h) +{ + for (int y=y_off; yw; + // sobel_kernel + vx = src->data[(y+0)*w+x+0] + - src->data[(y+0)*w+x+2] + + (src->data[(y+1)*w+x+0]<<1) + - (src->data[(y+1)*w+x+2]<<1) + + src->data[(y+2)*w+x+0] + - src->data[(y+2)*w+x+2]; + + // sobel_kernel + vy = src->data[(y+0)*w+x+0] + + (src->data[(y+0)*w+x+1]<<1) + + src->data[(y+0)*w+x+2] + - src->data[(y+2)*w+x+0] + - (src->data[(y+2)*w+x+1]<<1) + - src->data[(y+2)*w+x+2]; + + float m = fast_sqrtf(vx*vx+vy*vy); + if (m>200) { + vec_t *v = xalloc(sizeof(vec_t)); + v->m = m; + v->x = vx/m; + v->y = vy/m; + v->cx = x+1; + v->cy = y+1; + array_push_back(gradients, v); + } + } + } +} + +// TODO use the gradients median not average +static void filter_gradients(array_t *gradients) +{ + float total_m=0.0f; + for (int i=0; im; + } + + float avg_m = total_m/array_length(gradients); + + for (int i=0; im-avg_m) * (v->m-avg_m); + if (fast_sqrtf(diff)>100) { + array_erase(gradients, i); + } + } +} + +static void find_iris(image_t *src, array_t *gradients, int x_off, int y_off, int box_w, int box_h, point_t *e) +{ + int max_x=0; + int max_y=0; + float max_dot = 0.0f; + + for (int y=y_off; ycx, y-v->cy}; + + // normalize d vector + float m = fast_sqrtf(d.x*d.x+d.y*d.y); + d.x = d.x/m; + d.y = d.y/m; + + // compute the dot product d.g + float t = (d.x*v->x)+(d.y*v->y); + + // d,g should point the same direction + if (t>0.0) { + // dark centres are more likely to be pupils than + // bright centres, so we use the grayscale value as weight. + sum_dot += t*t*(255-src->data[y*src->w+x]); + } + } + sum_dot=sum_dot/array_length(gradients); + + if (sum_dot > max_dot) { + max_dot = sum_dot; + max_x = x; + max_y = y; + } + } + } + + e->x = max_x; + e->y = max_y; +} + +// This function should be called on an ROI detected with the eye Haar cascade. +void imlib_find_iris(image_t *src, point_t *iris, rectangle_t *roi) +{ + array_t *iris_gradients; + array_alloc(&iris_gradients, xfree); + + // Tune these offsets to skip eyebrows and reduce window size + int box_w = roi->w-((int)(0.15f*roi->w)); + int box_h = roi->h-((int)(0.40f*roi->h)); + int x_off = roi->x+((int)(0.15f*roi->w)); + int y_off = roi->y+((int)(0.40f*roi->h)); + + // find gradients with strong magnitudes + find_gradients(src, iris_gradients, x_off, y_off, box_w, box_h); + + // filter gradients + filter_gradients(iris_gradients); + + // search for iriss + find_iris(src, iris_gradients, x_off, y_off, box_w, box_h, iris); + + array_free(iris_gradients); +} diff --git a/github_source/minicv2/src/fast.c b/github_source/minicv2/src/fast.c new file mode 100644 index 0000000..79448ef --- /dev/null +++ b/github_source/minicv2/src/fast.c @@ -0,0 +1,6071 @@ +/* + * NOTE: This code is mostly auto-generated. + * See https://www.edwardrosten.com/work/fast.html + * + * Copyright (c) 2006, 2008, 2009, 2010 Edward Rosten 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 name of the University of Cambridge nor the names of + * its 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. + */ +#include +#include "imlib.h" +#include "xalloc.h" +#include "fb_alloc.h" +// #include "gc.h" + +#ifdef IMLIB_ENABLE_FAST + +#define MAX_ROW (480) +#define MAX_CORNERS (2000) +#define Compare(X, Y) ((X)>=(Y)) + +typedef struct { + uint16_t x; + uint16_t y; + uint16_t score; +} corner_t; + +static int pixel[16]; +static corner_t *fast9_detect(image_t *image, rectangle_t *roi, int *n_corners, int b); +static void fast9_score(image_t *image, corner_t *corners, int num_corners, int b); +static void nonmax_suppression(corner_t *corners, int num_corners, array_t *keypoints); + +static kp_t *alloc_keypoint(uint16_t x, uint16_t y, uint16_t score) +{ + // Note must set keypoint descriptor to zeros + kp_t *kpt = xalloc0(sizeof*kpt); + kpt->x = x; + kpt->y = y; + kpt->score = score; + return kpt; +} + +static void make_offsets(int pixel[], int row_stride) +{ + pixel[0] = 0 + row_stride * 3; + pixel[1] = 1 + row_stride * 3; + pixel[2] = 2 + row_stride * 2; + pixel[3] = 3 + row_stride * 1; + pixel[4] = 3 + row_stride * 0; + pixel[5] = 3 + row_stride * -1; + pixel[6] = 2 + row_stride * -2; + pixel[7] = 1 + row_stride * -3; + pixel[8] = 0 + row_stride * -3; + pixel[9] = -1 + row_stride * -3; + pixel[10] = -2 + row_stride * -2; + pixel[11] = -3 + row_stride * -1; + pixel[12] = -3 + row_stride * 0; + pixel[13] = -3 + row_stride * 1; + pixel[14] = -2 + row_stride * 2; + pixel[15] = -1 + row_stride * 3; +} + +void fast_detect(image_t *image, array_t *keypoints, int threshold, rectangle_t *roi) +{ + int num_corners=0; + make_offsets(pixel, image->w); + + // Find corners + corner_t *corners = fast9_detect(image, roi, &num_corners, threshold); + if (num_corners) { + // Score corners + fast9_score(image, corners, num_corners, threshold); + // Non-max suppression + nonmax_suppression(corners, num_corners, keypoints); + } + + // Free corners; + fb_free(corners); +} + +static void nonmax_suppression(corner_t *corners, int num_corners, array_t *keypoints) +{ + // gc_info_t info; + + int last_row; + int16_t row_start[MAX_ROW+1]; + const int sz = num_corners; + + /* Point above points (roughly) to the pixel above + the one of interest, if there is a feature there.*/ + int point_above = 0; + int point_below = 0; + + /* Find where each row begins (the corners are output in raster scan order). + A beginning of -1 signifies that there are no corners on that row. */ + last_row = corners[sz-1].y; + + for(int i=0; iy != prev_row) { + row_start[c->y] = i; + prev_row = c->y; + } + } + + for(int i=0; i 0) { + if (corners[i-1].x == pos.x-1 && corners[i-1].y == pos.y && Compare(corners[i-1].score, score)) { + goto nonmax; + } + } + + /*Check right*/ + if (i < (sz - 1)) { + if (corners[i+1].x == pos.x+1 && corners[i+1].y == pos.y && Compare(corners[i+1].score, score)) { + goto nonmax; + } + } + + /*Check above (if there is a valid row above)*/ + if (pos.y != 0 && row_start[pos.y - 1] != -1) { + /*Make sure that current point_above is one row above.*/ + if(corners[point_above].y < pos.y - 1) + point_above = row_start[pos.y-1]; + + /*Make point_above point to the first of the pixels above the current point, if it exists.*/ + for (; corners[point_above].y < pos.y && corners[point_above].x < pos.x - 1; point_above++) { + + } + + for (int j=point_above; corners[j].y < pos.y && corners[j].x <= pos.x + 1; j++) { + int x = corners[j].x; + if( (x == pos.x - 1 || x ==pos.x || x == pos.x+1) && Compare(corners[j].score, score)) + goto nonmax; + } + } + + /*Check below (if there is anything below)*/ + if (pos.y != last_row && row_start[pos.y + 1] != -1 && point_below < sz) /*Nothing below*/ { + if (corners[point_below].y < pos.y + 1) + point_below = row_start[pos.y+1]; + + /* Make point below point to one of the pixels belowthe current point, if it exists.*/ + for (; point_below < sz && corners[point_below].y == pos.y+1 && corners[point_below].x < pos.x - 1; point_below++) { + } + + for (int j=point_below; j < sz && corners[j].y == pos.y+1 && corners[j].x <= pos.x + 1; j++) { + int x = corners[j].x; + if( (x == pos.x - 1 || x ==pos.x || x == pos.x+1) && Compare(corners[j].score, score)) + goto nonmax; + } + } + + // gc_info(&info); + #define MIN_MEM (10*1024) + // Allocate keypoints until we're almost out of memory + // if (info.free < MIN_MEM) { + // // Try collecting memory + // gc_collect(); + // // If it didn't work break + // gc_info(&info); + // if (info.free < MIN_MEM) { + // break; + // } + // } + + #undef MIN_MEM + array_push_back(keypoints, alloc_keypoint(pos.x, pos.y, pos.score)); + nonmax: + ; + } +} + +static int fast9_corner_score(const uint8_t *p, int bstart) +{ + int bmin = bstart; + int bmax = 255; + int b = (bmax + bmin)/2; + + /*Compute the score using binary search*/ + for(;;) + { + int cb = *p + b; + int c_b= *p - b; + + + if( p[pixel[0]] > cb) + if( p[pixel[1]] > cb) + if( p[pixel[2]] > cb) + if( p[pixel[3]] > cb) + if( p[pixel[4]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + goto is_a_corner; + else + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[7]] < c_b) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[14]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[6]] < c_b) + if( p[pixel[15]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[13]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[14]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[13]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[5]] < c_b) + if( p[pixel[14]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[12]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[13]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[14]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[6]] < c_b) + goto is_a_corner; + else + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[12]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[6]] < c_b) + goto is_a_corner; + else + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[4]] < c_b) + if( p[pixel[13]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[11]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[12]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[13]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[5]] < c_b) + goto is_a_corner; + else + if( p[pixel[14]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[11]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[5]] < c_b) + goto is_a_corner; + else + if( p[pixel[14]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[3]] < c_b) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[4]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[10]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[4]] < c_b) + goto is_a_corner; + else + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[4]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[10]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[4]] < c_b) + goto is_a_corner; + else + if( p[pixel[13]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[2]] < c_b) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[4]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[3]] > cb) + if( p[pixel[4]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[9]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[4]] < c_b) + if( p[pixel[3]] < c_b) + goto is_a_corner; + else + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[4]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[3]] > cb) + if( p[pixel[4]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[9]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[4]] < c_b) + if( p[pixel[3]] < c_b) + goto is_a_corner; + else + if( p[pixel[12]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[1]] < c_b) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[4]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[3]] > cb) + if( p[pixel[4]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[2]] > cb) + if( p[pixel[3]] > cb) + if( p[pixel[4]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[8]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[4]] < c_b) + if( p[pixel[3]] < c_b) + if( p[pixel[2]] < c_b) + goto is_a_corner; + else + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[4]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[3]] > cb) + if( p[pixel[4]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[2]] > cb) + if( p[pixel[3]] > cb) + if( p[pixel[4]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[8]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[4]] < c_b) + if( p[pixel[3]] < c_b) + if( p[pixel[2]] < c_b) + goto is_a_corner; + else + if( p[pixel[11]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[0]] < c_b) + if( p[pixel[1]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[4]] > cb) + if( p[pixel[3]] > cb) + if( p[pixel[2]] > cb) + goto is_a_corner; + else + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[4]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[3]] < c_b) + if( p[pixel[4]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[2]] < c_b) + if( p[pixel[3]] < c_b) + if( p[pixel[4]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[1]] < c_b) + if( p[pixel[2]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[4]] > cb) + if( p[pixel[3]] > cb) + goto is_a_corner; + else + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[4]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[3]] < c_b) + if( p[pixel[4]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[2]] < c_b) + if( p[pixel[3]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[4]] > cb) + goto is_a_corner; + else + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[4]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[3]] < c_b) + if( p[pixel[4]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[5]] > cb) + goto is_a_corner; + else + if( p[pixel[14]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[13]] < c_b) + if( p[pixel[11]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[12]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[4]] < c_b) + if( p[pixel[5]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[6]] > cb) + goto is_a_corner; + else + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[14]] < c_b) + if( p[pixel[12]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[13]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[6]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[5]] < c_b) + if( p[pixel[6]] > cb) + if( p[pixel[15]] < c_b) + if( p[pixel[13]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[14]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[6]] < c_b) + if( p[pixel[7]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + goto is_a_corner; + else + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[13]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[12]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[6]] > cb) + goto is_a_corner; + else + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[5]] > cb) + goto is_a_corner; + else + if( p[pixel[14]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[4]] > cb) + goto is_a_corner; + else + if( p[pixel[13]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[4]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[9]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[4]] > cb) + if( p[pixel[3]] > cb) + goto is_a_corner; + else + if( p[pixel[12]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[4]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[3]] < c_b) + if( p[pixel[4]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[8]] > cb) + if( p[pixel[7]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[10]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[4]] > cb) + if( p[pixel[3]] > cb) + if( p[pixel[2]] > cb) + goto is_a_corner; + else + if( p[pixel[11]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[4]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[3]] < c_b) + if( p[pixel[4]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[2]] < c_b) + if( p[pixel[3]] < c_b) + if( p[pixel[4]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[7]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[7]] > cb) + if( p[pixel[8]] > cb) + if( p[pixel[9]] > cb) + if( p[pixel[6]] > cb) + if( p[pixel[5]] > cb) + if( p[pixel[4]] > cb) + if( p[pixel[3]] > cb) + if( p[pixel[2]] > cb) + if( p[pixel[1]] > cb) + goto is_a_corner; + else + if( p[pixel[10]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] > cb) + if( p[pixel[11]] > cb) + if( p[pixel[12]] > cb) + if( p[pixel[13]] > cb) + if( p[pixel[14]] > cb) + if( p[pixel[15]] > cb) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else if( p[pixel[7]] < c_b) + if( p[pixel[8]] < c_b) + if( p[pixel[9]] < c_b) + if( p[pixel[6]] < c_b) + if( p[pixel[5]] < c_b) + if( p[pixel[4]] < c_b) + if( p[pixel[3]] < c_b) + if( p[pixel[2]] < c_b) + if( p[pixel[1]] < c_b) + goto is_a_corner; + else + if( p[pixel[10]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + if( p[pixel[10]] < c_b) + if( p[pixel[11]] < c_b) + if( p[pixel[12]] < c_b) + if( p[pixel[13]] < c_b) + if( p[pixel[14]] < c_b) + if( p[pixel[15]] < c_b) + goto is_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + else + goto is_not_a_corner; + + is_a_corner: + bmin=b; + goto end_if; + + is_not_a_corner: + bmax=b; + goto end_if; + + end_if: + + if(bmin == bmax - 1 || bmin == bmax) + return bmin; + b = (bmin + bmax) / 2; + } +} + +static void fast9_score(image_t *image, corner_t *corners, int num_corners, int b) +{ + for (int i=0; iscore = fast9_corner_score(image->pixels + c->y*image->w + c->x, b); + } +} + +static corner_t *fast9_detect(image_t *image, rectangle_t *roi, int *n_corners, int b) +{ + int num_corners = 0; + // Try to alloc MAX_CORNERS or the actual max corners we can alloc. + int max_corners = IM_MIN(MAX_CORNERS, (fb_avail() / sizeof(corner_t))); + corner_t *corners = (corner_t*) fb_alloc(max_corners * sizeof(corner_t), FB_ALLOC_NO_HINT); + + for(int y=roi->y+3; yy+roi->h-3; y++) { + for(int x=roi->x+3; xx+roi->w-3; x++) { + const uint8_t *p = image->pixels+(y * image->w + x); + int cb = *p + b; + int c_b= *p - b; + if(p[pixel[0]] > cb) + if(p[pixel[1]] > cb) + if(p[pixel[2]] > cb) + if(p[pixel[3]] > cb) + if(p[pixel[4]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + {} + else + if(p[pixel[15]] > cb) + {} + else + continue; + else if(p[pixel[7]] < c_b) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else if(p[pixel[14]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else if(p[pixel[6]] < c_b) + if(p[pixel[15]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + {} + else + continue; + else if(p[pixel[13]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[14]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else if(p[pixel[13]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[5]] < c_b) + if(p[pixel[14]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[15]] > cb) + {} + else + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[12]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[13]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[14]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[6]] < c_b) + {} + else + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[12]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[6]] < c_b) + {} + else + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[4]] < c_b) + if(p[pixel[13]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[11]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[12]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[13]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[5]] < c_b) + {} + else + if(p[pixel[14]] < c_b) + {} + else + continue; + else + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[11]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[5]] < c_b) + {} + else + if(p[pixel[14]] < c_b) + {} + else + continue; + else + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[3]] < c_b) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[4]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[10]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[4]] < c_b) + {} + else + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + {} + else + continue; + else + continue; + else + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[4]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[10]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[4]] < c_b) + {} + else + if(p[pixel[13]] < c_b) + {} + else + continue; + else + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + {} + else + continue; + else + continue; + else + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[2]] < c_b) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[4]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[3]] > cb) + if(p[pixel[4]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[9]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[4]] < c_b) + if(p[pixel[3]] < c_b) + {} + else + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + {} + else + continue; + else + continue; + else + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[4]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[3]] > cb) + if(p[pixel[4]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[9]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[4]] < c_b) + if(p[pixel[3]] < c_b) + {} + else + if(p[pixel[12]] < c_b) + {} + else + continue; + else + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + {} + else + continue; + else + continue; + else + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[1]] < c_b) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + {} + else + continue; + else + continue; + else + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[4]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[3]] > cb) + if(p[pixel[4]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[2]] > cb) + if(p[pixel[3]] > cb) + if(p[pixel[4]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[8]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[4]] < c_b) + if(p[pixel[3]] < c_b) + if(p[pixel[2]] < c_b) + {} + else + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + {} + else + continue; + else + continue; + else + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + {} + else + continue; + else + continue; + else + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[4]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[3]] > cb) + if(p[pixel[4]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[2]] > cb) + if(p[pixel[3]] > cb) + if(p[pixel[4]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[8]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[4]] < c_b) + if(p[pixel[3]] < c_b) + if(p[pixel[2]] < c_b) + {} + else + if(p[pixel[11]] < c_b) + {} + else + continue; + else + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + {} + else + continue; + else + continue; + else + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[0]] < c_b) + if(p[pixel[1]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[4]] > cb) + if(p[pixel[3]] > cb) + if(p[pixel[2]] > cb) + {} + else + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + {} + else + continue; + else + continue; + else + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + {} + else + continue; + else + continue; + else + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[4]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[3]] < c_b) + if(p[pixel[4]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[2]] < c_b) + if(p[pixel[3]] < c_b) + if(p[pixel[4]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[1]] < c_b) + if(p[pixel[2]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[4]] > cb) + if(p[pixel[3]] > cb) + {} + else + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + {} + else + continue; + else + continue; + else + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[4]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[3]] < c_b) + if(p[pixel[4]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[2]] < c_b) + if(p[pixel[3]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[4]] > cb) + {} + else + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + {} + else + continue; + else + continue; + else + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[4]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[3]] < c_b) + if(p[pixel[4]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[5]] > cb) + {} + else + if(p[pixel[14]] > cb) + {} + else + continue; + else + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[13]] < c_b) + if(p[pixel[11]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[12]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[4]] < c_b) + if(p[pixel[5]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[6]] > cb) + {} + else + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[14]] < c_b) + if(p[pixel[12]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[13]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[6]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[5]] < c_b) + if(p[pixel[6]] > cb) + if(p[pixel[15]] < c_b) + if(p[pixel[13]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[14]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + {} + else + continue; + else + continue; + else + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[6]] < c_b) + if(p[pixel[7]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + {} + else + if(p[pixel[15]] < c_b) + {} + else + continue; + else + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + if(p[pixel[13]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[12]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[6]] > cb) + {} + else + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[11]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[5]] > cb) + {} + else + if(p[pixel[14]] > cb) + {} + else + continue; + else + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[4]] > cb) + {} + else + if(p[pixel[13]] > cb) + {} + else + continue; + else + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + {} + else + continue; + else + continue; + else + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[4]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[9]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[4]] > cb) + if(p[pixel[3]] > cb) + {} + else + if(p[pixel[12]] > cb) + {} + else + continue; + else + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + {} + else + continue; + else + continue; + else + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[4]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[3]] < c_b) + if(p[pixel[4]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[8]] > cb) + if(p[pixel[7]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[10]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[4]] > cb) + if(p[pixel[3]] > cb) + if(p[pixel[2]] > cb) + {} + else + if(p[pixel[11]] > cb) + {} + else + continue; + else + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + {} + else + continue; + else + continue; + else + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + {} + else + continue; + else + continue; + else + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[4]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[3]] < c_b) + if(p[pixel[4]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[2]] < c_b) + if(p[pixel[3]] < c_b) + if(p[pixel[4]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[7]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[7]] > cb) + if(p[pixel[8]] > cb) + if(p[pixel[9]] > cb) + if(p[pixel[6]] > cb) + if(p[pixel[5]] > cb) + if(p[pixel[4]] > cb) + if(p[pixel[3]] > cb) + if(p[pixel[2]] > cb) + if(p[pixel[1]] > cb) + {} + else + if(p[pixel[10]] > cb) + {} + else + continue; + else + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + {} + else + continue; + else + continue; + else + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] > cb) + if(p[pixel[11]] > cb) + if(p[pixel[12]] > cb) + if(p[pixel[13]] > cb) + if(p[pixel[14]] > cb) + if(p[pixel[15]] > cb) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else if(p[pixel[7]] < c_b) + if(p[pixel[8]] < c_b) + if(p[pixel[9]] < c_b) + if(p[pixel[6]] < c_b) + if(p[pixel[5]] < c_b) + if(p[pixel[4]] < c_b) + if(p[pixel[3]] < c_b) + if(p[pixel[2]] < c_b) + if(p[pixel[1]] < c_b) + {} + else + if(p[pixel[10]] < c_b) + {} + else + continue; + else + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + {} + else + continue; + else + continue; + else + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + if(p[pixel[10]] < c_b) + if(p[pixel[11]] < c_b) + if(p[pixel[12]] < c_b) + if(p[pixel[13]] < c_b) + if(p[pixel[14]] < c_b) + if(p[pixel[15]] < c_b) + {} + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + else + continue; + + // Add corner + corners[num_corners].x = x; + corners[num_corners].y = y; + + if (++num_corners == max_corners) { + goto done; + } + } + } + +done: + *n_corners = num_corners; + return corners; +} +#endif //IMLIB_ENABLE_FAST diff --git a/github_source/minicv2/src/fb_alloc.c b/github_source/minicv2/src/fb_alloc.c new file mode 100644 index 0000000..8115054 --- /dev/null +++ b/github_source/minicv2/src/fb_alloc.c @@ -0,0 +1,452 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013-2016 Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Interface for using extra frame buffer RAM as a stack. + * + */ +// #include "py/obj.h" +// #include "py/runtime.h" +#include "fb_alloc.h" +// #include "framebuffer.h" +#include "omv_boardconfig.h" +#include "imlib_config.h" +#include "xalloc.h" +#include +#include +#include "imlib_io.h" +#define true 1 +#define false 0 +#define bool uint8_t + +#ifdef USE_FB_ALLOC + +uint32_t fb_size[100]; +size_t fb_point[100]; +uint32_t fb_size_idx; +uint32_t fb_alloc_num; + +char *fb_alloc_stack_pointer() +{ + return NULL; +} +void fb_alloc_fail() +{ + imlib_printf(0, "MemoryError :Out of fast Frame Buffer Stack Memory! Please reduce the resolution of the image you are running this algorithm on to bypass this issue!"); +} +void fb_alloc_init0() +{ + fb_size[0] = OMV_FB_ALLOC_SIZE; + fb_size_idx = 1; + fb_alloc_num = 0; +} +void fb_alloc_close0() +{ + +} +uint32_t fb_avail() +{ + return fb_size[0]; +} +void fb_alloc_mark() +{ + fb_alloc_num = 0; +} +void fb_alloc_free_till_mark() +{ + +} +void fb_alloc_mark_permanent() // tag memory that should not be popped on exception +{ + +} +void fb_alloc_free_till_mark_past_mark_permanent() // frees past marked permanent allocations +{ + +} +void *fb_alloc(uint32_t size, int hints) +{ + uint8_t *tmp = xalloc(size); + fb_size[0] -= size; + fb_size[fb_size_idx] = size; + fb_point[fb_size_idx] = tmp; + fb_size_idx ++; + fb_alloc_num ++; + return tmp; +} +void *fb_alloc0(uint32_t size, int hints) +{ + uint8_t *tmp = xalloc0(size); + fb_size[0] -= size; + fb_size[fb_size_idx] = size; + fb_point[fb_size_idx] = tmp; + fb_size_idx ++; + fb_alloc_num ++; + return tmp; +} +void *fb_alloc_all(uint32_t *size, int hints) // returns pointer and sets size +{ + uint8_t *tmp = xalloc(fb_size[0]); + fb_size[fb_size_idx] = size; + fb_point[fb_size_idx] = tmp; + fb_size_idx ++; + *size = fb_size[0]; + fb_size[0] = 0; + fb_alloc_num ++; + return tmp; +} +void *fb_alloc0_all(uint32_t *size, int hints) // returns pointer and sets size +{ + uint8_t *tmp = xalloc0(fb_size[0]); + fb_size[fb_size_idx] = size; + fb_point[fb_size_idx] = tmp; + fb_size_idx ++; + *size = fb_size[0]; + fb_size[0] = 0; + fb_alloc_num ++; + return tmp; +} +void fb_free(void *msm) +{ + fb_size_idx --; + if(NULL == msm) + { + uint8_t *tmp = fb_point[fb_size_idx]; + xfree(tmp); + } + else + { + xfree(msm); + } + fb_size[0] += fb_size[fb_size_idx]; +} +void fb_free_all() +{ + for (;fb_alloc_num != 0; fb_alloc_num--) + { + fb_free(NULL); + } +} + + + + +#else +#ifndef __DCACHE_PRESENT +#define FB_ALLOC_ALIGNMENT 32 // Use 32-byte alignment on MCUs with no cache for DMA buffer alignment. +#else +#define FB_ALLOC_ALIGNMENT __SCB_DCACHE_LINE_SIZE +#endif + +static char* _fballoc_start = NULL; +static char* _fballoc = NULL; +static char* pointer = NULL; + +static int alloc_num = 0; + + +#if defined(FB_ALLOC_STATS) +static uint32_t alloc_bytes; +static uint32_t alloc_bytes_peak; +#endif + +// #if defined(OMV_FB_OVERLAY_MEMORY) +// #define FB_OVERLAY_MEMORY_FLAG 0x1 +// extern char _fballoc_overlay_end, _fballoc_overlay_start; +// static char *pointer_overlay = &_fballoc_overlay_end; +// #endif + +// fb_alloc_free_till_mark() will not free past this. +// Use fb_alloc_free_till_mark_permanent() instead. +#define FB_PERMANENT_FLAG 0x2 + +char *fb_alloc_stack_pointer() +{ + return pointer; +} + +void fb_alloc_fail() +{ + ERR_PRINT("MemoryError :Out of fast Frame Buffer Stack Memory! Please reduce the resolution of \ + the image you are running this algorithm on to bypass this issue!"); +} + +void fb_alloc_init0() +{ + _fballoc_start = (char*)xalloc(OMV_FB_ALLOC_SIZE); + _fballoc = _fballoc_start + OMV_FB_ALLOC_SIZE - sizeof(uint32_t); + pointer = _fballoc; +} +/** + * @brief fb_realloc_init1 + * Functional description: + * Reprogram the memory used by the fb_alloc module . + * Previously used data is not saved ! + * @param size + * will be alloc memory! + */ +void fb_realloc_init1(uint32_t size) +{ + if(NULL == _fballoc_start) + { + _fballoc_start = (char*)xalloc(size); + _fballoc = _fballoc_start + size - sizeof(uint32_t); + pointer = _fballoc; + } + else + { + xfree(_fballoc_start); + _fballoc_start = (char*)xalloc(size); + _fballoc = _fballoc_start + size - sizeof(uint32_t); + pointer = _fballoc; + } +} + +void fb_alloc_close0() +{ + xfree(_fballoc_start); + _fballoc_start = NULL; + _fballoc = NULL; + pointer = NULL; + +} + + +uint32_t fb_avail() +{ + uint32_t temp = pointer - _fballoc_start - sizeof(uint32_t); + return (temp < sizeof(uint32_t)) ? 0 : temp; +} + +void fb_alloc_mark() +{ + char *new_pointer = pointer - sizeof(uint32_t); + + // Check if allocation overwrites the framebuffer pixels + if (new_pointer < _fballoc_start) { + fb_alloc_fail(); + // nlr_raise_for_fb_alloc_mark(mp_obj_new_exception_msg(&mp_type_MemoryError, + // MP_ERROR_TEXT("Out of fast Frame Buffer Stack Memory!" + // " Please reduce the resolution of the image you are running this algorithm on to bypass this issue!"))); + } + + // fb_alloc does not allow regions which are a size of 0 to be alloced, + // meaning that the value below is always 8 or more but never 4. So, + // we will use a size value of 4 as a marker in the alloc stack. + *((uint32_t *) new_pointer) = sizeof(uint32_t); // Save size. + pointer = new_pointer; + #if defined(FB_ALLOC_STATS) + alloc_bytes = 0; + alloc_bytes_peak = 0; + #endif + DEBUG_PRINT("start a flage!"); +} + +static void int_fb_alloc_free_till_mark(bool free_permanent) +{ + // Previously there was a marks counting method used to provide a semaphore lock for this code: + // + // https://github.com/openmv/openmv/commit/c982617523766018fda70c15818f643ee8b1fd33 + // + // This does not really help you in complex memory allocation operations where you want to be + // able to unwind things until after a certain point. It also did not handle preventing + // fb_alloc_free_till_mark() from running in recursive call situations (see find_blobs()). + while (pointer < _fballoc) { + uint32_t size = *((uint32_t *) pointer); + if ((!free_permanent) && (size & FB_PERMANENT_FLAG)) return; + size &= ~FB_PERMANENT_FLAG; + // #if defined(OMV_FB_OVERLAY_MEMORY) + // if (size & FB_OVERLAY_MEMORY_FLAG) { // Check for fast flag. + // size &= ~FB_OVERLAY_MEMORY_FLAG; // Remove it. + // pointer_overlay += size - sizeof(uint32_t); + // } + // #endif + pointer += size; // Get size and pop. + if (size == sizeof(uint32_t)) break; // Break on first marker. + } + #if defined(FB_ALLOC_STATS) + printf("fb_alloc peak memory: %lu\n", alloc_bytes_peak); + #endif + DEBUG_PRINT("free a flage!"); +} + +void fb_alloc_free_till_mark() +{ + int_fb_alloc_free_till_mark(false); +} + +void fb_alloc_mark_permanent() +{ + if (pointer < _fballoc) *((uint32_t *) pointer) |= FB_PERMANENT_FLAG; +} + +void fb_alloc_free_till_mark_past_mark_permanent() +{ + int_fb_alloc_free_till_mark(true); +} + +// returns null pointer without error if size==0 +void *fb_alloc(uint32_t size, int hints) +{ + if (!size) { + return NULL; + } + + size = ((size + sizeof(uint32_t) - 1) / sizeof(uint32_t)) * sizeof(uint32_t); // Round Up + + if (hints & FB_ALLOC_CACHE_ALIGN) { + size = ((size + FB_ALLOC_ALIGNMENT - 1) / FB_ALLOC_ALIGNMENT) * FB_ALLOC_ALIGNMENT; + size += FB_ALLOC_ALIGNMENT - sizeof(uint32_t); + } + + char *result = pointer - size; + char *new_pointer = result - sizeof(uint32_t); + + // Check if allocation overwrites the framebuffer pixels + if (new_pointer < _fballoc_start) { + fb_alloc_fail(); + } + + // size is always 4/8/12/etc. so the value below must be 8 or more. + *((uint32_t *) new_pointer) = size + sizeof(uint32_t); // Save size. + pointer = new_pointer; + + #if defined(FB_ALLOC_STATS) + alloc_bytes += size; + if (alloc_bytes > alloc_bytes_peak) { + alloc_bytes_peak = alloc_bytes; + } + printf("fb_alloc %lu bytes\n", size); + #endif + + // #if defined(OMV_FB_OVERLAY_MEMORY) + // if ((!(hints & FB_ALLOC_PREFER_SIZE)) + // && (((uint32_t) (pointer_overlay - &_fballoc_overlay_start)) >= size)) { + // // Return overlay memory instead. + // pointer_overlay -= size; + // result = pointer_overlay; + // *new_pointer |= FB_OVERLAY_MEMORY_FLAG; // Add flag. + // } + // #endif + + if (hints & FB_ALLOC_CACHE_ALIGN) { + int offset = ((size_t) result) % FB_ALLOC_ALIGNMENT; + if (offset) { + result += FB_ALLOC_ALIGNMENT - offset; + } + } + DEBUG_PRINT("mem num:%d pointer:%p size:%d", ++ alloc_num, pointer, size); + return result; +} + +// returns null pointer without error if passed size==0 +void *fb_alloc0(uint32_t size, int hints) +{ + void *mem = fb_alloc(size, hints); + memset(mem, 0, size); // does nothing if size is zero. + return mem; +} + +void *fb_alloc_all(uint32_t *size, int hints) +{ + uint32_t temp = pointer - _fballoc_start - sizeof(uint32_t); + + if (temp < sizeof(uint32_t)) { + *size = 0; + return NULL; + } + + // #if defined(OMV_FB_OVERLAY_MEMORY) + // if (!(hints & FB_ALLOC_PREFER_SIZE)) { + // *size = (uint32_t) (pointer_overlay - &_fballoc_overlay_start); + // temp = IM_MIN(temp, *size); + // } + // #endif + + *size = (temp / sizeof(uint32_t)) * sizeof(uint32_t); // Round Down + + char *result = pointer - *size; + char *new_pointer = result - sizeof(uint32_t); + + // size is always 4/8/12/etc. so the value below must be 8 or more. + *((uint32_t *) new_pointer) = *size + sizeof(uint32_t); // Save size. + pointer = new_pointer; + + #if defined(FB_ALLOC_STATS) + alloc_bytes += *size; + if (alloc_bytes > alloc_bytes_peak) { + alloc_bytes_peak = alloc_bytes; + } + printf("fb_alloc_all %lu bytes\n", *size); + #endif + + // #if defined(OMV_FB_OVERLAY_MEMORY) + // if (!(hints & FB_ALLOC_PREFER_SIZE)) { + // // Return overlay memory instead. + // pointer_overlay -= *size; + // result = pointer_overlay; + // *new_pointer |= FB_OVERLAY_MEMORY_FLAG; // Add flag. + // } + // #endif + + if (hints & FB_ALLOC_CACHE_ALIGN) { + int offset = ((size_t) result) % FB_ALLOC_ALIGNMENT; + if (offset) { + int inc = FB_ALLOC_ALIGNMENT - offset; + result += inc; + *size -= inc; + } + *size = (*size / FB_ALLOC_ALIGNMENT) * FB_ALLOC_ALIGNMENT; + } + DEBUG_PRINT("alloc all mem,num:%d", ++ alloc_num); + return result; +} + +// returns null pointer without error if returned size==0 +void *fb_alloc0_all(uint32_t *size, int hints) +{ + void *mem = fb_alloc_all(size, hints); + memset(mem, 0, *size); // does nothing if size is zero. + return mem; +} + +void fb_free(void *msm) +{ + if (pointer < _fballoc) { + uint32_t size = *((uint32_t *) pointer); + size &= ~FB_PERMANENT_FLAG; + // #if defined(OMV_FB_OVERLAY_MEMORY) + // if (size & FB_OVERLAY_MEMORY_FLAG) { // Check for fast flag. + // size &= ~FB_OVERLAY_MEMORY_FLAG; // Remove it. + // pointer_overlay += size - sizeof(uint32_t); + // } + // #endif + #if defined(FB_ALLOC_STATS) + alloc_bytes -= size; + #endif + pointer += size; // Get size and pop. + DEBUG_PRINT("free num:%d size:%d pointer:%p", -- alloc_num, size, pointer); + } +} + +void fb_free_all() +{ + while (pointer < _fballoc) { + uint32_t size = *((uint32_t *) pointer); + size &= ~FB_PERMANENT_FLAG; + // #if defined(OMV_FB_OVERLAY_MEMORY) + // if (size & FB_OVERLAY_MEMORY_FLAG) { // Check for fast flag. + // size &= ~FB_OVERLAY_MEMORY_FLAG; // Remove it. + // pointer_overlay += size - sizeof(uint32_t); + // } + // #endif + #if defined(FB_ALLOC_STATS) + alloc_bytes -= size; + #endif + pointer += size; // Get size and pop. + } + DEBUG_PRINT("free all mem!"); +} + +#endif \ No newline at end of file diff --git a/github_source/minicv2/src/ff_wrapper.c b/github_source/minicv2/src/ff_wrapper.c new file mode 100644 index 0000000..4ef127b --- /dev/null +++ b/github_source/minicv2/src/ff_wrapper.c @@ -0,0 +1,742 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013-2016 Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * File System Helper Functions + * + */ +#include "imlib_config.h" + + + +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) +#include +#include +#include +// #include "py/runtime.h" +// #include "extmod/vfs.h" +// #include "extmod/vfs_fat.h" + +#include "common.h" +#include "fb_alloc.h" +#include "ff_wrapper.h" +#define FF_MIN(x,y) (((x)<(y))?(x):(y)) + +#if 0 + +static void ff_fail(FIL *fp, uint32_t res) +{ + if (fp) fclose(fp); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT(ffs_strerror(res))); +} + +static void ff_read_fail(FIL *fp) +{ + if (fp) fclose(fp); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to read requested bytes!")); +} + +static void ff_write_fail(FIL *fp) +{ + if (fp) fclose(fp); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Failed to write requested bytes!")); +} + + static void ff_expect_fail(FIL *fp) +{ + if (fp) fclose(fp); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unexpected value read!")); +} + + void ff_unsupported_format(FIL *fp) +{ + if (fp) fclose(fp); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported format!")); +} + + void ff_file_corrupted(FIL *fp) +{ + if (fp) fclose(fp); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("File corrupted!")); +} + + void ff_not_equal(FIL *fp) +{ + if (fp) fclose(fp); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Images not equal!")); +} + + void ff_no_intersection(FIL *fp) +{ + if (fp) fclose(fp); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("No intersection!")); +} + +void file_read_open(FIL *fp, const char *path) +{ + *fp = fopen(path, "rb"); + if(NULL == *fp) ff_fail(fp, 0); + // FRESULT res = f_open_helper(fp, path, FA_READ|FA_OPEN_EXISTING); + // if (res != FR_OK) ff_fail(fp, res); +} + +void file_write_open(FIL *fp, const char *path) +{ + *fp = fopen(path, "wb"); + if(NULL == *fp) ff_fail(fp, 0); + // FRESULT res = f_open_helper(fp, path, FA_WRITE|FA_CREATE_ALWAYS); + // if (res != FR_OK) ff_fail(fp, res); +} + +void file_close(FIL *fp) +{ + if(NULL != *fp) + { + fclose(*fp); + } + // FRESULT res = f_close(fp); + // if (res != FR_OK) ff_fail(fp, res); +} + +void file_seek(FIL *fp, size_t offset) +{ + fseek(*fp, offset, SEEK_SET); + // FRESULT res = f_lseek(fp, offset); + // if (res != FR_OK) ff_fail(fp, res); +} + +void file_truncate(FIL *fp) +{ + // FRESULT res = f_truncate(fp); + // if (res != FR_OK) ff_fail(fp, res); +} + +void file_sync(FIL *fp) +{ + int fd = fileno(*fp); + fsync(fd); + // FRESULT res = f_sync(fp); + // if (res != FR_OK) ff_fail(fp, res); +} + +// These wrapper functions are used for backward compatibility with +// OpenMV code using vanilla FatFS. Note: Extracted from cc3200 ftp.c + +// FATFS *lookup_path(const TCHAR **path) { + // mp_vfs_mount_t *fs = mp_vfs_lookup_path(*path, path); + // if (fs == MP_VFS_NONE || fs == MP_VFS_ROOT) { + // return NULL; + // } + // // here we assume that the mounted device is FATFS + // return &((fs_user_mount_t*)MP_OBJ_TO_PTR(fs->obj))->fatfs; +} + +// FRESULT f_open_helper(FIL *fp, const TCHAR *path, BYTE mode) { +// FATFS *fs = lookup_path(&path); +// if (fs == NULL) { +// return FR_NO_PATH; +// } +// return f_open(fs, fp, path, mode); +// } + +// FRESULT f_opendir_helper(FF_DIR *dp, const TCHAR *path) { +// FATFS *fs = lookup_path(&path); +// if (fs == NULL) { +// return FR_NO_PATH; +// } +// return f_opendir(fs, dp, path); +// } + +// FRESULT f_stat_helper(const TCHAR *path, FILINFO *fno) { +// FATFS *fs = lookup_path(&path); +// if (fs == NULL) { +// return FR_NO_PATH; +// } +// return f_stat(fs, path, fno); +// } + +// FRESULT f_mkdir_helper(const TCHAR *path) { +// FATFS *fs = lookup_path(&path); +// if (fs == NULL) { +// return FR_NO_PATH; +// } +// return f_mkdir(fs, path); +// } + +// FRESULT f_unlink_helper(const TCHAR *path) { +// FATFS *fs = lookup_path(&path); +// if (fs == NULL) { +// return FR_NO_PATH; +// } +// return f_unlink(fs, path); +// } + +// FRESULT f_rename_helper(const TCHAR *path_old, const TCHAR *path_new) { +// FATFS *fs_old = lookup_path(&path_old); +// if (fs_old == NULL) { +// return FR_NO_PATH; +// } +// FATFS *fs_new = lookup_path(&path_new); +// if (fs_new == NULL) { +// return FR_NO_PATH; +// } +// if (fs_old != fs_new) { +// return FR_NO_PATH; +// } +// return f_rename(fs_new, path_old, path_new); +// } +// When a sector boundary is encountered while writing a file and there are +// more than 512 bytes left to write FatFs will detect that it can bypass +// its internal write buffer and pass the data buffer passed to it directly +// to the disk write function. However, the disk write function needs the +// buffer to be aligned to a 4-byte boundary. FatFs doesn't know this and +// will pass an unaligned buffer if we don't fix the issue. To fix this problem +// we use a temporary buffer to fix the alignment and to speed everything up. + +// We use this temporary buffer for both reads and writes. The buffer allows us +// to do multi-block reads and writes which signifcantly speed things up. + +static uint32_t file_buffer_offset = 0; +static uint8_t *file_buffer_pointer = 0; +static uint32_t file_buffer_size = 0; +static uint32_t file_buffer_index = 0; + +void file_buffer_init0() +{ + file_buffer_offset = 0; + file_buffer_pointer = 0; + file_buffer_size = 0; + file_buffer_index = 0; +} + +OMV_ATTR_ALWAYS_INLINE static void file_fill(FIL *fp) +{ + if (file_buffer_index == file_buffer_size) { + file_buffer_pointer -= file_buffer_offset; + file_buffer_size += file_buffer_offset; + file_buffer_offset = 0; + file_buffer_index = 0; + uint32_t file_remaining = fsize(*fp) - ftell(*fp); + uint32_t can_do = FF_MIN(file_buffer_size, file_remaining); + uint8_t bytes; + uint32_t res = fread(*fp, file_buffer_pointer, can_do, &bytes); + // FRESULT res = fread(*fp, file_buffer_pointer, can_do, &bytes); + // if (res != FR_OK) ff_fail(fp, res); + // if (bytes != can_do) ff_read_fail(fp); + } +} + +OMV_ATTR_ALWAYS_INLINE static void file_flush(FIL *fp) +{ + if (file_buffer_index == file_buffer_size) { + uint8_t bytes; + uint32_t res = fwrite(*fp, file_buffer_pointer, file_buffer_index, &bytes); + // if (res != FR_OK) ff_fail(fp, res); + if (bytes != file_buffer_index) ff_write_fail(*fp); + file_buffer_pointer -= file_buffer_offset; + file_buffer_size += file_buffer_offset; + file_buffer_offset = 0; + file_buffer_index = 0; + } +} + +uint32_t file_tell_w_buf(FIL *fp) +{ + if (fp->flag & FA_READ) { + return ftell(*fp) - file_buffer_size + file_buffer_index; + } else { + return ftell(*fp) + file_buffer_index; + } +} + +uint32_t file_size_w_buf(FIL *fp) +{ + if (fp->flag & FA_READ) { + return fsize(*fp); + } else { + return fsize(*fp) + file_buffer_index; + } +} + +void file_buffer_on(FIL *fp) +{ + file_buffer_offset = ftell(*fp) % 4; + file_buffer_pointer = fb_alloc_all(&file_buffer_size, FB_ALLOC_PREFER_SIZE) + file_buffer_offset; + if (!file_buffer_size) { + // mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("No memory!")); + } + file_buffer_size -= file_buffer_offset; + file_buffer_index = 0; + if (fp->flag & FA_READ) { + uint32_t file_remaining = fsize(*fp) - ftell(*fp); + uint32_t can_do = FF_MIN(file_buffer_size, file_remaining); + uint8_t bytes; + uint32_t res = fread(*fp, file_buffer_pointer, can_do, &bytes); + // if (res != FR_OK) ff_fail(fp, res); + // if (bytes != can_do) ff_read_fail(fp); + } +} + +void file_buffer_off(FIL *fp) +{ + if ((fp->flag & FA_WRITE) && file_buffer_index) { + uint8_t bytes; + uint32_t res = fwrite(*fp, file_buffer_pointer, file_buffer_index, &bytes); + // if (res != FR_OK) ff_fail(fp, res); + // if (bytes != file_buffer_index) ff_write_fail(fp); + } + file_buffer_pointer = 0; + fb_free(NULL); +} + +void read_byte(FIL *fp, uint8_t *value) +{ + if (file_buffer_pointer) { + // We get a massive speed boost by buffering up as much data as possible + // via massive reads. So much so that the time wasted by + // all these operations does not cost us. + for (size_t i = 0; i < sizeof(*value); i++) { + file_fill(fp); + ((uint8_t *) value)[i] = file_buffer_pointer[file_buffer_index++]; + } + } else { + uint8_t bytes; + uint32_t res = fread(*fp, value, sizeof(*value), &bytes); + // if (res != FR_OK) ff_fail(fp, res); + // if (bytes != sizeof(*value)) ff_read_fail(fp); + } +} + +void read_byte_expect(FIL *fp, uint8_t value) +{ + uint8_t compare; + read_byte(fp, &compare); + if (value != compare) ff_expect_fail(fp); +} + +void read_byte_ignore(FIL *fp) +{ + uint8_t trash; + read_byte(fp, &trash); +} + +void read_word(FIL *fp, uint16_t *value) +{ + if (file_buffer_pointer) { + // We get a massive speed boost by buffering up as much data as possible + // via massive reads. So much so that the time wasted by + // all these operations does not cost us. + for (size_t i = 0; i < sizeof(*value); i++) { + file_fill(fp); + ((uint8_t *) value)[i] = file_buffer_pointer[file_buffer_index++]; + } + } else { + uint8_t bytes; + uint32_t res = fread(*fp, value, sizeof(*value), &bytes); + // if (res != FR_OK) ff_fail(fp, res); + // if (bytes != sizeof(*value)) ff_read_fail(fp); + } +} + +void read_word_expect(FIL *fp, uint16_t value) +{ + uint16_t compare; + read_word(fp, &compare); + if (value != compare) ff_expect_fail(fp); +} + +void read_word_ignore(FIL *fp) +{ + uint16_t trash; + read_word(fp, &trash); +} + +void read_long(FIL *fp, uint32_t *value) +{ + if (file_buffer_pointer) { + // We get a massive speed boost by buffering up as much data as possible + // via massive reads. So much so that the time wasted by + // all these operations does not cost us. + for (size_t i = 0; i < sizeof(*value); i++) { + file_fill(fp); + ((uint8_t *) value)[i] = file_buffer_pointer[file_buffer_index++]; + } + } else { + uint8_t bytes; + uint32_t res = fread(*fp, value, sizeof(*value), &bytes); + // if (res != FR_OK) ff_fail(fp, res); + // if (bytes != sizeof(*value)) ff_read_fail(fp); + } +} + +void read_long_expect(FIL *fp, uint32_t value) +{ + uint32_t compare; + read_long(fp, &compare); + if (value != compare) ff_expect_fail(fp); +} + +void read_long_ignore(FIL *fp) +{ + uint32_t trash; + read_long(fp, &trash); +} + +void read_data(FIL *fp, void *data, size_t size) +{ + if (file_buffer_pointer) { + // We get a massive speed boost by buffering up as much data as possible + // via massive reads. So much so that the time wasted by + // all these operations does not cost us. + while (size) { + file_fill(fp); + uint32_t file_buffer_space_left = file_buffer_size - file_buffer_index; + uint32_t can_do = FF_MIN(size, file_buffer_space_left); + memcpy(data, file_buffer_pointer+file_buffer_index, can_do); + file_buffer_index += can_do; + data += can_do; + size -= can_do; + } + } else { + uint8_t bytes; + uint32_t res = fread(*fp, data, size, &bytes); + // if (res != FR_OK) ff_fail(fp, res); + if (bytes != size) ff_read_fail(fp); + } +} + +void write_byte(FIL *fp, uint8_t value) +{ + if (file_buffer_pointer) { + // We get a massive speed boost by buffering up as much data as possible + // before a write to the SD card. So much so that the time wasted by + // all these operations does not cost us. + for (size_t i = 0; i < sizeof(value); i++) { + file_buffer_pointer[file_buffer_index++] = ((uint8_t *) &value)[i]; + file_flush(fp); + } + } else { + uint8_t bytes; + uint32_t res = fwrite(*fp, &value, sizeof(value), &bytes); + // if (res != FR_OK) ff_fail(fp, res); + if (bytes != sizeof(value)) ff_write_fail(fp); + } +} + +void write_word(FIL *fp, uint16_t value) +{ + if (file_buffer_pointer) { + // We get a massive speed boost by buffering up as much data as possible + // before a write to the SD card. So much so that the time wasted by + // all these operations does not cost us. + for (size_t i = 0; i < sizeof(value); i++) { + file_buffer_pointer[file_buffer_index++] = ((uint8_t *) &value)[i]; + file_flush(fp); + } + } else { + uint8_t bytes; + uint32_t res = fwrite(*fp, &value, sizeof(value), &bytes); + // if (res != FR_OK) ff_fail(fp, res); + if (bytes != sizeof(value)) ff_write_fail(fp); + } +} + +void write_long(FIL *fp, uint32_t value) +{ + if (file_buffer_pointer) { + // We get a massive speed boost by buffering up as much data as possible + // before a write to the SD card. So much so that the time wasted by + // all these operations does not cost us. + for (size_t i = 0; i < sizeof(value); i++) { + file_buffer_pointer[file_buffer_index++] = ((uint8_t *) &value)[i]; + file_flush(fp); + } + } else { + uint8_t bytes; + uint32_t res = fwrite(*fp, &value, sizeof(value), &bytes); + // if (res != FR_OK) ff_fail(fp, res); + if (bytes != sizeof(value)) ff_write_fail(fp); + } +} + +void write_data(FIL *fp, const void *data, size_t size) +{ + if (file_buffer_pointer) { + // We get a massive speed boost by buffering up as much data as possible + // before a write to the SD card. So much so that the time wasted by + // all these operations does not cost us. + while (size) { + uint32_t file_buffer_space_left = file_buffer_size - file_buffer_index; + uint32_t can_do = FF_MIN(size, file_buffer_space_left); + memcpy(file_buffer_pointer+file_buffer_index, data, can_do); + file_buffer_index += can_do; + data += can_do; + size -= can_do; + file_flush(fp); + } + } else { + uint8_t bytes; + uint32_t res = fwrite(*fp, data, size, &bytes); + // if (res != FR_OK) ff_fail(fp, res); + if (bytes != size) ff_write_fail(fp); + } +} + + +#else +#include "imlib_io.h" + +int ff_unsupported_format(FIL *fp) +{ + if (*fp) fclose(*fp); + *fp = NULL; + ERR_PRINT("ff_unsupported_format"); + return 0; +} + +int ff_file_corrupted(FIL *fp) +{ + if (*fp) fclose(*fp); + *fp = NULL; + ERR_PRINT("ff_file_corrupted!\n"); + return 0; +} +int ff_not_equal(FIL *fp) +{ + if (*fp) fclose(*fp); + *fp = NULL; + ERR_PRINT("ff_not_equal!\n"); + return 0; +} + +int ff_no_intersection(FIL *fp) +{ + if (*fp) fclose(*fp); + *fp = NULL; + ERR_PRINT("ff_no_intersection!\n"); + return 0; +} +int file_read_open(FIL *fp, const char *path) +{ + *fp = fopen(path, "rb"); + if(NULL == *fp) + { + LOG_PRINT("%s,not exit!", path); + } + return 0; +} +int file_write_open(FIL *fp, const char *path) +{ + *fp = fopen(path, "wb"); + return 0; +} + +int file_close(FIL *fp) +{ + fclose(*fp); + return 0; +} +int file_seek(FIL *fp, size_t offset) +{ + fseek(*fp, offset, SEEK_SET); + return 0; +} +int file_truncate(FIL *fp) +{ + return -1; +} +int file_sync(FIL *fp) +{ + int fd = fileno(*fp); + fsync(fd); + return 0; +} + +long file_fsize(FIL *fp) +{ + long n; + fpos_t fpos; //当前位置 + fgetpos(*fp, &fpos); //获取当前位置 + fseek(*fp, 0, SEEK_END); + n = ftell(*fp); + fsetpos(*fp, &fpos); //恢复之前的位置 + return n; +} + +static uint32_t file_buffer_offset = 0; +static uint8_t *file_buffer_pointer = 0; +static uint32_t file_buffer_size = 0; +static uint32_t file_buffer_index = 0; + +int file_buffer_init0() +{ + file_buffer_offset = 0; + file_buffer_pointer = 0; + file_buffer_size = 0; + file_buffer_index = 0; + return 0; +} + + + + + + +int file_buffer_on(FIL *fp) // does fb_alloc_all +{ + file_buffer_offset = ftell(*fp) % 4; + file_buffer_pointer = fb_alloc_all(&file_buffer_size, FB_ALLOC_PREFER_SIZE) + file_buffer_offset; + if (!file_buffer_size) { + // mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("No memory!")); + ERR_PRINT("MemoryError: No memory!"); + // printf("%s:%d,No memory!\n", __FUNCTION__, __LINE__); + } + file_buffer_size -= file_buffer_offset; + file_buffer_index = 0; + // if (fp->flag & FA_READ) { + // uint32_t file_remaining = fsize(*fp) - ftell(*fp); + // uint32_t can_do = FF_MIN(file_buffer_size, file_remaining); + // uint8_t bytes; + // uint32_t res = fread(*fp, file_buffer_pointer, can_do, &bytes); + // // if (res != FR_OK) ff_fail(fp, res); + // // if (bytes != can_do) ff_read_fail(fp); + // } + return 0; +} +uint32_t file_tell_w_buf(FIL *fp) // valid between on and off +{ + return -1; +} +uint32_t file_size_w_buf(FIL *fp) // valid between on and off +{ + return -1; +} +int file_buffer_off(FIL *fp) // does fb_free +{ + return -1; +} +int read_byte(FIL *fp, uint8_t *value) +{ + int num; + num = fread(value, 1, 1, *fp); + if(num != 1) + { + ERR_PRINT("file error!\n"); + } + return num; +} +int read_byte_expect(FIL *fp, uint8_t value) +{ + int num; + uint8_t str_s; + num = fread(&str_s, 1, 1, *fp); + if(str_s != value) + { + ERR_PRINT("file error!\n"); + return -1; + } + return num; +} +int read_byte_ignore(FIL *fp) +{ + int num; + uint32_t str_s; + num = fread(&str_s, 1, 1, *fp); + if(num != 1) + { + ERR_PRINT("file error!\n"); + } + return num; +} +int read_word(FIL *fp, uint16_t *value) +{ + int num; + num = fread(value, 2, 1, *fp); + if(num != 1) + { + ERR_PRINT("file error!\n"); + } + return num; +} +int read_word_expect(FIL *fp, uint16_t value) +{ + int num; + uint16_t str_s; + num = fread(&str_s, 2, 1, *fp); + if(str_s != value) + { + ERR_PRINT("file error!\n"); + return -1; + } + return num; +} +int read_word_ignore(FIL *fp) +{ + int num; + uint16_t str_s; + num = fread(&str_s, 2, 1, *fp); + if(num != 1) + { + ERR_PRINT("file error!\n"); + return -1; + } + return num; +} +int read_long(FIL *fp, uint32_t *value) +{ + int num; + num = fread(value, 4, 1, *fp); + if(num != 1) + { + ERR_PRINT("file error!\n"); + return -1; + } + return num; +} +int read_long_expect(FIL *fp, uint32_t value) +{ + int num; + uint32_t str_s; + num = fread(&str_s, 4, 1, *fp); + if(str_s != value) + { + ERR_PRINT("file error!\n"); + return -1; + } + return num; +} +int read_long_ignore(FIL *fp) +{ + uint32_t str_s; + return fread(&str_s, 4, 1, *fp); +} +int read_data(FIL *fp, void *data, size_t size) +{ + return fread(data, 1, size, *fp); + +} + + +int write_byte(FIL *fp, uint8_t value) +{ + return fwrite(&value, 1, 1, *fp); +} +int write_word(FIL *fp, uint16_t value) +{ + return fwrite(&value, 2, 1, *fp); +} +int write_long(FIL *fp, uint32_t value) +{ + return fwrite(&value, 4, 1, *fp); +} +int write_data(FIL *fp, const void *data, size_t size) +{ + return fwrite(data, 1, size, *fp); +} + + + +#endif + +#endif //IMLIB_ENABLE_IMAGE_FILE_IO \ No newline at end of file diff --git a/github_source/minicv2/src/fft.c b/github_source/minicv2/src/fft.c new file mode 100644 index 0000000..4eeda75 --- /dev/null +++ b/github_source/minicv2/src/fft.c @@ -0,0 +1,775 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013-2016 Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * FFT LIB - can do 1024 point real FFTs and 512 point complex FFTs + * + */ +// #include "py/runtime.h" +// #include "py/obj.h" +// #include +#include "fb_alloc.h" +#include "ff_wrapper.h" +#include "common.h" +#include "fft.h" +// http://processors.wiki.ti.com/index.php/Efficient_FFT_Computation_of_Real_Input + +const static float fft_cos_table[512] = { + 1.000000f, 0.999981f, 0.999925f, 0.999831f, 0.999699f, 0.999529f, 0.999322f, 0.999078f, + 0.998795f, 0.998476f, 0.998118f, 0.997723f, 0.997290f, 0.996820f, 0.996313f, 0.995767f, + 0.995185f, 0.994565f, 0.993907f, 0.993212f, 0.992480f, 0.991710f, 0.990903f, 0.990058f, + 0.989177f, 0.988258f, 0.987301f, 0.986308f, 0.985278f, 0.984210f, 0.983105f, 0.981964f, + 0.980785f, 0.979570f, 0.978317f, 0.977028f, 0.975702f, 0.974339f, 0.972940f, 0.971504f, + 0.970031f, 0.968522f, 0.966976f, 0.965394f, 0.963776f, 0.962121f, 0.960431f, 0.958703f, + 0.956940f, 0.955141f, 0.953306f, 0.951435f, 0.949528f, 0.947586f, 0.945607f, 0.943593f, + 0.941544f, 0.939459f, 0.937339f, 0.935184f, 0.932993f, 0.930767f, 0.928506f, 0.926210f, + 0.923880f, 0.921514f, 0.919114f, 0.916679f, 0.914210f, 0.911706f, 0.909168f, 0.906596f, + 0.903989f, 0.901349f, 0.898674f, 0.895966f, 0.893224f, 0.890449f, 0.887640f, 0.884797f, + 0.881921f, 0.879012f, 0.876070f, 0.873095f, 0.870087f, 0.867046f, 0.863973f, 0.860867f, + 0.857729f, 0.854558f, 0.851355f, 0.848120f, 0.844854f, 0.841555f, 0.838225f, 0.834863f, + 0.831470f, 0.828045f, 0.824589f, 0.821103f, 0.817585f, 0.814036f, 0.810457f, 0.806848f, + 0.803208f, 0.799537f, 0.795837f, 0.792107f, 0.788346f, 0.784557f, 0.780737f, 0.776888f, + 0.773010f, 0.769103f, 0.765167f, 0.761202f, 0.757209f, 0.753187f, 0.749136f, 0.745058f, + 0.740951f, 0.736817f, 0.732654f, 0.728464f, 0.724247f, 0.720003f, 0.715731f, 0.711432f, + 0.707107f, 0.702755f, 0.698376f, 0.693971f, 0.689541f, 0.685084f, 0.680601f, 0.676093f, + 0.671559f, 0.667000f, 0.662416f, 0.657807f, 0.653173f, 0.648514f, 0.643832f, 0.639124f, + 0.634393f, 0.629638f, 0.624859f, 0.620057f, 0.615232f, 0.610383f, 0.605511f, 0.600616f, + 0.595699f, 0.590760f, 0.585798f, 0.580814f, 0.575808f, 0.570781f, 0.565732f, 0.560662f, + 0.555570f, 0.550458f, 0.545325f, 0.540171f, 0.534998f, 0.529804f, 0.524590f, 0.519356f, + 0.514103f, 0.508830f, 0.503538f, 0.498228f, 0.492898f, 0.487550f, 0.482184f, 0.476799f, + 0.471397f, 0.465976f, 0.460539f, 0.455084f, 0.449611f, 0.444122f, 0.438616f, 0.433094f, + 0.427555f, 0.422000f, 0.416430f, 0.410843f, 0.405241f, 0.399624f, 0.393992f, 0.388345f, + 0.382683f, 0.377007f, 0.371317f, 0.365613f, 0.359895f, 0.354164f, 0.348419f, 0.342661f, + 0.336890f, 0.331106f, 0.325310f, 0.319502f, 0.313682f, 0.307850f, 0.302006f, 0.296151f, + 0.290285f, 0.284408f, 0.278520f, 0.272621f, 0.266713f, 0.260794f, 0.254866f, 0.248928f, + 0.242980f, 0.237024f, 0.231058f, 0.225084f, 0.219101f, 0.213110f, 0.207111f, 0.201105f, + 0.195090f, 0.189069f, 0.183040f, 0.177004f, 0.170962f, 0.164913f, 0.158858f, 0.152797f, + 0.146730f, 0.140658f, 0.134581f, 0.128498f, 0.122411f, 0.116319f, 0.110222f, 0.104122f, + 0.098017f, 0.091909f, 0.085797f, 0.079682f, 0.073565f, 0.067444f, 0.061321f, 0.055195f, + 0.049068f, 0.042938f, 0.036807f, 0.030675f, 0.024541f, 0.018407f, 0.012272f, 0.006136f, + 0.000000f, -0.006136f, -0.012272f, -0.018407f, -0.024541f, -0.030675f, -0.036807f, -0.042938f, + -0.049068f, -0.055195f, -0.061321f, -0.067444f, -0.073565f, -0.079682f, -0.085797f, -0.091909f, + -0.098017f, -0.104122f, -0.110222f, -0.116319f, -0.122411f, -0.128498f, -0.134581f, -0.140658f, + -0.146730f, -0.152797f, -0.158858f, -0.164913f, -0.170962f, -0.177004f, -0.183040f, -0.189069f, + -0.195090f, -0.201105f, -0.207111f, -0.213110f, -0.219101f, -0.225084f, -0.231058f, -0.237024f, + -0.242980f, -0.248928f, -0.254866f, -0.260794f, -0.266713f, -0.272621f, -0.278520f, -0.284408f, + -0.290285f, -0.296151f, -0.302006f, -0.307850f, -0.313682f, -0.319502f, -0.325310f, -0.331106f, + -0.336890f, -0.342661f, -0.348419f, -0.354164f, -0.359895f, -0.365613f, -0.371317f, -0.377007f, + -0.382683f, -0.388345f, -0.393992f, -0.399624f, -0.405241f, -0.410843f, -0.416430f, -0.422000f, + -0.427555f, -0.433094f, -0.438616f, -0.444122f, -0.449611f, -0.455084f, -0.460539f, -0.465976f, + -0.471397f, -0.476799f, -0.482184f, -0.487550f, -0.492898f, -0.498228f, -0.503538f, -0.508830f, + -0.514103f, -0.519356f, -0.524590f, -0.529804f, -0.534998f, -0.540171f, -0.545325f, -0.550458f, + -0.555570f, -0.560662f, -0.565732f, -0.570781f, -0.575808f, -0.580814f, -0.585798f, -0.590760f, + -0.595699f, -0.600616f, -0.605511f, -0.610383f, -0.615232f, -0.620057f, -0.624859f, -0.629638f, + -0.634393f, -0.639124f, -0.643832f, -0.648514f, -0.653173f, -0.657807f, -0.662416f, -0.667000f, + -0.671559f, -0.676093f, -0.680601f, -0.685084f, -0.689541f, -0.693971f, -0.698376f, -0.702755f, + -0.707107f, -0.711432f, -0.715731f, -0.720003f, -0.724247f, -0.728464f, -0.732654f, -0.736817f, + -0.740951f, -0.745058f, -0.749136f, -0.753187f, -0.757209f, -0.761202f, -0.765167f, -0.769103f, + -0.773010f, -0.776888f, -0.780737f, -0.784557f, -0.788346f, -0.792107f, -0.795837f, -0.799537f, + -0.803208f, -0.806848f, -0.810457f, -0.814036f, -0.817585f, -0.821103f, -0.824589f, -0.828045f, + -0.831470f, -0.834863f, -0.838225f, -0.841555f, -0.844854f, -0.848120f, -0.851355f, -0.854558f, + -0.857729f, -0.860867f, -0.863973f, -0.867046f, -0.870087f, -0.873095f, -0.876070f, -0.879012f, + -0.881921f, -0.884797f, -0.887640f, -0.890449f, -0.893224f, -0.895966f, -0.898674f, -0.901349f, + -0.903989f, -0.906596f, -0.909168f, -0.911706f, -0.914210f, -0.916679f, -0.919114f, -0.921514f, + -0.923880f, -0.926210f, -0.928506f, -0.930767f, -0.932993f, -0.935184f, -0.937339f, -0.939459f, + -0.941544f, -0.943593f, -0.945607f, -0.947586f, -0.949528f, -0.951435f, -0.953306f, -0.955141f, + -0.956940f, -0.958703f, -0.960431f, -0.962121f, -0.963776f, -0.965394f, -0.966976f, -0.968522f, + -0.970031f, -0.971504f, -0.972940f, -0.974339f, -0.975702f, -0.977028f, -0.978317f, -0.979570f, + -0.980785f, -0.981964f, -0.983105f, -0.984210f, -0.985278f, -0.986308f, -0.987301f, -0.988258f, + -0.989177f, -0.990058f, -0.990903f, -0.991710f, -0.992480f, -0.993212f, -0.993907f, -0.994565f, + -0.995185f, -0.995767f, -0.996313f, -0.996820f, -0.997290f, -0.997723f, -0.998118f, -0.998476f, + -0.998795f, -0.999078f, -0.999322f, -0.999529f, -0.999699f, -0.999831f, -0.999925f, -0.999981f +}; + +OMV_ATTR_ALWAYS_INLINE static float get_cos(int k, int N_pow2) // N=512 -> N=pow2=9 +{ + return fft_cos_table[k << (9 - N_pow2)]; +} + +OMV_ATTR_ALWAYS_INLINE static float get_ai(int k, int N_pow2) // N=512 -> N=pow2=9 +{ + return 0.5 * (-get_cos(k, N_pow2)); +} + +OMV_ATTR_ALWAYS_INLINE static float get_bi(int k, int N_pow2) // N=512 -> N=pow2=9 +{ + return 0.5 * (+get_cos(k, N_pow2)); +} + +OMV_ATTR_ALWAYS_INLINE static float get_a_star_i(int k, int N_pow2) // N=512 -> N=pow2=9 +{ + return 0.5 * (+get_cos(k, N_pow2)); +} + +OMV_ATTR_ALWAYS_INLINE static float get_b_star_i(int k, int N_pow2) // N=512 -> N=pow2=9 +{ + return 0.5 * (-get_cos(k, N_pow2)); +} + +//// For samples 0 to (n/2)-1 --- Note: clog2(n/2) = N_pow2 +//OMV_ATTR_ALWAYS_INLINE static float get_hann_l_side(int k, int N_pow2) +//{ +// return 0.5 * (1 - get_cos(k, N_pow2)); +//} + +//// For samples (n/2) to n-1 --- Note: clog2(n/2) = N_pow2 +//OMV_ATTR_ALWAYS_INLINE static float get_hann_r_side(int k, int N_pow2) +//{ +// return 0.5 * (1 - get_cos((2 << N_pow2) - k - 1, N_pow2)); +//} + +const static float fft_sin_table[512] = { + 0.000000f, 0.006136f, 0.012272f, 0.018407f, 0.024541f, 0.030675f, 0.036807f, 0.042938f, + 0.049068f, 0.055195f, 0.061321f, 0.067444f, 0.073565f, 0.079682f, 0.085797f, 0.091909f, + 0.098017f, 0.104122f, 0.110222f, 0.116319f, 0.122411f, 0.128498f, 0.134581f, 0.140658f, + 0.146730f, 0.152797f, 0.158858f, 0.164913f, 0.170962f, 0.177004f, 0.183040f, 0.189069f, + 0.195090f, 0.201105f, 0.207111f, 0.213110f, 0.219101f, 0.225084f, 0.231058f, 0.237024f, + 0.242980f, 0.248928f, 0.254866f, 0.260794f, 0.266713f, 0.272621f, 0.278520f, 0.284408f, + 0.290285f, 0.296151f, 0.302006f, 0.307850f, 0.313682f, 0.319502f, 0.325310f, 0.331106f, + 0.336890f, 0.342661f, 0.348419f, 0.354164f, 0.359895f, 0.365613f, 0.371317f, 0.377007f, + 0.382683f, 0.388345f, 0.393992f, 0.399624f, 0.405241f, 0.410843f, 0.416430f, 0.422000f, + 0.427555f, 0.433094f, 0.438616f, 0.444122f, 0.449611f, 0.455084f, 0.460539f, 0.465976f, + 0.471397f, 0.476799f, 0.482184f, 0.487550f, 0.492898f, 0.498228f, 0.503538f, 0.508830f, + 0.514103f, 0.519356f, 0.524590f, 0.529804f, 0.534998f, 0.540171f, 0.545325f, 0.550458f, + 0.555570f, 0.560662f, 0.565732f, 0.570781f, 0.575808f, 0.580814f, 0.585798f, 0.590760f, + 0.595699f, 0.600616f, 0.605511f, 0.610383f, 0.615232f, 0.620057f, 0.624859f, 0.629638f, + 0.634393f, 0.639124f, 0.643832f, 0.648514f, 0.653173f, 0.657807f, 0.662416f, 0.667000f, + 0.671559f, 0.676093f, 0.680601f, 0.685084f, 0.689541f, 0.693971f, 0.698376f, 0.702755f, + 0.707107f, 0.711432f, 0.715731f, 0.720003f, 0.724247f, 0.728464f, 0.732654f, 0.736817f, + 0.740951f, 0.745058f, 0.749136f, 0.753187f, 0.757209f, 0.761202f, 0.765167f, 0.769103f, + 0.773010f, 0.776888f, 0.780737f, 0.784557f, 0.788346f, 0.792107f, 0.795837f, 0.799537f, + 0.803208f, 0.806848f, 0.810457f, 0.814036f, 0.817585f, 0.821103f, 0.824589f, 0.828045f, + 0.831470f, 0.834863f, 0.838225f, 0.841555f, 0.844854f, 0.848120f, 0.851355f, 0.854558f, + 0.857729f, 0.860867f, 0.863973f, 0.867046f, 0.870087f, 0.873095f, 0.876070f, 0.879012f, + 0.881921f, 0.884797f, 0.887640f, 0.890449f, 0.893224f, 0.895966f, 0.898674f, 0.901349f, + 0.903989f, 0.906596f, 0.909168f, 0.911706f, 0.914210f, 0.916679f, 0.919114f, 0.921514f, + 0.923880f, 0.926210f, 0.928506f, 0.930767f, 0.932993f, 0.935184f, 0.937339f, 0.939459f, + 0.941544f, 0.943593f, 0.945607f, 0.947586f, 0.949528f, 0.951435f, 0.953306f, 0.955141f, + 0.956940f, 0.958703f, 0.960431f, 0.962121f, 0.963776f, 0.965394f, 0.966976f, 0.968522f, + 0.970031f, 0.971504f, 0.972940f, 0.974339f, 0.975702f, 0.977028f, 0.978317f, 0.979570f, + 0.980785f, 0.981964f, 0.983105f, 0.984210f, 0.985278f, 0.986308f, 0.987301f, 0.988258f, + 0.989177f, 0.990058f, 0.990903f, 0.991710f, 0.992480f, 0.993212f, 0.993907f, 0.994565f, + 0.995185f, 0.995767f, 0.996313f, 0.996820f, 0.997290f, 0.997723f, 0.998118f, 0.998476f, + 0.998795f, 0.999078f, 0.999322f, 0.999529f, 0.999699f, 0.999831f, 0.999925f, 0.999981f, + 1.000000f, 0.999981f, 0.999925f, 0.999831f, 0.999699f, 0.999529f, 0.999322f, 0.999078f, + 0.998795f, 0.998476f, 0.998118f, 0.997723f, 0.997290f, 0.996820f, 0.996313f, 0.995767f, + 0.995185f, 0.994565f, 0.993907f, 0.993212f, 0.992480f, 0.991710f, 0.990903f, 0.990058f, + 0.989177f, 0.988258f, 0.987301f, 0.986308f, 0.985278f, 0.984210f, 0.983105f, 0.981964f, + 0.980785f, 0.979570f, 0.978317f, 0.977028f, 0.975702f, 0.974339f, 0.972940f, 0.971504f, + 0.970031f, 0.968522f, 0.966976f, 0.965394f, 0.963776f, 0.962121f, 0.960431f, 0.958703f, + 0.956940f, 0.955141f, 0.953306f, 0.951435f, 0.949528f, 0.947586f, 0.945607f, 0.943593f, + 0.941544f, 0.939459f, 0.937339f, 0.935184f, 0.932993f, 0.930767f, 0.928506f, 0.926210f, + 0.923880f, 0.921514f, 0.919114f, 0.916679f, 0.914210f, 0.911706f, 0.909168f, 0.906596f, + 0.903989f, 0.901349f, 0.898674f, 0.895966f, 0.893224f, 0.890449f, 0.887640f, 0.884797f, + 0.881921f, 0.879012f, 0.876070f, 0.873095f, 0.870087f, 0.867046f, 0.863973f, 0.860867f, + 0.857729f, 0.854558f, 0.851355f, 0.848120f, 0.844854f, 0.841555f, 0.838225f, 0.834863f, + 0.831470f, 0.828045f, 0.824589f, 0.821103f, 0.817585f, 0.814036f, 0.810457f, 0.806848f, + 0.803208f, 0.799537f, 0.795837f, 0.792107f, 0.788346f, 0.784557f, 0.780737f, 0.776888f, + 0.773010f, 0.769103f, 0.765167f, 0.761202f, 0.757209f, 0.753187f, 0.749136f, 0.745058f, + 0.740951f, 0.736817f, 0.732654f, 0.728464f, 0.724247f, 0.720003f, 0.715731f, 0.711432f, + 0.707107f, 0.702755f, 0.698376f, 0.693971f, 0.689541f, 0.685084f, 0.680601f, 0.676093f, + 0.671559f, 0.667000f, 0.662416f, 0.657807f, 0.653173f, 0.648514f, 0.643832f, 0.639124f, + 0.634393f, 0.629638f, 0.624859f, 0.620057f, 0.615232f, 0.610383f, 0.605511f, 0.600616f, + 0.595699f, 0.590760f, 0.585798f, 0.580814f, 0.575808f, 0.570781f, 0.565732f, 0.560662f, + 0.555570f, 0.550458f, 0.545325f, 0.540171f, 0.534998f, 0.529804f, 0.524590f, 0.519356f, + 0.514103f, 0.508830f, 0.503538f, 0.498228f, 0.492898f, 0.487550f, 0.482184f, 0.476799f, + 0.471397f, 0.465976f, 0.460539f, 0.455084f, 0.449611f, 0.444122f, 0.438616f, 0.433094f, + 0.427555f, 0.422000f, 0.416430f, 0.410843f, 0.405241f, 0.399624f, 0.393992f, 0.388345f, + 0.382683f, 0.377007f, 0.371317f, 0.365613f, 0.359895f, 0.354164f, 0.348419f, 0.342661f, + 0.336890f, 0.331106f, 0.325310f, 0.319502f, 0.313682f, 0.307850f, 0.302006f, 0.296151f, + 0.290285f, 0.284408f, 0.278520f, 0.272621f, 0.266713f, 0.260794f, 0.254866f, 0.248928f, + 0.242980f, 0.237024f, 0.231058f, 0.225084f, 0.219101f, 0.213110f, 0.207111f, 0.201105f, + 0.195090f, 0.189069f, 0.183040f, 0.177004f, 0.170962f, 0.164913f, 0.158858f, 0.152797f, + 0.146730f, 0.140658f, 0.134581f, 0.128498f, 0.122411f, 0.116319f, 0.110222f, 0.104122f, + 0.098017f, 0.091909f, 0.085797f, 0.079682f, 0.073565f, 0.067444f, 0.061321f, 0.055195f, + 0.049068f, 0.042938f, 0.036807f, 0.030675f, 0.024541f, 0.018407f, 0.012272f, 0.006136f +}; + +OMV_ATTR_ALWAYS_INLINE static float get_sin(int k, int N_pow2) // N=512 -> N=pow2=9 +{ + return fft_sin_table[k << (9 - N_pow2)]; +} + +OMV_ATTR_ALWAYS_INLINE static float get_ar(int k, int N_pow2) // N=512 -> N=pow2=9 +{ + return 0.5 * (1 - get_sin(k, N_pow2)); +} + +OMV_ATTR_ALWAYS_INLINE static float get_br(int k, int N_pow2) // N=512 -> N=pow2=9 +{ + return 0.5 * (1 + get_sin(k, N_pow2)); +} + +OMV_ATTR_ALWAYS_INLINE static float get_a_star_r(int k, int N_pow2) // N=512 -> N=pow2=9 +{ + return 0.5 * (1 - get_sin(k, N_pow2)); +} + +OMV_ATTR_ALWAYS_INLINE static float get_b_star_r(int k, int N_pow2) // N=512 -> N=pow2=9 +{ + return 0.5 * (1 + get_sin(k, N_pow2)); +} + +/////////////////////////////////////////////////////////////////////////////// + +// You give the FFT N real and imaginary pairs where each pair is an even/odd +// real value from 2N data. The FFT will then output N real and imaginary pairs +// and you can use the below function to unpack that into 2N real and imaginary +// pairs the FFT would normally output with 2N data. + +// Unpack 2N data from N point fft +// in = N real and complex floats +// out = 2N real and complex floats +static void unpack_fft(float *in, float *out, int N_pow2) +{ + for (int k = 0, l = 2 << N_pow2, m = l << 1; k < l; k += 2) { + int k_r = k+0; + int k_i = k+1; + int N_k_r = ((!k)?0:(l-k))+0; + int N_k_i = ((!k)?0:(l-k))+1; + int N2_K_r = ((!k)?0:(m-k))+0; + int N2_K_i = ((!k)?0:(m-k))+1; + int k_2 = k >> 1; + // real + out[k_r] = (in[k_r] * get_ar(k_2, N_pow2)) - + (in[k_i] * get_ai(k_2, N_pow2)) + + (in[N_k_r] * get_br(k_2, N_pow2)) + + (in[N_k_i] * get_bi(k_2, N_pow2)); + // imaginary + out[k_i] = (in[k_i] * get_ar(k_2, N_pow2)) + + (in[k_r] * get_ai(k_2, N_pow2)) + + (in[N_k_r] * get_bi(k_2, N_pow2)) - + (in[N_k_i] * get_br(k_2, N_pow2)); + if (k > 0) { + // real conj + out[N2_K_r] = out[k_r]; + // imaginary conj + out[N2_K_i] = -out[k_i]; + } + } + out[(2 << N_pow2)+0] = in[0+0] - in[0+1]; + out[(2 << N_pow2)+1] = 0; +} + +// The IFFT takes N real and imaginary pairs to generate N real and imaginary +// outputs with the imaginary part set to zero. To be more efficent this function +// packs 2N data into an N IFFT so that the N real and imaginary outputs have +// even/odd real values. + +// Pack 2N data to N point fft +// in = 2N real and complex floats +// out = N real and complex floats +static void pack_fft(float *in, float *out, int N_pow2) +{ + for (int k = 0, l = 2 << N_pow2; k < l; k += 2) { + int k_r = k+0; + int k_i = k+1; + int N_k_r = (l-k)+0; + int N_k_i = (l-k)+1; + int k_2 = k >> 1; + // real + out[k_r] = (in[k_r] * get_a_star_r(k_2, N_pow2)) - + (in[k_i] * get_a_star_i(k_2, N_pow2)) + + (in[N_k_r] * get_b_star_r(k_2, N_pow2)) + + (in[N_k_i] * get_b_star_i(k_2, N_pow2)); + // imaginary + out[k_i] = (in[k_i] * get_a_star_r(k_2, N_pow2)) + + (in[k_r] * get_a_star_i(k_2, N_pow2)) + + (in[N_k_r] * get_b_star_i(k_2, N_pow2)) - + (in[N_k_i] * get_b_star_r(k_2, N_pow2)); + } +} + +/////////////////////////////////////////////////////////////////////////////// + +OMV_ATTR_ALWAYS_INLINE static int int_flog2(int x) // floor log 2 +{ + return 31 - __CLZ(x); +} + +OMV_ATTR_ALWAYS_INLINE static int int_clog2(int x) // ceiling log 2 +{ + int y = int_flog2(x); + return (x - (1 << y)) ? (y + 1) : y; +} + +/////////////////////////////////////////////////////////////////////////////// + +// Input even numbered index +// Output even numbered index +OMV_ATTR_ALWAYS_INLINE static int bit_reverse(int index, int N_pow2) +{ + return __RBIT(index) >> (30 - N_pow2); +} + +OMV_ATTR_ALWAYS_INLINE static void swap(float *a, float *b) +{ + float tmp = *b; + *b = *a; + *a = tmp; +} + +//OMV_ATTR_ALWAYS_INLINE static float get_hann(int k, int N_pow2) +//{ +// if (k < (1 << N_pow2)) { +// return get_hann_l_side(k, N_pow2); +// } else { +// return get_hann_r_side(k, N_pow2); +// } +//} + +// Copies 2N real pairs (or pad with zero) from in to out while bit reversing +// their indexes. + +static void prepare_real_input(uint8_t *in, int in_len, float *out, int N_pow2) +{ + for (int k = 0, l = 2 << N_pow2; k < l; k += 2) { + int m = bit_reverse(k, N_pow2); + out[m+0] = ((k+0) < in_len) ? in[k+0] : 0; + out[m+1] = ((k+1) < in_len) ? in[k+1] : 0; +// // Apply Hann Window (this is working on real numbers) +// out[m+0] *= get_hann(k+0, N_pow2); +// out[m+1] *= get_hann(k+1, N_pow2); + } +} + +static void prepare_real_input_again(float *in, int in_len, float *out, int N_pow2) +{ + for (int k = 0, l = 2 << N_pow2; k < l; k += 2) { + int m = bit_reverse(k, N_pow2); + out[m+0] = ((k+0) < in_len) ? in[(k*2)+0] : 0; + out[m+1] = ((k+1) < in_len) ? in[(k*2)+2] : 0; +// // Apply Hann Window (this is working on real numbers) +// out[m+0] *= get_hann(k+0, N_pow2); +// out[m+1] *= get_hann(k+1, N_pow2); + } +} + +//// This works on complex numbers... +//static void apply_hann_window(float *inout, int N_pow2, int stride) +//{ +// for (int k = 0, l = 2 << N_pow2; k < l; k += 2) { +// inout[(k*stride)+0] *= get_hann(k>>1, N_pow2-1); +// inout[(k*stride)+1] *= get_hann(k>>1, N_pow2-1); +// } +//} + +// Copies N complex pairs from in to out while bit reversing their indexes. The +// in and out arrays may be the same. + +static void prepare_complex_input(float *in, float *out, int N_pow2, int stride) +{ + if(in == out) { + for (int k = 0, l = 2 << N_pow2; k < l; k += 2) { + int m = bit_reverse(k, N_pow2); + if (k < m) { + swap(out+(m*stride)+0, in+(k*stride)+0); + swap(out+(m*stride)+1, in+(k*stride)+1); + } + } + } else { + for (int k = 0, l = 2 << N_pow2; k < l; k += 2) { + int m = bit_reverse(k, N_pow2); + out[(m*stride)+0] = in[(k*stride)+0]; + out[(m*stride)+1] = in[(k*stride)+1]; + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +// Performs the fft in place. +static void do_fft(float *inout, int N_pow2, int stride) +{ + int N = 2 << N_pow2; + for (int N_pow2_i = 1; N_pow2_i <= N_pow2; N_pow2_i++) { + int N_mul2 = 2 << N_pow2_i; + int N_div2 = 1 << N_pow2_i; + for (int i = 0; i < N; i += N_mul2) { + for (int j = i, k = 0, l = i + N_div2, m = N >> N_pow2_i; j < l; j += 2, k += m) { + int x0_r = (j*stride)+0; + int x0_i = (j*stride)+1; + int x1_r = ((j+N_div2)*stride)+0; + int x1_i = ((j+N_div2)*stride)+1; + float tmp_r = (inout[x1_r] * get_cos(k, N_pow2)) + + (inout[x1_i] * get_sin(k, N_pow2)); + float tmp_i = (inout[x1_i] * get_cos(k, N_pow2)) - + (inout[x1_r] * get_sin(k, N_pow2)); + inout[x1_r] = inout[x0_r] - tmp_r; + inout[x1_i] = inout[x0_i] - tmp_i; + inout[x0_r] += tmp_r; + inout[x0_i] += tmp_i; + } + } + } +} + +// Performs the ifft in place. +static void do_ifft(float *inout, int N_pow2, int stride) +{ + int N = 2 << N_pow2; + for (int N_pow2_i = 1; N_pow2_i <= N_pow2; N_pow2_i++) { + int N_mul2 = 2 << N_pow2_i; + int N_div2 = 1 << N_pow2_i; + for (int i = 0; i < N; i += N_mul2) { + for (int j = i, k = 0, l = i + N_div2, m = N >> N_pow2_i; j < l; j += 2, k += m) { + int x0_r = (j*stride)+0; + int x0_i = (j*stride)+1; + int x1_r = ((j+N_div2)*stride)+0; + int x1_i = ((j+N_div2)*stride)+1; + float tmp_r = (inout[x1_r] * get_cos(k, N_pow2)) - + (inout[x1_i] * get_sin(k, N_pow2)); + float tmp_i = (inout[x1_i] * get_cos(k, N_pow2)) + + (inout[x1_r] * get_sin(k, N_pow2)); + inout[x1_r] = inout[x0_r] - tmp_r; + inout[x1_i] = inout[x0_i] - tmp_i; + inout[x0_r] += tmp_r; + inout[x0_i] += tmp_i; + } + } + } + + float div = 1.0 / (N >> 1); + for (int i = 0; i < N; i += 2) { + inout[(i*stride)+0] *= div; + inout[(i*stride)+1] *= div; + } +} + +/////////////////////////////////////////////////////////////////////////////// + +void fft1d_alloc(fft1d_controller_t *controller, uint8_t *buf, int len) +{ + controller->d_pointer = buf; + controller->d_len = len; + controller->pow2 = int_clog2(len); + controller->data = fb_alloc((2 << controller->pow2) * sizeof(float), FB_ALLOC_NO_HINT); +} + +void fft1d_dealloc(void *msm) +{ + fb_free(msm); +} + +void fft1d_run(fft1d_controller_t *controller) +{ + // We can speed up the FFT by packing data into both the real and imaginary + // values. This results in having to do an FFT of half the size normally. + + float *h_buffer = fb_alloc((1 << controller->pow2) * sizeof(float), FB_ALLOC_NO_HINT); + prepare_real_input(controller->d_pointer, controller->d_len, + h_buffer, controller->pow2 - 1); + do_fft(h_buffer, controller->pow2 - 1, 1); + unpack_fft(h_buffer, controller->data, controller->pow2 - 1); + fb_free(h_buffer); +} + +void ifft1d_run(fft1d_controller_t *controller) +{ + // We can speed up the FFT by packing data into both the real and imaginary + // values. This results in having to do an FFT of half the size normally. + + float *h_buffer = fb_alloc((1 << controller->pow2) * sizeof(float), FB_ALLOC_NO_HINT); + pack_fft(controller->data, h_buffer, controller->pow2 - 1); + prepare_complex_input(h_buffer, h_buffer, + controller->pow2 - 1, 1); + do_ifft(h_buffer, controller->pow2 - 1, 1); + memset(controller->data, 0, (2 << controller->pow2) * sizeof(float)); + memcpy(controller->data, h_buffer, (1 << controller->pow2) * sizeof(float)); + fb_free(h_buffer); +} + +void fft1d_mag(fft1d_controller_t *controller) +{ + for (int i = 0, j = 2 << controller->pow2; i < j; i += 2) { + float tmp_r = controller->data[i + 0]; + float tmp_i = controller->data[i + 1]; + controller->data[i + 0] = fast_sqrtf((tmp_r*tmp_r)+(tmp_i*tmp_i)); + controller->data[i + 1] = 0; + } +} + +void fft1d_phase(fft1d_controller_t *controller) +{ + for (int i = 0, j = 2 << controller->pow2; i < j; i += 2) { + float tmp_r = controller->data[i + 0]; + float tmp_i = controller->data[i + 1]; + controller->data[i + 0] = tmp_r ? fast_atan2f(tmp_i, tmp_r) : ((tmp_i < 0) ? (M_PI*1.5) : (M_PI*0.5)); + controller->data[i + 1] = 0; + } +} + +void fft1d_log(fft1d_controller_t *controller) +{ + for (int i = 0, j = 2 << controller->pow2; i < j; i += 2) { + float tmp_r = controller->data[i + 0]; + float tmp_i = controller->data[i + 1]; + controller->data[i + 0] = fast_log(fast_sqrtf((tmp_r*tmp_r)+(tmp_i*tmp_i))); + controller->data[i + 1] = tmp_r ? fast_atan2f(tmp_i, tmp_r) : ((tmp_i < 0) ? (M_PI*1.5) : (M_PI*0.5)); + } +} + +void fft1d_exp(fft1d_controller_t *controller) +{ + for (int i = 0, j = 2 << controller->pow2; i < j; i += 2) { + float tmp_r = controller->data[i + 0]; + float tmp_i = controller->data[i + 1]; + controller->data[i + 0] = fast_expf(tmp_r) * cosf(tmp_i); + controller->data[i + 1] = fast_expf(tmp_r) * sinf(tmp_i); + } +} + +void fft1d_swap(fft1d_controller_t *controller) +{ + for (int i = 0, j = ((1 << controller->pow2) / 2) * 2; i < j; i += 2) { + float tmp_r = controller->data[i + 0]; + float tmp_i = controller->data[i + 1]; + controller->data[i + 0] = controller->data[j + i + 0]; + controller->data[i + 1] = controller->data[j + i + 1]; + controller->data[j + i + 0] = tmp_r; + controller->data[j + i + 1] = tmp_i; + } +} + +void fft1d_run_again(fft1d_controller_t *controller) +{ + // We can speed up the FFT by packing data into both the real and imaginary + // values. This results in having to do an FFT of half the size normally. + + float *h_buffer = fb_alloc((1 << controller->pow2) * sizeof(float), FB_ALLOC_NO_HINT); + prepare_real_input_again(controller->data, 1 << controller->pow2, + h_buffer, controller->pow2 - 1); + do_fft(h_buffer, controller->pow2 - 1, 1); + unpack_fft(h_buffer, controller->data, controller->pow2 - 1); + fb_free(h_buffer); +} + +/////////////////////////////////////////////////////////////////////////////// + +void fft2d_alloc(fft2d_controller_t *controller, image_t *img, rectangle_t *r) +{ + controller->img = img; + if (!rectangle_subimg(controller->img, r, &controller->r)) { + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("No intersection!")); + } + + controller->w_pow2 = int_clog2(controller->r.w); + controller->h_pow2 = int_clog2(controller->r.h); + + controller->data = + fb_alloc0(2 * (1 << controller->w_pow2) * (1 << controller->h_pow2) * sizeof(float), FB_ALLOC_NO_HINT); +} + +void fft2d_dealloc(void *msm) +{ + fb_free(msm); +} + +void fft2d_run(fft2d_controller_t *controller) +{ + // This section copies image data into the fft buffer. It takes care of + // extracting the grey channel from RGB images if necessary. The code + // also handles dealing with a rect less than the image size. + for (int i = 0; i < controller->r.h; i++) { + // Get image data into buffer. + uint8_t *tmp = fb_alloc(controller->r.w * sizeof(uint8_t), FB_ALLOC_NO_HINT); + for (int j = 0; j < controller->r.w; j++) { + if (IM_IS_GS(controller->img)) { + tmp[j] = IM_GET_GS_PIXEL(controller->img, + controller->r.x + j, controller->r.y + i); + } else { + tmp[j] = COLOR_RGB565_TO_Y(IM_GET_RGB565_PIXEL(controller->img, + controller->r.x + j, controller->r.y + i)); + } + } + // Do FFT on image data and copy to main buffer. + fft1d_controller_t fft1d_controller_i; + fft1d_alloc(&fft1d_controller_i, tmp, controller->r.w); + fft1d_run(&fft1d_controller_i); + memcpy(controller->data + (i * (2 << controller->w_pow2)), + fft1d_controller_i.data, (2 << fft1d_controller_i.pow2) * sizeof(float)); + fft1d_dealloc(fft1d_controller_i.data); + // Free image data buffer. + fb_free(tmp); + } + + // The above operates on the rows and this fft operates on the columns. To + // avoid having to transpose the array the fft takes a stride input. + for (int i = 0, ii = 2 << controller->w_pow2; i < ii; i += 2) { + float *p = controller->data + i; +// apply_hann_window(p, controller->h_pow2, (1 << controller->w_pow2)); + prepare_complex_input(p, p, controller->h_pow2, (1 << controller->w_pow2)); + do_fft(p, controller->h_pow2, (1 << controller->w_pow2)); + } +} + +void ifft2d_run(fft2d_controller_t *controller) +{ + // Do columns... + for (int i = 0, ii = 2 << controller->w_pow2; i < ii; i += 2) { + float *p = controller->data + i; + prepare_complex_input(p, p, controller->h_pow2, (1 << controller->w_pow2)); + do_ifft(p, controller->h_pow2, (1 << controller->w_pow2)); + } + + // Do rows... + for (int i = 0, ii = 1 << controller->h_pow2; i < ii; i++) { + fft1d_controller_t fft1d_controller_i; + fft1d_controller_i.pow2 = controller->w_pow2; + fft1d_controller_i.data = controller->data + (i * (2 << controller->w_pow2)); + ifft1d_run(&fft1d_controller_i); + } +} + +void fft2d_mag(fft2d_controller_t *controller) +{ + for (int i = 0, j = (1 << controller->h_pow2) * (1 << controller->w_pow2) * 2; i < j; i += 2) { + float tmp_r = controller->data[i + 0]; + float tmp_i = controller->data[i + 1]; + controller->data[i + 0] = fast_sqrtf((tmp_r*tmp_r)+(tmp_i*tmp_i)); + controller->data[i + 1] = 0; + } +} + +void fft2d_phase(fft2d_controller_t *controller) +{ + for (int i = 0, j = (1 << controller->h_pow2) * (1 << controller->w_pow2) * 2; i < j; i += 2) { + float tmp_r = controller->data[i + 0]; + float tmp_i = controller->data[i + 1]; + controller->data[i + 0] = tmp_r ? fast_atan2f(tmp_i, tmp_r) : ((tmp_i < 0) ? (M_PI*1.5) : (M_PI*0.5)); + controller->data[i + 1] = 0; + } +} + +void fft2d_log(fft2d_controller_t *controller) +{ + for (int i = 0, j = (1 << controller->h_pow2) * (1 << controller->w_pow2) * 2; i < j; i += 2) { + float tmp_r = controller->data[i + 0]; + float tmp_i = controller->data[i + 1]; + controller->data[i + 0] = fast_log(fast_sqrtf((tmp_r*tmp_r)+(tmp_i*tmp_i))); + controller->data[i + 1] = tmp_r ? fast_atan2f(tmp_i, tmp_r) : ((tmp_i < 0) ? (M_PI*1.5) : (M_PI*0.5)); + } +} + +void fft2d_exp(fft2d_controller_t *controller) +{ + for (int i = 0, j = (1 << controller->h_pow2) * (1 << controller->w_pow2) * 2; i < j; i += 2) { + float tmp_r = controller->data[i + 0]; + float tmp_i = controller->data[i + 1]; + controller->data[i + 0] = fast_expf(tmp_r) * cosf(tmp_i); + controller->data[i + 1] = fast_expf(tmp_r) * sinf(tmp_i); + } +} + +void fft2d_swap(fft2d_controller_t *controller) +{ + // Do rows... + for (int i = 0, ii = 1 << controller->h_pow2; i < ii; i++) { + fft1d_controller_t fft1d_controller_i; + fft1d_controller_i.pow2 = controller->w_pow2; + fft1d_controller_i.data = controller->data + (i * (2 << controller->w_pow2)); + fft1d_swap(&fft1d_controller_i); + } + + // Do columns... + for (int x = 0, xx = 2 << controller->w_pow2; x < xx; x += 2) { + for (int y = 0, yy = (1 << controller->h_pow2) / 2; y < yy; y++) { + int i = (y * (2 << controller->w_pow2)) + x; + int j = yy * (2 << controller->w_pow2); + float tmp_r = controller->data[i + 0]; + float tmp_i = controller->data[i + 1]; + controller->data[i + 0] = controller->data[j + i + 0]; + controller->data[i + 1] = controller->data[j + i + 1]; + controller->data[j + i + 0] = tmp_r; + controller->data[j + i + 1] = tmp_i; + } + } +} + +void fft2d_linpolar(fft2d_controller_t *controller) +{ + int w = 1 << controller->w_pow2; + int h = 1 << controller->h_pow2; + int s = h * w * 2 * sizeof(float); + float *tmp = fb_alloc(s, FB_ALLOC_NO_HINT); + memcpy(tmp, controller->data, s); + memset(controller->data, 0, s); + + float w_2 = w / 2.0f; + float h_2 = h / 2.0f; + float rho_scale = fast_sqrtf((w_2 * w_2) + (h_2 * h_2)) / h; + float theta_scale = 360.0f / w; + + for (int y = 0; y < h; y++) { + float *row_ptr = controller->data + (y * w * 2); + float rho = y * rho_scale; + for (int x = 0; x < w; x++) { + int sourceX, sourceY; + int theta = 630 - fast_roundf(x * theta_scale); + if (theta >= 360) theta -= 360; + sourceX = fast_roundf((rho * cos_table[theta]) + w_2); + sourceY = fast_roundf((rho * sin_table[theta]) + h_2); + if ((0 <= sourceX) && (sourceX < w) && (0 <= sourceY) && (sourceY < h)) { + float *ptr = tmp + (sourceY * w * 2); + row_ptr[(x * 2) + 0] = ptr[(sourceX * 2) + 0]; + row_ptr[(x * 2) + 1] = ptr[(sourceX * 2) + 1]; + } + } + } + + fb_free(tmp); +} + +void fft2d_logpolar(fft2d_controller_t *controller) +{ + int w = 1 << controller->w_pow2; + int h = 1 << controller->h_pow2; + int s = h * w * 2 * sizeof(float); + float *tmp = fb_alloc(s, FB_ALLOC_NO_HINT); + memcpy(tmp, controller->data, s); + memset(controller->data, 0, s); + + float w_2 = w / 2.0f; + float h_2 = h / 2.0f; + float rho_scale = fast_log(fast_sqrtf((w_2 * w_2) + (h_2 * h_2))) / h; + float theta_scale = 360.0f / w; + + for (int y = 0; y < h; y++) { + float *row_ptr = controller->data + (y * w * 2); + float rho = y * rho_scale; + for (int x = 0; x < w; x++) { + int sourceX, sourceY; + int theta = 630 - fast_roundf(x * theta_scale); + if (theta >= 360) theta -= 360; + sourceX = fast_roundf((fast_expf(rho) * cos_table[theta]) + w_2); + sourceY = fast_roundf((fast_expf(rho) * sin_table[theta]) + h_2); + if ((0 <= sourceX) && (sourceX < w) && (0 <= sourceY) && (sourceY < h)) { + float *ptr = tmp + (sourceY * w * 2); + row_ptr[(x * 2) + 0] = ptr[(sourceX * 2) + 0]; + row_ptr[(x * 2) + 1] = ptr[(sourceX * 2) + 1]; + } + } + } + + fb_free(tmp); +} + +void fft2d_run_again(fft2d_controller_t *controller) +{ + for (int i = 0, ii = 1 << controller->h_pow2; i < ii; i++) { + fft1d_controller_t fft1d_controller_i; + fft1d_controller_i.pow2 = controller->w_pow2; + fft1d_controller_i.data = controller->data + (i * (2 << controller->w_pow2)); + fft1d_run_again(&fft1d_controller_i); + } + + // The above operates on the rows and this fft operates on the columns. To + // avoid having to transpose the array the fft takes a stride input. + for (int i = 0, ii = 2 << controller->w_pow2; i < ii; i += 2) { + float *p = controller->data + i; +// apply_hann_window(p, controller->h_pow2, (1 << controller->w_pow2)); + prepare_complex_input(p, p, controller->h_pow2, (1 << controller->w_pow2)); + do_fft(p, controller->h_pow2, (1 << controller->w_pow2)); + } +} diff --git a/github_source/minicv2/src/filter.c b/github_source/minicv2/src/filter.c new file mode 100644 index 0000000..c17d7b5 --- /dev/null +++ b/github_source/minicv2/src/filter.c @@ -0,0 +1,2569 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Image filtering functions. + */ +#include "fsort.h" +#include "imlib.h" + +void imlib_histeq(image_t *img, image_t *mask) +{ + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + int a = img->w * img->h; + float s = (COLOR_BINARY_MAX - COLOR_BINARY_MIN) / ((float) a); + uint32_t *hist = fb_alloc0((COLOR_BINARY_MAX - COLOR_BINARY_MIN + 1) * sizeof(uint32_t), FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + hist[IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x) - COLOR_BINARY_MIN] += 1; + } + } + + for (int i = 0, sum = 0, ii = COLOR_BINARY_MAX - COLOR_BINARY_MIN + 1; i < ii; i++) { + sum += hist[i]; + hist[i] = sum; + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, + fast_floorf((s * hist[pixel - COLOR_BINARY_MIN]) + COLOR_BINARY_MIN)); + } + } + + fb_free(hist); + break; + } + case PIXFORMAT_GRAYSCALE: { + int a = img->w * img->h; + float s = (COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN) / ((float) a); + uint32_t *hist = fb_alloc0((COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1) * sizeof(uint32_t), FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + hist[IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x) - COLOR_GRAYSCALE_MIN] += 1; + } + } + + for (int i = 0, sum = 0, ii = COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1; i < ii; i++) { + sum += hist[i]; + hist[i] = sum; + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, + fast_floorf((s * hist[pixel - COLOR_GRAYSCALE_MIN]) + COLOR_GRAYSCALE_MIN)); + } + } + + fb_free(hist); + break; + } + case PIXFORMAT_RGB565: { + int a = img->w * img->h; + float s = (COLOR_Y_MAX - COLOR_Y_MIN) / ((float) a); + uint32_t *hist = fb_alloc0((COLOR_Y_MAX - COLOR_Y_MIN + 1) * sizeof(uint32_t), FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + hist[COLOR_RGB565_TO_Y(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)) - COLOR_Y_MIN] += 1; + } + } + + for (int i = 0, sum = 0, ii = COLOR_Y_MAX - COLOR_Y_MIN + 1; i < ii; i++) { + sum += hist[i]; + hist[i] = sum; + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); + int r = COLOR_RGB565_TO_R8(pixel); + int g = COLOR_RGB565_TO_G8(pixel); + int b = COLOR_RGB565_TO_B8(pixel); + uint8_t y, u, v; + y = (uint8_t)(((r * 9770) + (g * 19182) + (b * 3736)) >> 15); // .299*r + .587*g + .114*b + u = (uint8_t)(((b << 14) - (r * 5529) - (g * 10855)) >> 15); // -0.168736*r + -0.331264*g + 0.5*b + v = (uint8_t)(((r << 14) - (g * 13682) - (b * 2664)) >> 15); // 0.5*r + -0.418688*g + -0.081312*b + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, imlib_yuv_to_rgb(fast_floorf(s * hist[y]), u,v)); + } + } + + fb_free(hist); + break; + } + case PIXFORMAT_RGB888: { + int a = img->w * img->h; + float s = (COLOR_Y_MAX - COLOR_Y_MIN) / ((float) a); + uint32_t *hist = fb_alloc0((COLOR_Y_MAX - COLOR_Y_MIN + 1) * sizeof(uint32_t), FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + hist[COLOR_RGB888_TO_Y(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)) - COLOR_Y_MIN] += 1; + } + } + + for (int i = 0, sum = 0, ii = COLOR_Y_MAX - COLOR_Y_MIN + 1; i < ii; i++) { + sum += hist[i]; + hist[i] = sum; + } + + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) continue; + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + int r = COLOR_RGB888_TO_R8(pixel); + int g = COLOR_RGB888_TO_G8(pixel); + int b = COLOR_RGB888_TO_B8(pixel); + uint8_t y, u, v; + y = (uint8_t)(((r * 9770) + (g * 19182) + (b * 3736)) >> 15); // .299*r + .587*g + .114*b + u = (uint8_t)(((b << 14) - (r * 5529) - (g * 10855)) >> 15); // -0.168736*r + -0.331264*g + 0.5*b + v = (uint8_t)(((r << 14) - (g * 13682) - (b * 2664)) >> 15); // 0.5*r + -0.418688*g + -0.081312*b + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr, x, imlib_yuv_to_rgb888(fast_floorf(s * hist[y]), u,v)); + } + } + + fb_free(hist); + break; + } + default: { + break; + } + } +} + +// ksize == 0 -> 1x1 kernel +// ksize == 1 -> 3x3 kernel +// ... +// ksize == n -> ((n*2)+1)x((n*2)+1) kernel +// +// To speed up this filter, we can help in two ways: +// 1) For the 'center portion' of the image area, we don't need to check +// the x+y values against the boundary conditions on each pixel. +// 2) In that same region we can take advantage of the filter property being +// the sum of all of the pixels by subtracting the last left edge values +// and adding the new right edge values instead of re-calculating the sum +// of every pixel. This will allow very large filters to be used without +// much change in performance. +// +#ifdef IMLIB_ENABLE_MEAN +void imlib_mean_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert, image_t *mask) +{ + int brows = ksize + 1; + image_t buf; + buf.w = img->w; + buf.h = brows; + buf.pixfmt = img->pixfmt; + + int32_t over32_n = 65536 / (((ksize*2)+1)*((ksize*2)+1)); + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + int pixel, acc = 0; + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + if (!mask && x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y + j); + acc -= IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, x - ksize - 1); + acc += IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, x + ksize); + } + } else { + acc = 0; + + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + acc += IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + } + } + } + + pixel = (int)((acc * over32_n)>>16); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_BINARY_MAX; + } else { + pixel = COLOR_BINARY_MIN; + } + } + + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + case PIXFORMAT_GRAYSCALE: { + buf.data = fb_alloc(IMAGE_GRAYSCALE_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + int pixel, acc = 0; + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint8_t *buf_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + if (!mask && x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j<= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y+j); + acc -= IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, x-ksize-1); + acc += IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, x+ksize); + } + } else { + acc = 0; + for (int j = -ksize; j <= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + acc += IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + } + } + } + + pixel = (int)((acc * over32_n)>>16); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_GRAYSCALE_BINARY_MAX; + } else { + pixel = COLOR_GRAYSCALE_BINARY_MIN; + } + } + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + case PIXFORMAT_RGB565: { + int pixel, r, g, b, r_acc, g_acc, b_acc; + buf.data = fb_alloc(IMAGE_RGB565_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint16_t *buf_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)); + + r_acc = g_acc = b_acc = 0; + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + if (!mask && x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j= -ksize; j<=ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y+j); + // subtract last left-most pixel from the sums + pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, x-ksize-1); + r_acc -= COLOR_RGB565_TO_R5(pixel); + g_acc -= COLOR_RGB565_TO_G6(pixel); + b_acc -= COLOR_RGB565_TO_B5(pixel); + // add new right edge pixel to the sums + pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, x+ksize); + r_acc += COLOR_RGB565_TO_R5(pixel); + g_acc += COLOR_RGB565_TO_G6(pixel); + b_acc += COLOR_RGB565_TO_B5(pixel); + } + } else { // check bounds and do full sum calculations + r_acc = g_acc = b_acc = 0; + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + r_acc += COLOR_RGB565_TO_R5(pixel); + g_acc += COLOR_RGB565_TO_G6(pixel); + b_acc += COLOR_RGB565_TO_B5(pixel); + } + } + } + int pixel; + r = (int)((r_acc * over32_n)>>16); + g = (int)((g_acc * over32_n)>>16); + b = (int)((b_acc * over32_n)>>16); + pixel = COLOR_R5_G6_B5_TO_RGB565(r, g, b); + + if (threshold) { + if (((COLOR_RGB565_TO_Y(pixel) - offset) < COLOR_RGB565_TO_Y(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x))) ^ invert) { + pixel = COLOR_RGB565_BINARY_MAX; + } else { + pixel = COLOR_RGB565_BINARY_MIN; + } + } + + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + case PIXFORMAT_RGB888: { + int pixel, r, g, b, r_acc, g_acc, b_acc; + buf.data = fb_alloc(IMAGE_RGB888_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel24_t *buf_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)); + + r_acc = g_acc = b_acc = 0; + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + if (!mask && x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j= -ksize; j<=ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y+j); + // subtract last left-most pixel from the sums + pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, x-ksize-1); + r_acc -= COLOR_RGB888_TO_R8(pixel); + g_acc -= COLOR_RGB888_TO_G8(pixel); + b_acc -= COLOR_RGB888_TO_B8(pixel); + // add new right edge pixel to the sums + pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, x+ksize); + r_acc += COLOR_RGB888_TO_R8(pixel); + g_acc += COLOR_RGB888_TO_G8(pixel); + b_acc += COLOR_RGB888_TO_B8(pixel); + } + } else { // check bounds and do full sum calculations + r_acc = g_acc = b_acc = 0; + for (int j = -ksize; j <= ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + r_acc += COLOR_RGB888_TO_R8(pixel); + g_acc += COLOR_RGB888_TO_G8(pixel); + b_acc += COLOR_RGB888_TO_B8(pixel); + } + } + } + int pixel; + r = (int)((r_acc * over32_n)>>16); + g = (int)((g_acc * over32_n)>>16); + b = (int)((b_acc * over32_n)>>16); + pixel = COLOR_R8_G8_B8_TO_RGB888(r, g, b); + + if (threshold) { + if (((COLOR_RGB888_TO_Y(pixel) - offset) < COLOR_RGB888_TO_Y(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x))) ^ invert) { + pixel = COLOR_RGB888_BINARY_MAX; + } else { + pixel = COLOR_RGB888_BINARY_MIN; + } + } + + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + default: { + break; + } + } +} +#endif // IMLIB_ENABLE_MEAN + +#ifdef IMLIB_ENABLE_MEDIAN +static uint8_t hist_median(uint8_t *data, int len, const int cutoff) +{ +int i; +#if defined(ARM_MATH_CM7) || defined(ARM_MATH_CM4) +uint32_t oldsum=0, sum32 = 0; + + for (i=0; i= cutoff) { // within this group + while (oldsum < cutoff && i < len) + oldsum += data[i++]; + break; + } // if we're at the last 4 values + oldsum = sum32; + } // for each group of 4 elements +#else // generic C version +int sum = 0; + for (i=0; iw; + buf.h = brows; + buf.pixfmt = img->pixfmt; + + const int n = ((ksize*2)+1)*((ksize*2)+1); + const int median_cutoff = fast_floorf(percentile * (float)n); + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + int sum = 0; + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + if (!mask && x > ksize && x < img->w-ksize && y >= ksize && y h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y+j); + sum -= IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, x-ksize-1); + sum += IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, x+ksize); + } + } else { + sum = 0; + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + sum += IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + } + } + } + int pixel = (sum >= median_cutoff); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_BINARY_MAX; + } else { + pixel = COLOR_BINARY_MIN; + } + } + + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + case PIXFORMAT_GRAYSCALE: { + buf.data = fb_alloc(IMAGE_GRAYSCALE_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + uint8_t *data = fb_alloc(64, FB_ALLOC_NO_HINT); + uint8_t pixel; + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint8_t *buf_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + if (!mask && x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + // update histogram edges + for (int j=-ksize; j<= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y+j); + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, x-ksize-1); + data[pixel >> 2]--; // remove old pixels + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, x+ksize); + data[pixel >> 2]++; // add new pixels + } // for j + } else { // slow way + memset(data, 0, 64); + for (int j = -ksize; j <= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + data[pixel >> 2]++; + } + } + } + + pixel = hist_median(data, 64, median_cutoff); // find the median + pixel <<= 2; // scale it back up + if (threshold) { + if (((pixel - offset) < IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_GRAYSCALE_BINARY_MAX; + } else { + pixel = COLOR_GRAYSCALE_BINARY_MIN; + } + } + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + + fb_free(data); + fb_free(buf.data); + break; + } + case PIXFORMAT_RGB565: { + buf.data = fb_alloc(IMAGE_RGB565_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + uint8_t *r_data = fb_alloc(32, FB_ALLOC_NO_HINT); + uint8_t *g_data = fb_alloc(64, FB_ALLOC_NO_HINT); + uint8_t *b_data = fb_alloc(32, FB_ALLOC_NO_HINT); + uint8_t r, g, b; + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint16_t *buf_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + + if (!mask && x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y+j); + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, x-ksize-1); + r_data[COLOR_RGB565_TO_R5(pixel)]--; // remove left pixel + g_data[COLOR_RGB565_TO_G6(pixel)]--; + b_data[COLOR_RGB565_TO_B5(pixel)]--; + pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, x+ksize); + r_data[COLOR_RGB565_TO_R5(pixel)]++; // add right pixel + g_data[COLOR_RGB565_TO_G6(pixel)]++; + b_data[COLOR_RGB565_TO_B5(pixel)]++; + } + } else { // need to check bounds + memset(r_data, 0, 32); memset(g_data, 0, 64); memset(b_data, 0, 32); + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + r_data[COLOR_RGB565_TO_R5(pixel)]++; + g_data[COLOR_RGB565_TO_G6(pixel)]++; + b_data[COLOR_RGB565_TO_B5(pixel)]++; + } + } + } + + r = hist_median(r_data, 32, median_cutoff); + g = hist_median(g_data, 64, median_cutoff); + b = hist_median(b_data, 32, median_cutoff); + + int pixel = COLOR_R5_G6_B5_TO_RGB565(r, g, b); + + if (threshold) { + if (((COLOR_RGB565_TO_Y(pixel) - offset) < COLOR_RGB565_TO_Y(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x))) ^ invert) { + pixel = COLOR_RGB565_BINARY_MAX; + } else { + pixel = COLOR_RGB565_BINARY_MIN; + } + } + + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + fb_free(b_data); + fb_free(g_data); + fb_free(r_data); + break; + } + case PIXFORMAT_RGB888: { + buf.data = fb_alloc(IMAGE_RGB888_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + uint8_t *r_data = fb_alloc(256, FB_ALLOC_NO_HINT); + uint8_t *g_data = fb_alloc(256, FB_ALLOC_NO_HINT); + uint8_t *b_data = fb_alloc(256, FB_ALLOC_NO_HINT); + uint8_t r, g, b; + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel24_t *buf_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + + if (!mask && x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y+j); + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, x-ksize-1); + r_data[COLOR_RGB888_TO_R8(pixel)]--; // remove left pixel + g_data[COLOR_RGB888_TO_G8(pixel)]--; + b_data[COLOR_RGB888_TO_B8(pixel)]--; + pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, x+ksize); + r_data[COLOR_RGB888_TO_R8(pixel)]++; // add right pixel + g_data[COLOR_RGB888_TO_G8(pixel)]++; + b_data[COLOR_RGB888_TO_B8(pixel)]++; + } + } else { // need to check bounds + memset(r_data, 0, 32); memset(g_data, 0, 64); memset(b_data, 0, 32); + for (int j = -ksize; j <= ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + r_data[COLOR_RGB888_TO_R8(pixel)]++; + g_data[COLOR_RGB888_TO_G8(pixel)]++; + b_data[COLOR_RGB888_TO_B8(pixel)]++; + } + } + } + + r = hist_median(r_data, 256, median_cutoff); + g = hist_median(g_data, 256, median_cutoff); + b = hist_median(b_data, 256, median_cutoff); + + int pixel = COLOR_R8_G8_B8_TO_RGB888(r, g, b); + + if (threshold) { + if (((COLOR_RGB888_TO_Y(pixel) - offset) < COLOR_RGB888_TO_Y(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x))) ^ invert) { + pixel = COLOR_RGB888_BINARY_MAX; + } else { + pixel = COLOR_RGB888_BINARY_MIN; + } + } + + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + fb_free(b_data); + fb_free(g_data); + fb_free(r_data); + break; + } + default: { + break; + } + } +} +#endif // IMLIB_ENABLE_MEDIAN + +#ifdef IMLIB_ENABLE_MODE +static uint8_t find_mode(uint8_t *bins, int len) +{ +int i, j; +uint8_t mode = 0, mcount = 0; + for(i=0; i mcount) { + mcount = bins[j]; + mode = j; + } + } + } + return mode; +} /* find_mode() */ + +void imlib_mode_filter(image_t *img, const int ksize, bool threshold, int offset, bool invert, image_t *mask) +{ + int brows = ksize + 1; + image_t buf; + buf.w = img->w; + buf.h = brows; + buf.pixfmt = img->pixfmt; + const uint8_t n2 = (((ksize*2)+1)*((ksize*2)+1))/2; + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + int bins = 0; + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + if (!mask && x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img,y+j); + bins -= IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr,x-ksize-1); + bins += IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr,x+ksize); + } + } else { + bins = 0; + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + for (int k = -ksize; k <= ksize; k++) { + bins += IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + } + } + } + + uint8_t pixel = (bins > n2); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_BINARY_MAX; + } else { + pixel = COLOR_BINARY_MIN; + } + } + + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + // fb_free(NULL); + fb_free(buf.data); + break; + } + case PIXFORMAT_GRAYSCALE: { + buf.data = fb_alloc(IMAGE_GRAYSCALE_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + uint8_t *bins = fb_alloc((COLOR_GRAYSCALE_MAX-COLOR_GRAYSCALE_MIN+1), FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint8_t *buf_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)); + uint8_t pixel = 0, mode = 0; + int mcount = -1; + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + if (!mask && x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint8_t m, *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img,y+j); + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr,x-ksize-1); + m = --bins[pixel]; + if (pixel == mode) { + if (m < n2) + mcount = 256; // need to search later + else + mcount = m; // we're still the mode + } + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr,x+ksize); + m = ++bins[pixel]; + if (m > mcount) { + mcount = m; + mode = pixel; + } + } + if (mcount == 256) { // need to find max + mode = find_mode(bins, 256); + mcount = bins[mode]; + } + } else { // slow way + mcount = -1; + memset(bins, 0, (COLOR_GRAYSCALE_MAX-COLOR_GRAYSCALE_MIN+1)); + for (int j = -ksize; j <= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + bins[pixel]++; + + if (bins[pixel] > mcount) { + mcount = bins[pixel]; + mode = pixel; + } + } + } + } + pixel = mode; + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_GRAYSCALE_BINARY_MAX; + } else { + pixel = COLOR_GRAYSCALE_BINARY_MIN; + } + } + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + + fb_free(bins); + fb_free(buf.data); + break; + } + case PIXFORMAT_RGB565: { + buf.data = fb_alloc(IMAGE_RGB565_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + uint8_t *r_bins = fb_alloc((COLOR_R5_MAX-COLOR_R5_MIN+1), FB_ALLOC_NO_HINT); + uint8_t *g_bins = fb_alloc((COLOR_G6_MAX-COLOR_G6_MIN+1), FB_ALLOC_NO_HINT); + uint8_t *b_bins = fb_alloc((COLOR_B5_MAX-COLOR_B5_MIN+1), FB_ALLOC_NO_HINT); + int r_pixel, g_pixel, b_pixel; + + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint16_t *buf_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)); + int r_mcount=0, g_mcount=0, b_mcount=0; + int pixel, r_mode, g_mode, b_mode; + r_mode = g_mode = b_mode = 0; + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + if (!mask && x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y + j); + pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, x - ksize -1); + r_pixel = COLOR_RGB565_TO_R5(pixel); + g_pixel = COLOR_RGB565_TO_G6(pixel); + b_pixel = COLOR_RGB565_TO_B5(pixel); + r_bins[r_pixel]--; + g_bins[g_pixel]--; + b_bins[b_pixel]--; + if (r_pixel == r_mode) { + if (r_bins[r_pixel] < n2) + r_mcount = 256; // need to search later + else + r_mcount = r_bins[r_pixel]; // we're still the mode + } + if (g_pixel == g_mode) { + if (g_bins[g_pixel] < n2) + g_mcount = 256; // need to search later + else + g_mcount = g_bins[g_pixel]; // we're still the mode + } + if (b_pixel == b_mode) { + if (b_bins[b_pixel] < n2) + b_mcount = 256; // need to search later + else + b_mcount = b_bins[b_pixel]; // we're still the mode + } + + pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, x + ksize); + r_pixel = COLOR_RGB565_TO_R5(pixel); + g_pixel = COLOR_RGB565_TO_G6(pixel); + b_pixel = COLOR_RGB565_TO_B5(pixel); + r_bins[r_pixel]++; + g_bins[g_pixel]++; + b_bins[b_pixel]++; + if (r_bins[r_pixel] > r_mcount) { + r_mcount = r_bins[r_pixel]; + r_mode = r_pixel; + } + + if (g_bins[g_pixel] > g_mcount) { + g_mcount = g_bins[g_pixel]; + g_mode = g_pixel; + } + + if (b_bins[b_pixel] > b_mcount) { + b_mcount = b_bins[b_pixel]; + b_mode = b_pixel; + } + } // for j + if (r_mcount == 256) { // need to find max + r_mode = find_mode(r_bins, 32); + r_mcount = r_bins[r_mode]; + } + if (g_mcount == 256) { // need to find max + g_mode = find_mode(g_bins, 64); + g_mcount = g_bins[g_mode]; + } + if (b_mcount == 256) { // need to find max + b_mode = find_mode(b_bins, 32); + b_mcount = r_bins[b_mode]; + } + } else { // slower way + memset(r_bins, 0, (COLOR_R5_MAX-COLOR_R5_MIN+1)); + memset(g_bins, 0, (COLOR_G6_MAX-COLOR_G6_MIN+1)); + memset(b_bins, 0, (COLOR_B5_MAX-COLOR_B5_MIN+1)); + r_mcount = g_mcount = b_mcount = 0; + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + r_pixel = COLOR_RGB565_TO_R5(pixel); + g_pixel = COLOR_RGB565_TO_G6(pixel); + b_pixel = COLOR_RGB565_TO_B5(pixel); + r_bins[r_pixel]++; + g_bins[g_pixel]++; + b_bins[b_pixel]++; + + if (r_bins[r_pixel] > r_mcount) { + r_mcount = r_bins[r_pixel]; + r_mode = r_pixel; + } + + if (g_bins[g_pixel] > g_mcount) { + g_mcount = g_bins[g_pixel]; + g_mode = g_pixel; + } + + if (b_bins[b_pixel] > b_mcount) { + b_mcount = b_bins[b_pixel]; + b_mode = b_pixel; + } + } // for k + } // for j + } // slow/fast way + pixel = COLOR_R5_G6_B5_TO_RGB565(r_mode, g_mode, b_mode); + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + + fb_free(b_bins); + fb_free(g_bins); + fb_free(r_bins); + fb_free(buf.data); + break; + } + case PIXFORMAT_RGB888: { + buf.data = fb_alloc(IMAGE_RGB888_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + uint8_t *r_bins = fb_alloc((COLOR_R8_MAX-COLOR_R8_MIN+1), FB_ALLOC_NO_HINT); + uint8_t *g_bins = fb_alloc((COLOR_G8_MAX-COLOR_G8_MIN+1), FB_ALLOC_NO_HINT); + uint8_t *b_bins = fb_alloc((COLOR_B8_MAX-COLOR_B8_MIN+1), FB_ALLOC_NO_HINT); + int r_pixel, g_pixel, b_pixel; + + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel24_t *buf_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)); + int r_mcount=0, g_mcount=0, b_mcount=0; + int pixel, r_mode, g_mode, b_mode; + r_mode = g_mode = b_mode = 0; + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + if (!mask && x > ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y + j); + pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, x - ksize -1); + r_pixel = COLOR_RGB888_TO_R8(pixel); + g_pixel = COLOR_RGB888_TO_G8(pixel); + b_pixel = COLOR_RGB888_TO_B8(pixel); + r_bins[r_pixel]--; + g_bins[g_pixel]--; + b_bins[b_pixel]--; + if (r_pixel == r_mode) { + if (r_bins[r_pixel] < n2) + r_mcount = 256; // need to search later + else + r_mcount = r_bins[r_pixel]; // we're still the mode + } + if (g_pixel == g_mode) { + if (g_bins[g_pixel] < n2) + g_mcount = 256; // need to search later + else + g_mcount = g_bins[g_pixel]; // we're still the mode + } + if (b_pixel == b_mode) { + if (b_bins[b_pixel] < n2) + b_mcount = 256; // need to search later + else + b_mcount = b_bins[b_pixel]; // we're still the mode + } + + pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, x + ksize); + r_pixel = COLOR_RGB888_TO_R8(pixel); + g_pixel = COLOR_RGB888_TO_G8(pixel); + b_pixel = COLOR_RGB888_TO_B8(pixel); + r_bins[r_pixel]++; + g_bins[g_pixel]++; + b_bins[b_pixel]++; + if (r_bins[r_pixel] > r_mcount) { + r_mcount = r_bins[r_pixel]; + r_mode = r_pixel; + } + + if (g_bins[g_pixel] > g_mcount) { + g_mcount = g_bins[g_pixel]; + g_mode = g_pixel; + } + + if (b_bins[b_pixel] > b_mcount) { + b_mcount = b_bins[b_pixel]; + b_mode = b_pixel; + } + } // for j + if (r_mcount == 256) { // need to find max + r_mode = find_mode(r_bins, 256); + r_mcount = r_bins[r_mode]; + } + if (g_mcount == 256) { // need to find max + g_mode = find_mode(g_bins, 256); + g_mcount = g_bins[g_mode]; + } + if (b_mcount == 256) { // need to find max + b_mode = find_mode(b_bins, 256); + b_mcount = r_bins[b_mode]; + } + } else { // slower way + memset(r_bins, 0, (COLOR_R5_MAX-COLOR_R8_MIN+1)); + memset(g_bins, 0, (COLOR_G6_MAX-COLOR_G8_MIN+1)); + memset(b_bins, 0, (COLOR_B5_MAX-COLOR_B8_MIN+1)); + r_mcount = g_mcount = b_mcount = 0; + for (int j = -ksize; j <= ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + r_pixel = COLOR_RGB888_TO_R8(pixel); + g_pixel = COLOR_RGB888_TO_G8(pixel); + b_pixel = COLOR_RGB888_TO_B8(pixel); + r_bins[r_pixel]++; + g_bins[g_pixel]++; + b_bins[b_pixel]++; + + if (r_bins[r_pixel] > r_mcount) { + r_mcount = r_bins[r_pixel]; + r_mode = r_pixel; + } + + if (g_bins[g_pixel] > g_mcount) { + g_mcount = g_bins[g_pixel]; + g_mode = g_pixel; + } + + if (b_bins[b_pixel] > b_mcount) { + b_mcount = b_bins[b_pixel]; + b_mode = b_pixel; + } + } // for k + } // for j + } // slow/fast way + pixel = COLOR_R8_G8_B8_TO_RGB888(r_mode, g_mode, b_mode); + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + + fb_free(b_bins); + fb_free(g_bins); + fb_free(r_bins); + fb_free(buf.data); + break; + } + default: { + break; + } + } +} +#endif // IMLIB_ENABLE_MODE + +#ifdef IMLIB_ENABLE_MIDPOINT +void imlib_midpoint_filter(image_t *img, const int ksize, float bias, bool threshold, int offset, bool invert, image_t *mask) +{ + int brows = ksize + 1; + image_t buf; + buf.w = img->w; + buf.h = brows; + buf.pixfmt = img->pixfmt; + uint8_t *u8BiasTable; + float max_bias = bias, min_bias = 1.0f - bias; + + u8BiasTable = fb_alloc(256, FB_ALLOC_NO_HINT); + for (int i=0; i<256; i++) { + u8BiasTable[i] = (uint8_t)fast_floorf((float)i * bias); + } + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int min = COLOR_BINARY_MAX, max = COLOR_BINARY_MIN; + + if (x >= ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img,y+j); + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr,x+k); + min &= pixel; + max |= pixel; + } + } + } else { + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + min &= pixel; + max |= pixel; + } + } + } + + int pixel = fast_floorf((min*min_bias)+(max*max_bias)); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_BINARY_MAX; + } else { + pixel = COLOR_BINARY_MIN; + } + } + + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + case PIXFORMAT_GRAYSCALE: { + buf.data = fb_alloc(IMAGE_GRAYSCALE_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint8_t *buf_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int min = COLOR_GRAYSCALE_MAX, max = COLOR_GRAYSCALE_MIN; + if (x >= ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img,y+j); + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr,x+k); + if (pixel < min) min = pixel; + else if (pixel > max) max = pixel; + } + } + } else { + for (int j = -ksize; j <= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + if (pixel < min) min = pixel; + else if (pixel > max) max = pixel; + } + } + } + + int pixel = min + u8BiasTable[max-min]; + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_GRAYSCALE_BINARY_MAX; + } else { + pixel = COLOR_GRAYSCALE_BINARY_MIN; + } + } + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + case PIXFORMAT_RGB565: { + buf.data = fb_alloc(IMAGE_RGB565_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint16_t *buf_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int r_min = COLOR_R5_MAX, r_max = COLOR_R5_MIN; + int g_min = COLOR_G6_MAX, g_max = COLOR_G6_MIN; + int b_min = COLOR_B5_MAX, b_max = COLOR_B5_MIN; + if (x >= ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img,y+j); + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr,x+k); + int r_pixel = COLOR_RGB565_TO_R5(pixel); + int g_pixel = COLOR_RGB565_TO_G6(pixel); + int b_pixel = COLOR_RGB565_TO_B5(pixel); + if (r_pixel < r_min) r_min = r_pixel; + else if (r_pixel > r_max) r_max = r_pixel; + if (g_pixel < g_min) g_min = g_pixel; + else if (g_pixel > g_max) g_max = g_pixel; + if (b_pixel < b_min) b_min = b_pixel; + else if (b_pixel > b_max) b_max = b_pixel; + } + } + } else { + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + int r_pixel = COLOR_RGB565_TO_R5(pixel); + int g_pixel = COLOR_RGB565_TO_G6(pixel); + int b_pixel = COLOR_RGB565_TO_B5(pixel); + if (r_pixel < r_min) r_min = r_pixel; + else if (r_pixel > r_max) r_max = r_pixel; + if (g_pixel < g_min) g_min = g_pixel; + else if (g_pixel > g_max) g_max = g_pixel; + if (b_pixel < b_min) b_min = b_pixel; + else if (b_pixel > b_max) b_max = b_pixel; + } + } + } + + r_min += u8BiasTable[r_max-r_min]; + g_min += u8BiasTable[g_max-g_min]; + b_min += u8BiasTable[b_max-b_min]; + int pixel = COLOR_R5_G6_B5_TO_RGB565(r_min, g_min, b_min); + + if (threshold) { + if (((COLOR_RGB565_TO_Y(pixel) - offset) < COLOR_RGB565_TO_Y(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x))) ^ invert) { + pixel = COLOR_RGB565_BINARY_MAX; + } else { + pixel = COLOR_RGB565_BINARY_MIN; + } + } + + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + case PIXFORMAT_RGB888: { + buf.data = fb_alloc(IMAGE_RGB888_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel24_t *buf_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int r_min = COLOR_R5_MAX, r_max = COLOR_R5_MIN; + int g_min = COLOR_G6_MAX, g_max = COLOR_G6_MIN; + int b_min = COLOR_B5_MAX, b_max = COLOR_B5_MIN; + if (x >= ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img,y+j); + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr,x+k); + int r_pixel = COLOR_RGB888_TO_R8(pixel); + int g_pixel = COLOR_RGB888_TO_G8(pixel); + int b_pixel = COLOR_RGB888_TO_B8(pixel); + if (r_pixel < r_min) r_min = r_pixel; + else if (r_pixel > r_max) r_max = r_pixel; + if (g_pixel < g_min) g_min = g_pixel; + else if (g_pixel > g_max) g_max = g_pixel; + if (b_pixel < b_min) b_min = b_pixel; + else if (b_pixel > b_max) b_max = b_pixel; + } + } + } else { + for (int j = -ksize; j <= ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + int r_pixel = COLOR_RGB888_TO_R8(pixel); + int g_pixel = COLOR_RGB888_TO_G8(pixel); + int b_pixel = COLOR_RGB888_TO_B8(pixel); + if (r_pixel < r_min) r_min = r_pixel; + else if (r_pixel > r_max) r_max = r_pixel; + if (g_pixel < g_min) g_min = g_pixel; + else if (g_pixel > g_max) g_max = g_pixel; + if (b_pixel < b_min) b_min = b_pixel; + else if (b_pixel > b_max) b_max = b_pixel; + } + } + } + + r_min += u8BiasTable[r_max-r_min]; + g_min += u8BiasTable[g_max-g_min]; + b_min += u8BiasTable[b_max-b_min]; + int pixel = COLOR_R8_G8_B8_TO_RGB888(r_min, g_min, b_min); + + if (threshold) { + if (((COLOR_RGB888_TO_Y(pixel) - offset) < COLOR_RGB888_TO_Y(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x))) ^ invert) { + pixel = COLOR_RGB888_BINARY_MAX; + } else { + pixel = COLOR_RGB888_BINARY_MIN; + } + } + + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + + xfree(buf.data); + break; + } + default: { + break; + } + } + fb_free(u8BiasTable); +} +#endif // IMLIB_ENABLE_MIDPOINT + +// http://www.fmwconcepts.com/imagemagick/digital_image_filtering.pdf + +void imlib_morph(image_t *img, const int ksize, const int *krn, const float m, const int b, bool threshold, int offset, bool invert, image_t *mask) +{ + int brows = ksize + 1; + image_t buf; + buf.w = img->w; + buf.h = brows; + buf.pixfmt = img->pixfmt; + const int32_t m_int = (int32_t)(65536.0 * m); // m is 1/kernel_weight + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int acc = 0, ptr = 0; + + if (x >= ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img,y+j); + for (int k = -ksize; k <= ksize; k++) { + acc += krn[ptr++] * IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr,x+k); + } + } + } else { + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + acc += krn[ptr++] * IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + } + } + } + int32_t tmp = (acc * m_int) >> 16; + int pixel = tmp + b; + if (pixel < 0) pixel = 0; + else if (pixel > 1) pixel = 1; + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_BINARY_MAX; + } else { + pixel = COLOR_BINARY_MIN; + } + } + + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + case PIXFORMAT_GRAYSCALE: { + buf.data = fb_alloc(IMAGE_GRAYSCALE_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint8_t *buf_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int32_t acc = 0, ptr = 0; + + if (x >= ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img,y+j); + for (int k = -ksize; k <= ksize; k++) { + acc += krn[ptr++] * IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr,x+k); + } + } + } else { + for (int j = -ksize; j <= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + acc += krn[ptr++] * IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + } + } + } + int32_t tmp = (acc * m_int)>>16; + int pixel = tmp + b; + if (pixel > COLOR_GRAYSCALE_MAX) pixel = COLOR_GRAYSCALE_MAX; + else if (pixel < 0) pixel = 0; + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_GRAYSCALE_BINARY_MAX; + } else { + pixel = COLOR_GRAYSCALE_BINARY_MIN; + } + } + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + case PIXFORMAT_RGB565: { + buf.data = fb_alloc(IMAGE_RGB565_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint16_t *buf_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int32_t tmp, r_acc = 0, g_acc = 0, b_acc = 0, ptr = 0; + + if (x >= ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img,y+j); + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr,x+k); + r_acc += krn[ptr] * COLOR_RGB565_TO_R5(pixel); + g_acc += krn[ptr] * COLOR_RGB565_TO_G6(pixel); + b_acc += krn[ptr++] * COLOR_RGB565_TO_B5(pixel); + } + } + } else { + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + r_acc += krn[ptr] * COLOR_RGB565_TO_R5(pixel); + g_acc += krn[ptr] * COLOR_RGB565_TO_G6(pixel); + b_acc += krn[ptr++] * COLOR_RGB565_TO_B5(pixel); + } + } + } + tmp = (r_acc * m_int) >> 16; + r_acc = tmp + b; + if (r_acc > COLOR_R5_MAX) r_acc = COLOR_R5_MAX; + else if (r_acc < 0) r_acc = 0; + tmp = (g_acc * m_int) >> 16; + g_acc = tmp + b; + if (g_acc > COLOR_G6_MAX) g_acc = COLOR_G6_MAX; + else if (g_acc < 0) g_acc = 0; + tmp = (b_acc * m_int) >> 16; + b_acc = tmp + b; + if (b_acc > COLOR_B5_MAX) b_acc = COLOR_B5_MAX; + else if (b_acc < 0) b_acc = 0; + + int pixel = COLOR_R5_G6_B5_TO_RGB565(r_acc, g_acc, b_acc); + + if (threshold) { + if (((COLOR_RGB565_TO_Y(pixel) - offset) < COLOR_RGB565_TO_Y(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x))) ^ invert) { + pixel = COLOR_RGB565_BINARY_MAX; + } else { + pixel = COLOR_RGB565_BINARY_MIN; + } + } + + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + case PIXFORMAT_RGB888: { + buf.data = fb_alloc(IMAGE_RGB888_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel24_t *buf_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int32_t tmp, r_acc = 0, g_acc = 0, b_acc = 0, ptr = 0; + + if (x >= ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img,y+j); + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr,x+k); + r_acc += krn[ptr] * COLOR_RGB888_TO_R8(pixel); + g_acc += krn[ptr] * COLOR_RGB888_TO_G8(pixel); + b_acc += krn[ptr++] * COLOR_RGB888_TO_B8(pixel); + } + } + } else { + for (int j = -ksize; j <= ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + r_acc += krn[ptr] * COLOR_RGB888_TO_R8(pixel); + g_acc += krn[ptr] * COLOR_RGB888_TO_G8(pixel); + b_acc += krn[ptr++] * COLOR_RGB888_TO_B8(pixel); + } + } + } + tmp = (r_acc * m_int) >> 16; + r_acc = tmp + b; + if (r_acc > COLOR_R8_MAX) r_acc = COLOR_R8_MAX; + else if (r_acc < 0) r_acc = 0; + tmp = (g_acc * m_int) >> 16; + g_acc = tmp + b; + if (g_acc > COLOR_G8_MAX) g_acc = COLOR_G8_MAX; + else if (g_acc < 0) g_acc = 0; + tmp = (b_acc * m_int) >> 16; + b_acc = tmp + b; + if (b_acc > COLOR_B8_MAX) b_acc = COLOR_B8_MAX; + else if (b_acc < 0) b_acc = 0; + + int pixel = COLOR_R8_G8_B8_TO_RGB888(r_acc, g_acc, b_acc); + + if (threshold) { + if (((COLOR_RGB888_TO_Y(pixel) - offset) < COLOR_RGB888_TO_Y(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x))) ^ invert) { + pixel = COLOR_RGB888_BINARY_MAX; + } else { + pixel = COLOR_RGB888_BINARY_MIN; + } + } + + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + + fb_free(buf.data); + break; + } + default: { + break; + } + } +} + +#ifdef IMLIB_ENABLE_BILATERAL +static float gaussian(float x, float sigma) +{ + return fast_expf((x * x) / (-2.0f * sigma * sigma)) / (fabsf(sigma) * 2.506628f); // sqrt(2 * PI) +} + +static float distance(int x, int y) +{ + return fast_sqrtf((x * x) + (y * y)); +} + +void imlib_bilateral_filter(image_t *img, const int ksize, float color_sigma, float space_sigma, bool threshold, int offset, bool invert, image_t *mask) +{ + int brows = ksize + 1; + image_t buf; + buf.w = img->w; + buf.h = brows; + buf.pixfmt = img->pixfmt; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + buf.data = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + float *gi_lut_ptr = fb_alloc((COLOR_BINARY_MAX - COLOR_BINARY_MIN + 1) * sizeof(float) *2, FB_ALLOC_NO_HINT); + float *gi_lut = &gi_lut_ptr[1]; + float max_color = IM_DIV(1.0f, COLOR_BINARY_MAX - COLOR_BINARY_MIN); + for (int i = COLOR_BINARY_MIN; i <= COLOR_BINARY_MAX; i++) { + gi_lut[-i] = gi_lut[i] = gaussian(i * max_color, color_sigma); + } + + int n = (ksize * 2) + 1; + float *gs_lut = fb_alloc(n * n * sizeof(float), FB_ALLOC_NO_HINT); + + float max_space = IM_DIV(1.0f, distance(ksize, ksize)); + for (int y = -ksize; y <= ksize; y++) { + for (int x = -ksize; x <= ksize; x++) { + gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y) * max_space, space_sigma); + } + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *buf_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int this_pixel = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x); + float i_acc = 0, w_acc = 0; + int ptr = 0; + for (int j = -ksize; j <= ksize; j++) { + uint32_t *k_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + float w = gi_lut[(this_pixel - pixel)] * gs_lut[ptr++]; + i_acc += pixel * w; + w_acc += w; + } + } + + int pixel = fast_floorf(IM_MIN(IM_DIV(i_acc, w_acc), COLOR_BINARY_MAX)); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_BINARY_MAX; + } else { + pixel = COLOR_BINARY_MIN; + } + } + + IMAGE_PUT_BINARY_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_BINARY_LINE_LEN_BYTES(img)); + } + + fb_free(gs_lut); + fb_free(gi_lut_ptr); + fb_free(buf.data); + break; + } + case PIXFORMAT_GRAYSCALE: { + buf.data = fb_alloc(IMAGE_GRAYSCALE_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + float *gi_lut_ptr = fb_alloc((COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1) * sizeof(float) * 2, FB_ALLOC_NO_HINT); + float *gi_lut = &gi_lut_ptr[256]; // point to the middle + float max_color = IM_DIV(1.0f, COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN); + for (int i = COLOR_GRAYSCALE_MIN; i <= COLOR_GRAYSCALE_MAX; i++) { + gi_lut[-i] = gi_lut[i] = gaussian(i * max_color, color_sigma); + } + + int n = (ksize * 2) + 1; + float *gs_lut = fb_alloc(n * n * sizeof(float), FB_ALLOC_NO_HINT); + + float max_space = IM_DIV(1.0f, distance(ksize, ksize)); + for (int y = -ksize; y <= ksize; y++) { + for (int x = -ksize; x <= ksize; x++) { + gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y) * max_space, space_sigma); + } + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint8_t *buf_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int this_pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + float i_acc = 0, w_acc = 0; + int ptr = 0; + if (x >= ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y+j); + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, x+k); + float w = gi_lut[this_pixel - pixel] * gs_lut[ptr++]; + i_acc += pixel * w; + w_acc += w; + } + } + } else { + for (int j = -ksize; j <= ksize; j++) { + uint8_t *k_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + float w = gi_lut[(this_pixel - pixel)] * gs_lut[ptr++]; + i_acc += pixel * w; + w_acc += w; + } + } + } + + int pixel = fast_floorf(IM_MIN(IM_DIV(i_acc, w_acc), COLOR_GRAYSCALE_MAX)); + + if (threshold) { + if (((pixel - offset) < IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)) ^ invert) { + pixel = COLOR_GRAYSCALE_BINARY_MAX; + } else { + pixel = COLOR_GRAYSCALE_BINARY_MIN; + } + } + + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_GRAYSCALE_LINE_LEN_BYTES(img)); + } + + fb_free(gs_lut); + fb_free(gi_lut_ptr); + fb_free(buf.data); + break; + } + case PIXFORMAT_RGB565: { + buf.data = fb_alloc(IMAGE_RGB565_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + float *rb_gi_ptr = fb_alloc((COLOR_R5_MAX - COLOR_R5_MIN + 1) * sizeof(float) *2, FB_ALLOC_NO_HINT); + float *g_gi_ptr = fb_alloc((COLOR_G6_MAX - COLOR_G6_MIN + 1) * sizeof(float) *2, FB_ALLOC_NO_HINT); + float *rb_gi_lut = &rb_gi_ptr[32]; // center + float *g_gi_lut = &g_gi_ptr[64]; + + float r_max_color = IM_DIV(1.0f, COLOR_R5_MAX - COLOR_R5_MIN); + for (int i = COLOR_R5_MIN; i <= COLOR_R5_MAX; i++) { + rb_gi_lut[-i] = rb_gi_lut[i] = gaussian(i * r_max_color, color_sigma); + } + + float g_max_color = IM_DIV(1.0f, COLOR_G6_MAX - COLOR_G6_MIN); + for (int i = COLOR_G6_MIN; i <= COLOR_G6_MAX; i++) { + g_gi_lut[-i] = g_gi_lut[i] = gaussian(i * g_max_color, color_sigma); + } + + int n = (ksize * 2) + 1; + float *gs_lut = fb_alloc(n * n * sizeof(float), FB_ALLOC_NO_HINT); + + float max_space = IM_DIV(1.0f, distance(ksize, ksize)); + for (int y = -ksize; y <= ksize; y++) { + for (int x = -ksize; x <= ksize; x++) { + gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y) * max_space, space_sigma); + } + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint16_t *buf_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int this_pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); + int r_this_pixel = COLOR_RGB565_TO_R5(this_pixel); + int g_this_pixel = COLOR_RGB565_TO_G6(this_pixel); + int b_this_pixel = COLOR_RGB565_TO_B5(this_pixel); + float r_i_acc = 0, r_w_acc = 0; + float g_i_acc = 0, g_w_acc = 0; + float b_i_acc = 0, b_w_acc = 0; + int ptr = 0; + if (x >= ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img,y+j); + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr,x+k); + int r_pixel = COLOR_RGB565_TO_R5(pixel); + int g_pixel = COLOR_RGB565_TO_G6(pixel); + int b_pixel = COLOR_RGB565_TO_B5(pixel); + float gs = gs_lut[ptr++]; + float r_w = rb_gi_lut[(r_this_pixel - r_pixel)] * gs; + float g_w = g_gi_lut[(g_this_pixel - g_pixel)] * gs; + float b_w = rb_gi_lut[(b_this_pixel - b_pixel)] * gs; + r_i_acc += r_pixel * r_w; + r_w_acc += r_w; + g_i_acc += g_pixel * g_w; + g_w_acc += g_w; + b_i_acc += b_pixel * b_w; + b_w_acc += b_w; + } + } + } else { // check boundary conditions + for (int j = -ksize; j <= ksize; j++) { + uint16_t *k_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + int r_pixel = COLOR_RGB565_TO_R5(pixel); + int g_pixel = COLOR_RGB565_TO_G6(pixel); + int b_pixel = COLOR_RGB565_TO_B5(pixel); + float gs = gs_lut[ptr++]; + float r_w = rb_gi_lut[(r_this_pixel - r_pixel)] * gs; + float g_w = g_gi_lut[(g_this_pixel - g_pixel)] * gs; + float b_w = rb_gi_lut[(b_this_pixel - b_pixel)] * gs; + r_i_acc += r_pixel * r_w; + r_w_acc += r_w; + g_i_acc += g_pixel * g_w; + g_w_acc += g_w; + b_i_acc += b_pixel * b_w; + b_w_acc += b_w; + } + } + } + + int pixel = COLOR_R5_G6_B5_TO_RGB565(fast_floorf(IM_MIN(IM_DIV(r_i_acc, r_w_acc), COLOR_R5_MAX)), + fast_floorf(IM_MIN(IM_DIV(g_i_acc, g_w_acc), COLOR_G6_MAX)), + fast_floorf(IM_MIN(IM_DIV(b_i_acc, b_w_acc), COLOR_B5_MAX))); + + if (threshold) { + if (((COLOR_RGB565_TO_Y(pixel) - offset) < COLOR_RGB565_TO_Y(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x))) ^ invert) { + pixel = COLOR_RGB565_BINARY_MAX; + } else { + pixel = COLOR_RGB565_BINARY_MIN; + } + } + + IMAGE_PUT_RGB565_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB565_LINE_LEN_BYTES(img)); + } + fb_free(gs_lut); + fb_free(g_gi_ptr); + fb_free(rb_gi_ptr); + fb_free(buf.data); + break; + } + case PIXFORMAT_RGB888: { + buf.data = fb_alloc(IMAGE_RGB888_LINE_LEN_BYTES(img) * brows, FB_ALLOC_NO_HINT); + float *r_gi_ptr = fb_alloc((COLOR_R8_MAX - COLOR_R8_MIN + 1) * sizeof(float) *3, FB_ALLOC_NO_HINT); + float *g_gi_ptr = fb_alloc((COLOR_G8_MAX - COLOR_G8_MIN + 1) * sizeof(float) *3, FB_ALLOC_NO_HINT); + float *b_gi_ptr = fb_alloc((COLOR_B8_MAX - COLOR_B8_MIN + 1) * sizeof(float) *3, FB_ALLOC_NO_HINT); + float *r_gi_lut = &r_gi_ptr[256]; // center + float *g_gi_lut = &g_gi_ptr[256]; + float *b_gi_lut = &b_gi_ptr[256]; // center + + float r_max_color = IM_DIV(1.0f, COLOR_R8_MAX - COLOR_R8_MIN); + for (int i = COLOR_R8_MIN; i <= COLOR_R8_MAX; i++) { + r_gi_lut[-i] = r_gi_lut[i] = gaussian(i * r_max_color, color_sigma); + } + + float g_max_color = IM_DIV(1.0f, COLOR_G8_MAX - COLOR_G8_MIN); + for (int i = COLOR_G8_MIN; i <= COLOR_G8_MAX; i++) { + g_gi_lut[-i] = g_gi_lut[i] = gaussian(i * g_max_color, color_sigma); + } + + float b_max_color = IM_DIV(1.0f, COLOR_B8_MAX - COLOR_B8_MIN); + for (int i = COLOR_B8_MIN; i <= COLOR_B8_MAX; i++) { + b_gi_lut[-i] = b_gi_lut[i] = gaussian(i * b_max_color, color_sigma); + } + + int n = (ksize * 2) + 1; + float *gs_lut = fb_alloc(n * n * sizeof(float), FB_ALLOC_NO_HINT); + + float max_space = IM_DIV(1.0f, distance(ksize, ksize)); + for (int y = -ksize; y <= ksize; y++) { + for (int x = -ksize; x <= ksize; x++) { + gs_lut[(n * (y + ksize)) + (x + ksize)] = gaussian(distance(x, y) * max_space, space_sigma); + } + } + + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel24_t *buf_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)); + + for (int x = 0, xx = img->w; x < xx; x++) { + if (mask && (!image_get_mask_pixel(mask, x, y))) { + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + continue; // Short circuit. + } + + int this_pixel = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + int r_this_pixel = COLOR_RGB888_TO_R8(this_pixel); + int g_this_pixel = COLOR_RGB888_TO_G8(this_pixel); + int b_this_pixel = COLOR_RGB888_TO_B8(this_pixel); + float r_i_acc = 0, r_w_acc = 0; + float g_i_acc = 0, g_w_acc = 0; + float b_i_acc = 0, b_w_acc = 0; + int ptr = 0; + if (x >= ksize && x < img->w-ksize && y >= ksize && y < img->h-ksize) { + for (int j = -ksize; j <= ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img,y+j); + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr,x+k); + int r_pixel = COLOR_RGB888_TO_R8(pixel); + int g_pixel = COLOR_RGB888_TO_G8(pixel); + int b_pixel = COLOR_RGB888_TO_B8(pixel); + float gs = gs_lut[ptr++]; + float r_w = r_gi_lut[(r_this_pixel - r_pixel)] * gs; + float g_w = g_gi_lut[(g_this_pixel - g_pixel)] * gs; + float b_w = r_gi_lut[(b_this_pixel - b_pixel)] * gs; + r_i_acc += r_pixel * r_w; + r_w_acc += r_w; + g_i_acc += g_pixel * g_w; + g_w_acc += g_w; + b_i_acc += b_pixel * b_w; + b_w_acc += b_w; + } + } + } else { // check boundary conditions + for (int j = -ksize; j <= ksize; j++) { + pixel24_t *k_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, + IM_MIN(IM_MAX(y + j, 0), (img->h - 1))); + + for (int k = -ksize; k <= ksize; k++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(k_row_ptr, + IM_MIN(IM_MAX(x + k, 0), (img->w - 1))); + int r_pixel = COLOR_RGB888_TO_R8(pixel); + int g_pixel = COLOR_RGB888_TO_G8(pixel); + int b_pixel = COLOR_RGB888_TO_B8(pixel); + float gs = gs_lut[ptr++]; + float r_w = r_gi_lut[(r_this_pixel - r_pixel)] * gs; + float g_w = g_gi_lut[(g_this_pixel - g_pixel)] * gs; + float b_w = r_gi_lut[(b_this_pixel - b_pixel)] * gs; + r_i_acc += r_pixel * r_w; + r_w_acc += r_w; + g_i_acc += g_pixel * g_w; + g_w_acc += g_w; + b_i_acc += b_pixel * b_w; + b_w_acc += b_w; + } + } + } + + int pixel = COLOR_R8_G8_B8_TO_RGB888(fast_floorf(IM_MIN(IM_DIV(r_i_acc, r_w_acc), COLOR_R5_MAX)), + fast_floorf(IM_MIN(IM_DIV(g_i_acc, g_w_acc), COLOR_G6_MAX)), + fast_floorf(IM_MIN(IM_DIV(b_i_acc, b_w_acc), COLOR_B5_MAX))); + + if (threshold) { + if (((COLOR_RGB888_TO_Y(pixel) - offset) < COLOR_RGB888_TO_Y(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x))) ^ invert) { + pixel = COLOR_RGB888_BINARY_MAX; + } else { + pixel = COLOR_RGB888_BINARY_MIN; + } + } + + IMAGE_PUT_RGB888_PIXEL_FAST(buf_row_ptr, x, pixel); + } + + if (y >= ksize) { // Transfer buffer lines... + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, (y - ksize)), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, ((y - ksize) % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + } + + // Copy any remaining lines from the buffer image... + for (int y = IM_MAX(img->h - ksize, 0), yy = img->h; y < yy; y++) { + memcpy(IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y), + IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(&buf, (y % brows)), + IMAGE_RGB888_LINE_LEN_BYTES(img)); + } + fb_free(gs_lut); + fb_free(b_gi_ptr); + fb_free(g_gi_ptr); + fb_free(r_gi_ptr); + fb_free(buf.data); + break; + } + default: { + break; + } + } +} +#endif // IMLIB_ENABLE_BILATERAL + +#ifdef IMLIB_ENABLE_CARTOON +typedef struct imlib_cartoon_filter_mean_state { + int r_acc, g_acc, b_acc, pixels; +} imlib_cartoon_filter_mean_state_t; + +static void imlib_cartoon_filter_mean(image_t *img, int line, int l, int r, void *data) +{ + imlib_cartoon_filter_mean_state_t *state = (imlib_cartoon_filter_mean_state_t *) data; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + state->g_acc += IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, i); + state->pixels += 1; + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + state->g_acc += IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, i); + state->pixels += 1; + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, i); + state->r_acc += COLOR_RGB565_TO_R5(pixel); + state->g_acc += COLOR_RGB565_TO_G6(pixel); + state->b_acc += COLOR_RGB565_TO_B5(pixel); + state->pixels += 1; + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, i); + state->r_acc += COLOR_RGB888_TO_R8(pixel); + state->g_acc += COLOR_RGB888_TO_G8(pixel); + state->b_acc += COLOR_RGB888_TO_B8(pixel); + state->pixels += 1; + } + break; + } + default: { + break; + } + } +} + +typedef struct imlib_cartoon_filter_fill_state { + int mean; +} imlib_cartoon_filter_fill_state_t; + +static void imlib_cartoon_filter_fill(image_t *img, int line, int l, int r, void *data) +{ + imlib_cartoon_filter_fill_state_t *state = (imlib_cartoon_filter_fill_state_t *) data; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, i, state->mean); + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, i, state->mean); + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, i, state->mean); + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + for (int i = l; i <= r; i++) { + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr, i, state->mean); + } + break; + } + default: { + break; + } + } +} + +void imlib_cartoon_filter(image_t *img, float seed_threshold, float floating_threshold, image_t *mask) +{ + image_t mean_image, fill_image; + + mean_image.w = img->w; + mean_image.h = img->h; + mean_image.pixfmt = PIXFORMAT_BINARY; + mean_image.pixels = fb_alloc0(image_size(&mean_image), FB_ALLOC_NO_HINT); + + fill_image.w = img->w; + fill_image.h = img->h; + fill_image.pixfmt = PIXFORMAT_BINARY; + fill_image.pixels = fb_alloc0(image_size(&fill_image), FB_ALLOC_NO_HINT); + + if (mask) { + for (int y = 0, yy = fill_image.h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&fill_image, y); + for (int x = 0, xx = fill_image.w; x < xx; x++) { + if (image_get_mask_pixel(mask, x, y)) IMAGE_SET_BINARY_PIXEL_FAST(row_ptr, x); + } + } + } + + int color_seed_threshold = 0; + int color_floating_threshold = 0; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + color_seed_threshold = fast_floorf(seed_threshold * COLOR_BINARY_MAX); + color_floating_threshold = fast_floorf(floating_threshold * COLOR_BINARY_MAX); + break; + } + case PIXFORMAT_GRAYSCALE: { + color_seed_threshold = fast_floorf(seed_threshold * COLOR_GRAYSCALE_MAX); + color_floating_threshold = fast_floorf(floating_threshold * COLOR_GRAYSCALE_MAX); + break; + } + case PIXFORMAT_RGB565: { + color_seed_threshold = COLOR_R5_G6_B5_TO_RGB565(fast_floorf(seed_threshold * COLOR_R5_MAX), + fast_floorf(seed_threshold * COLOR_G6_MAX), + fast_floorf(seed_threshold * COLOR_B5_MAX)); + color_floating_threshold = COLOR_R5_G6_B5_TO_RGB565(fast_floorf(floating_threshold * COLOR_R5_MAX), + fast_floorf(floating_threshold * COLOR_G6_MAX), + fast_floorf(floating_threshold * COLOR_B5_MAX)); + break; + } + case PIXFORMAT_RGB888: { + color_seed_threshold = COLOR_R8_G8_B8_TO_RGB888(fast_floorf(seed_threshold * COLOR_R8_MAX), + fast_floorf(seed_threshold * COLOR_G8_MAX), + fast_floorf(seed_threshold * COLOR_B8_MAX)); + color_floating_threshold = COLOR_R8_G8_B8_TO_RGB888(fast_floorf(floating_threshold * COLOR_R8_MAX), + fast_floorf(floating_threshold * COLOR_G8_MAX), + fast_floorf(floating_threshold * COLOR_B8_MAX)); + break; + } + default: { + break; + } + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&mean_image, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (!IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)) { + + imlib_cartoon_filter_mean_state_t mean_state; + memset(&mean_state, 0, sizeof(imlib_cartoon_filter_mean_state_t)); + imlib_flood_fill_int(&mean_image, img, x, y, color_seed_threshold, color_floating_threshold, + imlib_cartoon_filter_mean, &mean_state); + + imlib_cartoon_filter_fill_state_t fill_state; + memset(&fill_state, 0, sizeof(imlib_cartoon_filter_fill_state_t)); + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + fill_state.mean = mean_state.g_acc / mean_state.pixels; + break; + } + case PIXFORMAT_GRAYSCALE: { + fill_state.mean = mean_state.g_acc / mean_state.pixels; + break; + } + case PIXFORMAT_RGB565: { + fill_state.mean = COLOR_R5_G6_B5_TO_RGB565(mean_state.r_acc / mean_state.pixels, + mean_state.g_acc / mean_state.pixels, + mean_state.b_acc / mean_state.pixels); + break; + } + case PIXFORMAT_RGB888: { + fill_state.mean = COLOR_R8_G8_B8_TO_RGB888(mean_state.r_acc / mean_state.pixels, + mean_state.g_acc / mean_state.pixels, + mean_state.b_acc / mean_state.pixels); + break; + } + default: { + break; + } + } + + imlib_flood_fill_int(&fill_image, img, x, y, color_seed_threshold, color_floating_threshold, + imlib_cartoon_filter_fill, &fill_state); + } + } + } + + fb_free(fill_image.pixels); + fb_free(mean_image.pixels); +} +#endif // IMLIB_ENABLE_CARTOON diff --git a/github_source/minicv2/src/fmath.c b/github_source/minicv2/src/fmath.c new file mode 100644 index 0000000..63ea274 --- /dev/null +++ b/github_source/minicv2/src/fmath.c @@ -0,0 +1,266 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Fast approximate math functions. + */ +#include "fmath.h" +#include "common.h" + +#ifndef M_PI +#define M_PI 3.14159265f +#define M_PI_2 1.57079632f +#define M_PI_4 0.78539816f +#endif + +const float __atanf_lut[4] = { + -0.0443265554792128f, //p7 + -0.3258083974640975f, //p3 + +0.1555786518463281f, //p5 + +0.9997878412794807f //p1 +}; +#ifdef __ARM_ARCH +#undef __ARM_ARCH +#endif +#if (__ARM_ARCH < 7) +float OMV_ATTR_ALWAYS_INLINE fast_sqrtf(float x) +{ + return sqrtf(x); +} + +int OMV_ATTR_ALWAYS_INLINE fast_floorf(float x) +{ + return (int)floorf(x); +} + +int OMV_ATTR_ALWAYS_INLINE fast_ceilf(float x) +{ + return (int)ceilf(x); +} + +int OMV_ATTR_ALWAYS_INLINE fast_roundf(float x) +{ + return (int)roundf(x); +} + +float OMV_ATTR_ALWAYS_INLINE fast_fabsf(float x) +{ + return fabsf(x); +} +#else +float OMV_ATTR_ALWAYS_INLINE fast_sqrtf(float x) +{ + asm volatile ( + "vsqrt.f32 %[r], %[x]\n" + : [r] "=t" (x) + : [x] "t" (x)); + return x; +} + +int OMV_ATTR_ALWAYS_INLINE fast_floorf(float x) +{ + int i; + asm volatile ( + "vcvt.S32.f32 %[r], %[x]\n" + : [r] "=t" (i) + : [x] "t" (x)); + return i; +} + +int OMV_ATTR_ALWAYS_INLINE fast_ceilf(float x) +{ + int i; + x += 0.9999f; + asm volatile ( + "vcvt.S32.f32 %[r], %[x]\n" + : [r] "=t" (i) + : [x] "t" (x)); + return i; +} + +int OMV_ATTR_ALWAYS_INLINE fast_roundf(float x) +{ + int i; + asm volatile ( + "vcvtr.s32.f32 %[r], %[x]\n" + : [r] "=t" (i) + : [x] "t" (x)); + return i; +} + +float OMV_ATTR_ALWAYS_INLINE fast_fabsf(float x) +{ + asm volatile ( + "vabs.f32 %[r], %[x]\n" + : [r] "=t" (x) + : [x] "t" (x)); + return x; +} + +#endif + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +typedef union{ + uint32_t l; + struct { + uint32_t m : 20; + uint32_t e : 11; + uint32_t s : 1; + }; +}exp_t; + +float fast_expf(float x) +{ + exp_t e; + e.l = (uint32_t)(1512775 * x + 1072632447); + // IEEE binary32 format + e.e = (e.e -1023 + 127) &0xFF; // rebase + + uint32_t packed = (e.s << 31) | (e.e << 23) | e.m <<3; + return *((float*)&packed); +} +#pragma GCC diagnostic pop + +/* + * From Hackers Delight: + * This is a very approximate but very fast version of acbrt. It is just eight + * integer instructions (shift rights and adds), plus instructions to load the constant. + * 1/3 is approximated as 1/4 + 1/16 + 1/64 + 1/256 + ... + 1/65536. + * The constant 0x2a511cd0 balances the relative error at +-0.0321. + */ +float fast_cbrtf(float x) +{ + union {int ix; float x;} v; + v.x = x; // x can be viewed as int. + v.ix = v.ix/4 + v.ix/16; // Approximate divide by 3. + v.ix = v.ix + v.ix/16; + v.ix = v.ix + v.ix/256; + v.ix = 0x2a511cd0 + v.ix; // Initial guess. + return v.x; +} + +inline float fast_atanf(float xx) +{ + float x, y, z; + int sign; + + x = xx; + + /* make argument positive and save the sign */ + if( xx < 0.0f ) + { + sign = -1; + x = -xx; + } + else + { + sign = 1; + x = xx; + } + /* range reduction */ + if( x > 2.414213562373095f ) /* tan 3pi/8 */ + { + y = M_PI_2; + x = -( 1.0f/x ); + } + + else if( x > 0.4142135623730950f ) /* tan pi/8 */ + { + y = M_PI_4; + x = (x-1.0f)/(x+1.0f); + } + else + y = 0.0f; + + z = x * x; + y += + ((( 8.05374449538e-2f * z + - 1.38776856032E-1f) * z + + 1.99777106478E-1f) * z + - 3.33329491539E-1f) * z * x + x; + + if( sign < 0 ) + y = -y; + + return( y ); +} + +float fast_atan2f(float y, float x) +{ + if(x > 0 && y >= 0) + return fast_atanf(y/x); + + if(x < 0 && y >= 0) + return M_PI - fast_atanf(-y/x); + + if(x < 0 && y < 0) + return M_PI + fast_atanf(y/x); + + if(x > 0 && y < 0) + return 2*M_PI - fast_atanf(-y/x); + + return (y == 0) ? 0 : ((y > 0) ? M_PI : -M_PI); +} + +float fast_log2(float x) +{ + union { float f; uint32_t i; } vx = { x }; + union { uint32_t i; float f; } mx = { (vx.i & 0x007FFFFF) | 0x3f000000 }; + float y = vx.i; + y *= 1.1920928955078125e-7f; + + return y - 124.22551499f - 1.498030302f * mx.f + - 1.72587999f / (0.3520887068f + mx.f); +} + +float fast_log(float x) +{ + return 0.69314718f * fast_log2 (x); +} + +float fast_powf(float a, float b) +{ + union { float d; int x; } u = { a }; + u.x = (int)((b * (u.x - 1064866805)) + 1064866805); + return u.d; +} + +void fast_get_min_max(float *data, size_t data_len, float *p_min, float *p_max) +{ + float min = FLT_MAX, max = -FLT_MAX; + + for (size_t i = 0; i < data_len; i++) { + float temp = data[i]; + + if (temp < min) { + min = temp; + } + + if (temp > max) { + max = temp; + } + } + + *p_min = min; + *p_max = max; +} + +#include +#include + +void fmath_init() +{ + srand((unsigned int)time(0)); +} + +uint32_t rng_randint(uint32_t min, uint32_t max) +{ + uint32_t tmp = max - min; + tmp = rand() % tmp + min; + return tmp; +} diff --git a/github_source/minicv2/src/font.c b/github_source/minicv2/src/font.c new file mode 100644 index 0000000..359ef36 --- /dev/null +++ b/github_source/minicv2/src/font.c @@ -0,0 +1,1077 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Font data. + * + * Size: 8 Style: Normal + * Included characters: + * !"#$%&'()*+,-./0123456789:;<=>?\x0040ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ + * Antialiasing: yes + * Type: monospaced + * Encoding: ASMO-708 + * Unicode bom: no + * + * Preset name: Monochrome + * Data block size: 8 bit(s), uint8_t + * RLE compression enabled: no + * Conversion type: Monochrome, Diffuse Dither 128 + * Bits per pixel: 1 + * + * Preprocess: + * main scan direction: top_to_bottom + * line scan direction: forward + * inverse: yes + */ +#include "font.h" +const glyph_t font[95] = { + // character: ' ' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}}, + // character: '!' + {8, 10, {0x00, + 0x00, + 0x10, + 0x10, + 0x10, + 0x10, + 0x00, + 0x10, + 0x00, + 0x00}}, + // character: '"' + {8, 10, {0x00, + 0x00, + 0x18, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}}, + // character: '#' + {8, 10, {0x00, + 0x00, + 0x24, + 0x7c, + 0x24, + 0x48, + 0x7c, + 0x48, + 0x00, + 0x00}}, + // character: '$' + {8, 10, {0x00, + 0x00, + 0x08, + 0x1c, + 0x20, + 0x18, + 0x04, + 0x38, + 0x08, + 0x00}}, + // character: '%' + {8, 10, {0x00, + 0x00, + 0x24, + 0x58, + 0x28, + 0x14, + 0x1a, + 0x24, + 0x00, + 0x00}}, + // character: '&' + {8, 10, {0x00, + 0x00, + 0x10, + 0x28, + 0x30, + 0x34, + 0x2c, + 0x1c, + 0x00, + 0x00}}, + // character: ''' + {8, 10, {0x00, + 0x00, + 0x10, + 0x10, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}}, + // character: '(' + {8, 10, {0x00, + 0x00, + 0x08, + 0x10, + 0x10, + 0x10, + 0x10, + 0x10, + 0x08, + 0x00}}, + // character: ')' + {8, 10, {0x00, + 0x00, + 0x20, + 0x10, + 0x10, + 0x10, + 0x10, + 0x10, + 0x20, + 0x00}}, + // character: '*' + {8, 10, {0x00, + 0x00, + 0x10, + 0x38, + 0x28, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}}, + // character: '+' + {8, 10, {0x00, + 0x00, + 0x00, + 0x10, + 0x10, + 0x7c, + 0x10, + 0x10, + 0x00, + 0x00}}, + // character: ',' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x10, + 0x20, + 0x00}}, + // character: '-' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00}}, + // character: '.' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00}}, + // character: '/' + {8, 10, {0x00, + 0x00, + 0x08, + 0x08, + 0x10, + 0x10, + 0x10, + 0x20, + 0x20, + 0x00}}, + // character: '0' + {8, 10, {0x00, + 0x00, + 0x18, + 0x24, + 0x24, + 0x24, + 0x24, + 0x18, + 0x00, + 0x00}}, + // character: '1' + {8, 10, {0x00, + 0x00, + 0x08, + 0x18, + 0x08, + 0x08, + 0x08, + 0x08, + 0x00, + 0x00}}, + // character: '2' + {8, 10, {0x00, + 0x00, + 0x18, + 0x24, + 0x04, + 0x08, + 0x10, + 0x3c, + 0x00, + 0x00}}, + // character: '3' + {8, 10, {0x00, + 0x00, + 0x38, + 0x04, + 0x18, + 0x04, + 0x04, + 0x38, + 0x00, + 0x00}}, + // character: '4' + {8, 10, {0x00, + 0x00, + 0x08, + 0x18, + 0x28, + 0x3c, + 0x08, + 0x08, + 0x00, + 0x00}}, + // character: '5' + {8, 10, {0x00, + 0x00, + 0x1c, + 0x10, + 0x18, + 0x04, + 0x04, + 0x18, + 0x00, + 0x00}}, + // character: '6' + {8, 10, {0x00, + 0x00, + 0x0c, + 0x10, + 0x38, + 0x24, + 0x24, + 0x18, + 0x00, + 0x00}}, + // character: '7' + {8, 10, {0x00, + 0x00, + 0x38, + 0x08, + 0x08, + 0x10, + 0x10, + 0x10, + 0x00, + 0x00}}, + // character: '8' + {8, 10, {0x00, + 0x00, + 0x18, + 0x24, + 0x18, + 0x24, + 0x24, + 0x18, + 0x00, + 0x00}}, + // character: '9' + {8, 10, {0x00, + 0x00, + 0x18, + 0x24, + 0x24, + 0x3c, + 0x08, + 0x30, + 0x00, + 0x00}}, + // character: ':' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00}}, + // character: ';' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x00, + 0x10, + 0x20, + 0x00}}, + // character: '<' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x38, + 0x40, + 0x38, + 0x00, + 0x00, + 0x00}}, + // character: '=' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x3c, + 0x00, + 0x3c, + 0x00, + 0x00, + 0x00}}, + // character: '>' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x30, + 0x08, + 0x30, + 0x00, + 0x00, + 0x00}}, + // character: '?' + {8, 10, {0x00, + 0x00, + 0x18, + 0x08, + 0x08, + 0x10, + 0x00, + 0x10, + 0x00, + 0x00}}, + // character: '\x0040' + {8, 10, {0x00, + 0x00, + 0x38, + 0x44, + 0x9a, + 0xaa, + 0xaa, + 0xb4, + 0x40, + 0x38}}, + // character: 'A' + {8, 10, {0x00, + 0x00, + 0x10, + 0x28, + 0x28, + 0x28, + 0x7c, + 0x44, + 0x00, + 0x00}}, + // character: 'B' + {8, 10, {0x00, + 0x00, + 0x38, + 0x24, + 0x38, + 0x24, + 0x24, + 0x38, + 0x00, + 0x00}}, + // character: 'C' + {8, 10, {0x00, + 0x00, + 0x1c, + 0x20, + 0x20, + 0x20, + 0x20, + 0x1c, + 0x00, + 0x00}}, + // character: 'D' + {8, 10, {0x00, + 0x00, + 0x78, + 0x44, + 0x44, + 0x44, + 0x44, + 0x78, + 0x00, + 0x00}}, + // character: 'E' + {8, 10, {0x00, + 0x00, + 0x3c, + 0x20, + 0x38, + 0x20, + 0x20, + 0x3c, + 0x00, + 0x00}}, + // character: 'F' + {8, 10, {0x00, + 0x00, + 0x38, + 0x20, + 0x30, + 0x20, + 0x20, + 0x20, + 0x00, + 0x00}}, + // character: 'G' + {8, 10, {0x00, + 0x00, + 0x1c, + 0x20, + 0x20, + 0x24, + 0x24, + 0x1c, + 0x00, + 0x00}}, + // character: 'H' + {8, 10, {0x00, + 0x00, + 0x44, + 0x44, + 0x7c, + 0x44, + 0x44, + 0x44, + 0x00, + 0x00}}, + // character: 'I' + {8, 10, {0x00, + 0x00, + 0x10, + 0x10, + 0x10, + 0x10, + 0x10, + 0x10, + 0x00, + 0x00}}, + // character: 'J' + {8, 10, {0x00, + 0x00, + 0x08, + 0x08, + 0x08, + 0x08, + 0x08, + 0x30, + 0x00, + 0x00}}, + // character: 'K' + {8, 10, {0x00, + 0x00, + 0x24, + 0x28, + 0x30, + 0x30, + 0x28, + 0x24, + 0x00, + 0x00}}, + // character: 'L' + {8, 10, {0x00, + 0x00, + 0x20, + 0x20, + 0x20, + 0x20, + 0x20, + 0x38, + 0x00, + 0x00}}, + // character: 'M' + {8, 10, {0x00, + 0x00, + 0x44, + 0x6c, + 0x6c, + 0x54, + 0x54, + 0x44, + 0x00, + 0x00}}, + // character: 'N' + {8, 10, {0x00, + 0x00, + 0x42, + 0x62, + 0x52, + 0x4a, + 0x46, + 0x42, + 0x00, + 0x00}}, + // character: 'O' + {8, 10, {0x00, + 0x00, + 0x38, + 0x44, + 0x44, + 0x44, + 0x44, + 0x38, + 0x00, + 0x00}}, + // character: 'P' + {8, 10, {0x00, + 0x00, + 0x38, + 0x24, + 0x24, + 0x38, + 0x20, + 0x20, + 0x00, + 0x00}}, + // character: 'Q' + {8, 10, {0x00, + 0x00, + 0x38, + 0x44, + 0x44, + 0x44, + 0x44, + 0x38, + 0x10, + 0x08}}, + // character: 'R' + {8, 10, {0x00, + 0x00, + 0x38, + 0x24, + 0x24, + 0x38, + 0x28, + 0x24, + 0x00, + 0x00}}, + // character: 'S' + {8, 10, {0x00, + 0x00, + 0x18, + 0x20, + 0x20, + 0x18, + 0x08, + 0x30, + 0x00, + 0x00}}, + // character: 'T' + {8, 10, {0x00, + 0x00, + 0x38, + 0x10, + 0x10, + 0x10, + 0x10, + 0x10, + 0x00, + 0x00}}, + // character: 'U' + {8, 10, {0x00, + 0x00, + 0x44, + 0x44, + 0x44, + 0x44, + 0x44, + 0x38, + 0x00, + 0x00}}, + // character: 'V' + {8, 10, {0x00, + 0x00, + 0x44, + 0x44, + 0x28, + 0x28, + 0x28, + 0x10, + 0x00, + 0x00}}, + // character: 'W' + {8, 10, {0x00, + 0x00, + 0x82, + 0x92, + 0xaa, + 0xaa, + 0xaa, + 0x44, + 0x00, + 0x00}}, + // character: 'X' + {8, 10, {0x00, + 0x00, + 0x44, + 0x28, + 0x10, + 0x10, + 0x28, + 0x44, + 0x00, + 0x00}}, + // character: 'Y' + {8, 10, {0x00, + 0x00, + 0x44, + 0x28, + 0x28, + 0x10, + 0x10, + 0x10, + 0x00, + 0x00}}, + // character: 'Z' + {8, 10, {0x00, + 0x00, + 0x3c, + 0x04, + 0x08, + 0x10, + 0x20, + 0x3c, + 0x00, + 0x00}}, + // character: '[' + {8, 10, {0x00, + 0x00, + 0x18, + 0x10, + 0x10, + 0x10, + 0x10, + 0x10, + 0x18, + 0x00}}, + // character: '\' + {8, 10, {0x00, + 0x00, + 0x20, + 0x20, + 0x10, + 0x10, + 0x10, + 0x08, + 0x08, + 0x00}}, + // character: ']' + {8, 10, {0x00, + 0x00, + 0x30, + 0x10, + 0x10, + 0x10, + 0x10, + 0x10, + 0x30, + 0x00}}, + // character: '^' + {8, 10, {0x00, + 0x00, + 0x10, + 0x28, + 0x28, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}}, + // character: '_' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x78, + 0x00}}, + // character: '`' + {8, 10, {0x00, + 0x10, + 0x08, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}}, + // character: 'a' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x30, + 0x08, + 0x18, + 0x38, + 0x00, + 0x00}}, + // character: 'b' + {8, 10, {0x00, + 0x00, + 0x20, + 0x20, + 0x38, + 0x24, + 0x24, + 0x38, + 0x00, + 0x00}}, + // character: 'c' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x20, + 0x20, + 0x18, + 0x00, + 0x00}}, + // character: 'd' + {8, 10, {0x00, + 0x00, + 0x04, + 0x04, + 0x1c, + 0x24, + 0x24, + 0x1c, + 0x00, + 0x00}}, + // character: 'e' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x38, + 0x38, + 0x20, + 0x18, + 0x00, + 0x00}}, + // character: 'f' + {8, 10, {0x00, + 0x00, + 0x08, + 0x10, + 0x18, + 0x10, + 0x10, + 0x10, + 0x00, + 0x00}}, + // character: 'g' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x24, + 0x1c, + 0x04, + 0x38, + 0x00}}, + // character: 'h' + {8, 10, {0x00, + 0x00, + 0x20, + 0x20, + 0x38, + 0x24, + 0x24, + 0x24, + 0x00, + 0x00}}, + // character: 'i' + {8, 10, {0x00, + 0x00, + 0x10, + 0x00, + 0x10, + 0x10, + 0x10, + 0x10, + 0x00, + 0x00}}, + // character: 'j' + {8, 10, {0x00, + 0x00, + 0x10, + 0x00, + 0x10, + 0x10, + 0x10, + 0x10, + 0x20, + 0x00}}, + // character: 'k' + {8, 10, {0x00, + 0x00, + 0x20, + 0x20, + 0x28, + 0x30, + 0x30, + 0x28, + 0x00, + 0x00}}, + // character: 'l' + {8, 10, {0x00, + 0x00, + 0x10, + 0x10, + 0x10, + 0x10, + 0x10, + 0x08, + 0x00, + 0x00}}, + // character: 'm' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0xec, + 0x92, + 0x92, + 0x92, + 0x00, + 0x00}}, + // character: 'n' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x38, + 0x24, + 0x24, + 0x24, + 0x00, + 0x00}}, + // character: 'o' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x24, + 0x24, + 0x18, + 0x00, + 0x00}}, + // character: 'p' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x38, + 0x24, + 0x24, + 0x38, + 0x20, + 0x00}}, + // character: 'q' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x1c, + 0x24, + 0x24, + 0x1c, + 0x04, + 0x00}}, + // character: 'r' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x10, + 0x10, + 0x10, + 0x00, + 0x00}}, + // character: 's' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x20, + 0x18, + 0x30, + 0x00, + 0x00}}, + // character: 't' + {8, 10, {0x00, + 0x00, + 0x00, + 0x10, + 0x18, + 0x10, + 0x10, + 0x08, + 0x00, + 0x00}}, + // character: 'u' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x24, + 0x24, + 0x24, + 0x1c, + 0x00, + 0x00}}, + // character: 'v' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x28, + 0x28, + 0x10, + 0x10, + 0x00, + 0x00}}, + // character: 'w' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x54, + 0x54, + 0x54, + 0x28, + 0x00, + 0x00}}, + // character: 'x' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x28, + 0x10, + 0x10, + 0x28, + 0x00, + 0x00}}, + // character: 'y' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x28, + 0x28, + 0x10, + 0x10, + 0x20, + 0x00}}, + // character: 'z' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x18, + 0x08, + 0x10, + 0x18, + 0x00, + 0x00}}, + // character: '{' + {8, 10, {0x00, + 0x00, + 0x08, + 0x10, + 0x10, + 0x20, + 0x10, + 0x10, + 0x08, + 0x00}}, + // character: '|' + {8, 10, {0x00, + 0x00, + 0x10, + 0x10, + 0x10, + 0x10, + 0x10, + 0x10, + 0x10, + 0x00}}, + // character: '}' + {8, 10, {0x00, + 0x00, + 0x20, + 0x10, + 0x10, + 0x08, + 0x10, + 0x10, + 0x20, + 0x00}}, + // character: '~' + {8, 10, {0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x28, + 0x50, + 0x00, + 0x00, + 0x00}} +}; diff --git a/github_source/minicv2/src/font_ttf.c b/github_source/minicv2/src/font_ttf.c new file mode 100644 index 0000000..5e2eb15 --- /dev/null +++ b/github_source/minicv2/src/font_ttf.c @@ -0,0 +1,559 @@ + +#include "font_ttf.h" +/* + * Name Envy-Code-B-10pt-1.ttf (8 * 10) + * Included characters: + * !"#$%&'()*+,-./0123456789:;<=>?\x0040ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ + * Preprocess: + * main scan direction: top_to_bottom + * line scan direction: forward + */ +static uint8_t ascii[] = { + /* 0x20 [ ] */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + /* 0x21 [!] */ + 0x00,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x10,0x10,0x00,0x00, + /* 0x22 ["] */ + 0x00,0x48,0x48,0x48,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + /* 0x23 [#] */ + 0x00,0x00,0x00,0x28,0x7C,0x28,0x28,0x7C,0x28,0x00,0x00,0x00, + /* 0x24 [$] */ + 0x00,0x10,0x3C,0x50,0x50,0x30,0x18,0x14,0x14,0x78,0x10,0x00, + /* 0x25 [%] */ + 0x00,0x00,0x30,0x49,0x32,0x04,0x08,0x10,0x26,0x49,0x06,0x00, + /* 0x26 [&] */ + 0x00,0x00,0x78,0x48,0x50,0x66,0xA4,0x94,0x88,0x76,0x00,0x00, + /* 0x27 ['] */ + 0x00,0x10,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + /* 0x28 [(] */ + 0x08,0x10,0x10,0x20,0x20,0x20,0x20,0x20,0x20,0x10,0x10,0x08, + /* 0x29 [)] */ + 0x20,0x10,0x10,0x08,0x08,0x08,0x08,0x08,0x08,0x10,0x10,0x20, + /* 0x2A [*] */ + 0x00,0x10,0x54,0x38,0x54,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + /* 0x2B [+] */ + 0x00,0x00,0x00,0x10,0x10,0x7C,0x10,0x10,0x00,0x00,0x00,0x00, + /* 0x2C [,] */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x30,0x00, + /* 0x2D [-] */ + 0x00,0x00,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,0x00,0x00,0x00, + /* 0x2E [.] */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00, + /* 0x2F [/] */ + 0x00,0x04,0x04,0x08,0x08,0x10,0x10,0x20,0x20,0x40,0x40,0x00, + /* 0x30 [0] */ + 0x00,0x38,0x44,0x44,0x4C,0x54,0x64,0x44,0x44,0x38,0x00,0x00, + /* 0x31 [1] */ + 0x00,0x10,0x30,0x10,0x10,0x10,0x10,0x10,0x10,0x38,0x00,0x00, + /* 0x32 [2] */ + 0x00,0x38,0x44,0x04,0x08,0x10,0x20,0x40,0x40,0x7C,0x00,0x00, + /* 0x33 [3] */ + 0x00,0x38,0x44,0x04,0x04,0x18,0x04,0x04,0x44,0x38,0x00,0x00, + /* 0x34 [4] */ + 0x00,0x08,0x18,0x28,0x28,0x48,0x48,0x7C,0x08,0x08,0x00,0x00, + /* 0x35 [5] */ + 0x00,0x7C,0x40,0x40,0x40,0x78,0x04,0x04,0x04,0x78,0x00,0x00, + /* 0x36 [6] */ + 0x00,0x08,0x10,0x20,0x40,0x78,0x44,0x44,0x44,0x38,0x00,0x00, + /* 0x37 [7] */ + 0x00,0x7C,0x04,0x04,0x08,0x08,0x10,0x10,0x20,0x20,0x00,0x00, + /* 0x38 [8] */ + 0x00,0x38,0x44,0x44,0x44,0x38,0x44,0x44,0x44,0x38,0x00,0x00, + /* 0x39 [9] */ + 0x00,0x38,0x44,0x44,0x44,0x38,0x08,0x10,0x20,0x40,0x00,0x00, + /* 0x3A [:] */ + 0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x30,0x30,0x00,0x00, + /* 0x3B [;] */ + 0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x30,0x30,0x60,0x00, + /* 0x3C [<] */ + 0x00,0x00,0x08,0x10,0x20,0x40,0x20,0x10,0x08,0x00,0x00,0x00, + /* 0x3D [=] */ + 0x00,0x00,0x00,0x00,0xFC,0x00,0xFC,0x00,0x00,0x00,0x00,0x00, + /* 0x3E [>] */ + 0x00,0x00,0x40,0x20,0x10,0x08,0x10,0x20,0x40,0x00,0x00,0x00, + /* 0x3F [?] */ + 0x00,0x38,0x44,0x44,0x04,0x08,0x10,0x00,0x10,0x10,0x00,0x00, + /* 0x40 [@] */ + 0x00,0x38,0x44,0x82,0x9A,0xAA,0xAA,0xAA,0xAA,0x9C,0x80,0x40, + /* 0x41 [A] */ + 0x00,0x38,0x44,0x44,0x44,0x7C,0x44,0x44,0x44,0x44,0x00,0x00, + /* 0x42 [B] */ + 0x00,0x78,0x44,0x44,0x44,0x78,0x44,0x44,0x44,0x78,0x00,0x00, + /* 0x43 [C] */ + 0x00,0x1C,0x20,0x40,0x40,0x40,0x40,0x40,0x20,0x1C,0x00,0x00, + /* 0x44 [D] */ + 0x00,0x70,0x48,0x44,0x44,0x44,0x44,0x44,0x48,0x70,0x00,0x00, + /* 0x45 [E] */ + 0x00,0x7C,0x40,0x40,0x40,0x78,0x40,0x40,0x40,0x7C,0x00,0x00, + /* 0x46 [F] */ + 0x00,0x7C,0x40,0x40,0x40,0x78,0x40,0x40,0x40,0x40,0x00,0x00, + /* 0x47 [G] */ + 0x00,0x1C,0x20,0x40,0x40,0x4C,0x44,0x44,0x24,0x1C,0x00,0x00, + /* 0x48 [H] */ + 0x00,0x44,0x44,0x44,0x44,0x7C,0x44,0x44,0x44,0x44,0x00,0x00, + /* 0x49 [I] */ + 0x00,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00, + /* 0x4A [J] */ + 0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x44,0x38,0x00,0x00, + /* 0x4B [K] */ + 0x00,0x44,0x44,0x48,0x50,0x60,0x50,0x48,0x44,0x44,0x00,0x00, + /* 0x4C [L] */ + 0x00,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x7C,0x00,0x00, + /* 0x4D [M] */ + 0x00,0x44,0x44,0x6C,0x6C,0x54,0x54,0x44,0x44,0x44,0x00,0x00, + /* 0x4E [N] */ + 0x00,0x44,0x44,0x64,0x64,0x54,0x54,0x4C,0x4C,0x44,0x00,0x00, + /* 0x4F [O] */ + 0x00,0x38,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x38,0x00,0x00, + /* 0x50 [P] */ + 0x00,0x78,0x44,0x44,0x44,0x78,0x40,0x40,0x40,0x40,0x00,0x00, + /* 0x51 [Q] */ + 0x00,0x38,0x44,0x44,0x44,0x44,0x44,0x44,0x48,0x34,0x04,0x00, + /* 0x52 [R] */ + 0x00,0x78,0x44,0x44,0x44,0x78,0x50,0x48,0x44,0x44,0x00,0x00, + /* 0x53 [S] */ + 0x00,0x3C,0x40,0x40,0x20,0x10,0x08,0x04,0x04,0x78,0x00,0x00, + /* 0x54 [T] */ + 0x00,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00, + /* 0x55 [U] */ + 0x00,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0x38,0x00,0x00, + /* 0x56 [V] */ + 0x00,0x44,0x44,0x44,0x44,0x28,0x28,0x28,0x10,0x10,0x00,0x00, + /* 0x57 [W] */ + 0x00,0x44,0x44,0x44,0x44,0x44,0x54,0x54,0x54,0x28,0x00,0x00, + /* 0x58 [X] */ + 0x00,0x44,0x44,0x28,0x28,0x10,0x28,0x28,0x44,0x44,0x00,0x00, + /* 0x59 [Y] */ + 0x00,0x44,0x44,0x44,0x28,0x28,0x10,0x10,0x10,0x10,0x00,0x00, + /* 0x5A [Z] */ + 0x00,0x7C,0x04,0x04,0x08,0x10,0x20,0x40,0x40,0x7C,0x00,0x00, + /* 0x5B [[] */ + 0x38,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x38, + /* 0x5C [\] */ + 0x00,0x40,0x40,0x20,0x20,0x10,0x10,0x08,0x08,0x04,0x04,0x00, + /* 0x5D []] */ + 0x38,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x38, + /* 0x5E [^] */ + 0x00,0x10,0x10,0x28,0x28,0x44,0x00,0x00,0x00,0x00,0x00,0x00, + /* 0x5F [_] */ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x00, + /* 0x60 [`] */ + 0x00,0x30,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + /* 0x61 [a] */ + 0x00,0x00,0x00,0x38,0x04,0x04,0x3C,0x44,0x44,0x3C,0x00,0x00, + /* 0x62 [b] */ + 0x40,0x40,0x40,0x78,0x44,0x44,0x44,0x44,0x44,0x78,0x00,0x00, + /* 0x63 [c] */ + 0x00,0x00,0x00,0x1C,0x20,0x40,0x40,0x40,0x20,0x1C,0x00,0x00, + /* 0x64 [d] */ + 0x04,0x04,0x04,0x3C,0x44,0x44,0x44,0x44,0x44,0x3C,0x00,0x00, + /* 0x65 [e] */ + 0x00,0x00,0x00,0x38,0x44,0x44,0x7C,0x40,0x40,0x3C,0x00,0x00, + /* 0x66 [f] */ + 0x00,0x1C,0x20,0x20,0x78,0x20,0x20,0x20,0x20,0x20,0x00,0x00, + /* 0x67 [g] */ + 0x00,0x00,0x00,0x3C,0x44,0x44,0x44,0x44,0x3C,0x04,0x04,0x7C, + /* 0x68 [h] */ + 0x00,0x40,0x40,0x58,0x64,0x44,0x44,0x44,0x44,0x44,0x00,0x00, + /* 0x69 [i] */ + 0x00,0x10,0x00,0x30,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00, + /* 0x6A [j] */ + 0x00,0x08,0x00,0x18,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x30, + /* 0x6B [k] */ + 0x00,0x40,0x40,0x44,0x48,0x50,0x60,0x50,0x48,0x44,0x00,0x00, + /* 0x6C [l] */ + 0x00,0x30,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00, + /* 0x6D [m] */ + 0x00,0x00,0x00,0x68,0x54,0x54,0x54,0x54,0x54,0x54,0x00,0x00, + /* 0x6E [n] */ + 0x00,0x00,0x00,0x58,0x64,0x44,0x44,0x44,0x44,0x44,0x00,0x00, + /* 0x6F [o] */ + 0x00,0x00,0x00,0x38,0x44,0x44,0x44,0x44,0x44,0x38,0x00,0x00, + /* 0x70 [p] */ + 0x00,0x00,0x00,0x78,0x44,0x44,0x44,0x44,0x44,0x78,0x40,0x40, + /* 0x71 [q] */ + 0x00,0x00,0x00,0x3C,0x44,0x44,0x44,0x44,0x44,0x3C,0x04,0x04, + /* 0x72 [r] */ + 0x00,0x00,0x00,0x58,0x64,0x40,0x40,0x40,0x40,0x40,0x00,0x00, + /* 0x73 [s] */ + 0x00,0x00,0x00,0x3C,0x40,0x40,0x38,0x04,0x04,0x78,0x00,0x00, + /* 0x74 [t] */ + 0x00,0x20,0x20,0x78,0x20,0x20,0x20,0x20,0x20,0x1C,0x00,0x00, + /* 0x75 [u] */ + 0x00,0x00,0x00,0x44,0x44,0x44,0x44,0x44,0x4C,0x34,0x00,0x00, + /* 0x76 [v] */ + 0x00,0x00,0x00,0x44,0x44,0x44,0x28,0x28,0x10,0x10,0x00,0x00, + /* 0x77 [w] */ + 0x00,0x00,0x00,0x44,0x44,0x44,0x54,0x54,0x54,0x28,0x00,0x00, + /* 0x78 [x] */ + 0x00,0x00,0x00,0x44,0x44,0x28,0x10,0x28,0x44,0x44,0x00,0x00, + /* 0x79 [y] */ + 0x00,0x00,0x00,0x44,0x44,0x44,0x28,0x28,0x28,0x10,0x10,0x20, + /* 0x7A [z] */ + 0x00,0x00,0x00,0x7C,0x04,0x08,0x10,0x20,0x40,0x7C,0x00,0x00, + /* 0x7B [{] */ + 0x06,0x08,0x08,0x08,0x10,0x60,0x10,0x08,0x08,0x08,0x08,0x06, + /* 0x7C [|] */ + 0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, + /* 0x7D [}] */ + 0x60,0x10,0x10,0x10,0x08,0x06,0x08,0x10,0x10,0x10,0x10,0x60, + /* 0x7E [~] */ + 0x00,0x00,0x00,0x00,0x24,0x54,0x48,0x00,0x00,0x00,0x00,0x00, +}; + +struct font +{ + /* data */ + uint8_t width; + uint8_t high; + uint8_t index; + uint8_t source; + void *this; +} font_config = { + 8, 12, ASCII, BuildIn, ascii +}; + +static inline void font_init(uint8_t width, uint8_t high, uint8_t index, uint8_t source_type, void *font_offset) +{ + struct font tmp = { width, high, index, source_type, font_offset}; + font_config = tmp; +} + +// #include "vfs_wrapper.h" +// #include "nlr.h" + +void font_free() +{ + switch (font_config.index) + { + case UTF8: + case Unicode: + if (font_config.source == FileIn) + { + file_close(font_config.this); + } + case GBK: + case GB2312: + case ASCII: + default: + font_init(8, 12, ASCII, BuildIn, ascii); + break; + } +} + +void font_load(uint8_t index, uint8_t width, uint8_t high, uint8_t source_type, void *src_addr) +{ + + switch (index) + { + case UTF8: + if (src_addr == NULL) + { + font_init(8, 12, ASCII, BuildIn, ascii); + break; + } + font_init(width, high, UTF8, source_type, src_addr); + break; + default: + case Unicode: + case GBK: + case GB2312: + case ASCII: + font_init(8, 12, ASCII, BuildIn, ascii); + break; + } +} + +int font_get_utf8_size(const uint8_t pInput) +{ + uint8_t c = pInput; + // 0xxxxxxx 1 + // 10xxxxxx -1 + // 110xxxxx 2 + // 1110xxxx 3 + // 11110xxx 4 + // 111110xx 5 + // 1111110x 6 + if(c< 0x80) return 1; + if(c>=0x80 && c<0xC0) return -1; + if(c>=0xC0 && c<0xE0) return 2; + if(c>=0xE0 && c<0xF0) return 3; + if(c>=0xF0 && c<0xF8) return 4; + if(c>=0xF8 && c<0xFC) return 5; + if(c>=0xFC) return 6; + return 0; +} + +int font_utf8_to_unicode(const uint8_t* pInput, uint64_t *Unic) +{ + // assert(pInput != NULL && Unic != NULL); + + char b1, b2, b3, b4, b5, b6; + + *Unic = 0x0; + int utfbytes = font_get_utf8_size(*pInput); + uint8_t *pOutput = (uint8_t *) Unic; + + switch ( utfbytes ) + { + case 1: + *pOutput = *pInput; + break; + case 2: + b1 = *pInput; + b2 = *(pInput + 1); + if ( (b2 & 0xE0) != 0x80 ) + return 0; + *pOutput = (b1 << 6) + (b2 & 0x3F); + *(pOutput+1) = (b1 >> 2) & 0x07; + break; + case 3: + b1 = *pInput; + b2 = *(pInput + 1); + b3 = *(pInput + 2); + if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) ) + return 0; + *pOutput = (b2 << 6) + (b3 & 0x3F); + *(pOutput+1) = (b1 << 4) + ((b2 >> 2) & 0x0F); + break; + case 4: + b1 = *pInput; + b2 = *(pInput + 1); + b3 = *(pInput + 2); + b4 = *(pInput + 3); + if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) + || ((b4 & 0xC0) != 0x80) ) + return 0; + *pOutput = (b3 << 6) + (b4 & 0x3F); + *(pOutput+1) = (b2 << 4) + ((b3 >> 2) & 0x0F); + *(pOutput+2) = ((b1 << 2) & 0x1C) + ((b2 >> 4) & 0x03); + break; + case 5: + b1 = *pInput; + b2 = *(pInput + 1); + b3 = *(pInput + 2); + b4 = *(pInput + 3); + b5 = *(pInput + 4); + if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) + || ((b4 & 0xC0) != 0x80) || ((b5 & 0xC0) != 0x80) ) + return 0; + *pOutput = (b4 << 6) + (b5 & 0x3F); + *(pOutput+1) = (b3 << 4) + ((b4 >> 2) & 0x0F); + *(pOutput+2) = (b2 << 2) + ((b3 >> 4) & 0x03); + *(pOutput+3) = (b1 << 6); + break; + case 6: + b1 = *pInput; + b2 = *(pInput + 1); + b3 = *(pInput + 2); + b4 = *(pInput + 3); + b5 = *(pInput + 4); + b6 = *(pInput + 5); + if ( ((b2 & 0xC0) != 0x80) || ((b3 & 0xC0) != 0x80) + || ((b4 & 0xC0) != 0x80) || ((b5 & 0xC0) != 0x80) + || ((b6 & 0xC0) != 0x80) ) + return 0; + *pOutput = (b5 << 6) + (b6 & 0x3F); + *(pOutput+1) = (b5 << 4) + ((b6 >> 2) & 0x0F); + *(pOutput+2) = (b3 << 2) + ((b4 >> 4) & 0x03); + *(pOutput+3) = ((b1 << 6) & 0x40) + (b2 & 0x3F); + break; + default: + return 0; + break; + } + + return utfbytes; +} + +void imlib_draw_font(image_t *img, int x_off, int y_off, int c, float scale, uint8_t font_h, uint8_t font_w, const uint8_t *font) +{ + // It will be replaced by *.SVG. + /* font ↑ ↓ ↝ → + 01 02 03 04 + 05 06 07 08 + 09 10 11 12 + 13 14 15 16 + 1. ↓ 01 05 09 13 + 2. → 01 02 03 04 + 3. Got it? + */ + for (int y = 0, yy = fast_roundf(font_h * scale); y < yy; y++) { + uint8_t pos = fast_roundf(y / scale); + uint32_t tmp = font[pos]; + for (uint8_t i = 1; i < font_h / 8; i++) { + tmp <<= 8, tmp |= font[pos + i * font_h]; + } + for (uint8_t x = 0, xx = fast_roundf(font_w * scale); x < xx; x++) { + if (tmp & (1 << (font_w - 1 - fast_roundf(x / scale)))) { + imlib_set_pixel(img, (x_off + x), (y_off + y), c); + } + } + } +} + + +void imlib_draw_utf8_string(image_t *img, int x_off, int y_off, const char *str, int c, float scale, int x_spacing, int y_spacing, bool mono_space) { + const uint8_t font_len = (font_config.width / 8) * font_config.high; + const uint8_t *string = (uint8_t*)str; + int len = strlen(str); + + // mp_buffer_info_t bufinfo; + // mp_get_buffer_raise(str, &bufinfo, MP_BUFFER_READ); + + for(int i = 0, pos = 0, bytes = 0; i < len; i += bytes, pos += 1) { + + uint64_t offset = 0; + bytes = font_utf8_to_unicode(string + i, &offset); + // printk("utfbytes %d offset %llu\r\n", bytes, offset); + if (bytes <= 0 || offset <= 0) { // unicode len + break; + } + + uint8_t buffer[font_len]; + + switch (font_config.source) + { + case FileIn: + /*******************************************************************************************/ + // file_seek_raise(font_config.this, offset * font_len, 0); + // read_data_raise(font_config.this, buffer, font_len); + /*******************************************************************************************/ + break; + case ArrayIn: + // printk("%d %p %p %p", font_len, buffer, font_config.this, &font_config.this[offset * font_len]); + // memcpy(buffer, &font_config.this[offset * font_len], font_len); + /*******************************************************************************************/ + // sys_spiffs_read(font_config.this + offset * font_len, font_len, buffer); + /*******************************************************************************************/ + break; + default: + break; + } + + const uint8_t *font = buffer; + + if (!mono_space) { + // Find the first pixel set and offset to that. + bool exit = false; + + for (int x = 0, xx = font_config.width; x < xx; x++) { + for (int y = 0, yy = font_config.high; y < yy; y++) { + if (font[y] & (1 << (font_config.width - 1 - x))) { + x_off -= fast_roundf(x * scale); + exit = true; + break; + } + } + + if (exit) break; + } + } + + imlib_draw_font(img, x_off, y_off, c, scale, font_config.high, font_config.width, font); + + if (mono_space) { + x_off += fast_roundf(font_config.width * scale) + x_spacing; + } else { + // Find the last pixel set and offset to that. + bool exit = false; + + for (int x = font_config.width - 1; x >= 0; x--) { + for (int y = font_config.high - 1; y >= 0; y--) { + if (font[y] & (1 << (font_config.width - 1 - x))) { + x_off += fast_roundf((x + 2) * scale) + x_spacing; + exit = true; + break; + } + } + + if (exit) break; + } + + if (!exit) x_off += fast_roundf(scale * 3); // space char + } + } +} + +void imlib_draw_ascii_string(image_t *img, int x_off, int y_off, const char *str, int c, float scale, int x_spacing, int y_spacing, bool mono_space) { + const uint8_t font_len = (font_config.width / 8) * font_config.high; + const int anchor = x_off; + + for(char ch, last = '\0'; (ch = *str); str++, last = ch) { + + if ((last == '\r') && (ch == '\n')) { // handle "\r\n" strings + continue; + } + + if ((ch == '\n') || (ch == '\r')) { // handle '\n' or '\r' strings + x_off = anchor; + y_off += fast_roundf(font_config.high * scale) + y_spacing; // newline height == space height + continue; + } + + if ((ch < ' ') || (ch > '~')) { // handle unknown characters + imlib_draw_rectangle(img, + x_off + (fast_roundf(scale * 3) / 2), + y_off + (fast_roundf(scale * 3) / 2), + fast_roundf(font_config.high * scale) - ((fast_roundf(scale * 3) / 2) * 2), + fast_roundf(font_config.width * scale) - ((fast_roundf(scale * 3) / 2) * 2), + c, fast_roundf(scale), false); + continue; + } + + const uint8_t *font = &ascii[(ch - ' ') * font_len]; + + if (!mono_space) { + // Find the first pixel set and offset to that. + bool exit = false; + + for (int x = 0, xx = font_config.width; x < xx; x++) { + for (int y = 0, yy = font_config.high; y < yy; y++) { + if (font[y] & (1 << (font_config.width - 1 - x))) { + x_off -= fast_roundf(x * scale); + exit = true; + break; + } + } + + if (exit) break; + } + } + + imlib_draw_font(img, x_off, y_off, c, scale, font_config.high, font_config.width, font); + + if (mono_space) { + x_off += fast_roundf(font_config.width * scale) + x_spacing; + } else { + // Find the last pixel set and offset to that. + bool exit = false; + + for (int x = font_config.width - 1; x >= 0; x--) { + for (int y = font_config.high - 1; y >= 0; y--) { + if (font[y] & (1 << (font_config.width - 1 - x))) { + x_off += fast_roundf((x + 2) * scale) + x_spacing; + exit = true; + break; + } + } + + if (exit) break; + } + + if (!exit) x_off += fast_roundf(scale * 3); // space char + } + } +} +void *protocol = NULL; +void imlib_draw_string_ttf(image_t *img, int x_off, int y_off, const char *str, int c, float scale, int x_spacing, int y_spacing, bool mono_space) +{ + // 检查字库文件是否有效 + if(font_config.source == FileIn && protocol == NULL){ + font_init(8, 12, ASCII, BuildIn, ascii); + } + if (font_config.index == ASCII) { + imlib_draw_ascii_string(img, x_off, y_off, str, c, scale, x_spacing, y_spacing, mono_space); + } else if (font_config.index == UTF8) { + imlib_draw_utf8_string(img, x_off, y_off, str, c, scale, x_spacing, y_spacing, mono_space); + } +} \ No newline at end of file diff --git a/github_source/minicv2/src/fsort.c b/github_source/minicv2/src/fsort.c new file mode 100644 index 0000000..2c6824f --- /dev/null +++ b/github_source/minicv2/src/fsort.c @@ -0,0 +1,309 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013-2016 Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Fast 9 and 25 bin sort. + * + */ +#include +#include "fsort.h" + +// http://pages.ripco.net/~jgamble/nw.html + +static void cmpswp(int *a, int *b) +{ + if ((*b) < (*a)) { + int tmp = *a; + *a = *b; + *b = tmp; + } +} + +// Network for N=9, using Best Known Arrangement. + +// There are 25 comparators in this network, +// grouped into 9 parallel operations. + +// [[0,1],[3,4],[6,7]] +// [[1,2],[4,5],[7,8]] +// [[0,1],[3,4],[6,7],[2,5]] +// [[0,3],[1,4],[5,8]] +// [[3,6],[4,7],[2,5]] +// [[0,3],[1,4],[5,7],[2,6]] +// [[1,3],[4,6]] +// [[2,4],[5,6]] +// [[2,3]] + +// This is graphed in 17 columns. + +static void fsort9(int *data) +{ + cmpswp(data+0, data+1); + cmpswp(data+3, data+4); + cmpswp(data+6, data+7); + + cmpswp(data+1, data+2); + cmpswp(data+4, data+5); + cmpswp(data+7, data+8); + + cmpswp(data+0, data+1); + cmpswp(data+3, data+4); + cmpswp(data+6, data+7); + cmpswp(data+2, data+5); + + cmpswp(data+0, data+3); + cmpswp(data+1, data+4); + cmpswp(data+5, data+8); + + cmpswp(data+3, data+6); + cmpswp(data+4, data+7); + cmpswp(data+2, data+5); + + cmpswp(data+0, data+3); + cmpswp(data+1, data+4); + cmpswp(data+5, data+7); + cmpswp(data+2, data+6); + + cmpswp(data+1, data+3); + cmpswp(data+4, data+6); + + cmpswp(data+2, data+4); + cmpswp(data+5, data+6); + + cmpswp(data+2, data+3); +} + +// Network for N=25, using Bose-Nelson Algorithm. + +// There are 154 comparators in this network, +// grouped into 27 parallel operations. + +// [[1,2],[4,5],[7,8],[10,11],[13,14],[16,17],[19,20],[21,22],[23,24]] +// [[0,2],[3,5],[6,8],[9,11],[12,14],[15,17],[18,20],[21,23],[22,24]] +// [[0,1],[3,4],[2,5],[6,7],[9,10],[8,11],[12,13],[15,16],[14,17],[18,19],[22,23],[20,24]] +// [[0,3],[1,4],[6,9],[7,10],[5,11],[12,15],[13,16],[18,22],[19,23],[17,24]] +// [[2,4],[1,3],[8,10],[7,9],[0,6],[14,16],[13,15],[18,21],[20,23],[11,24]] +// [[2,3],[8,9],[1,7],[4,10],[14,15],[19,21],[20,22],[16,23]] +// [[2,8],[1,6],[3,9],[5,10],[20,21],[12,19],[15,22],[17,23]] +// [[2,7],[4,9],[12,18],[13,20],[14,21],[16,22],[10,23]] +// [[2,6],[5,9],[4,7],[14,20],[13,18],[17,22],[11,23]] +// [[3,6],[5,8],[14,19],[16,20],[17,21],[0,13],[9,22]] +// [[5,7],[4,6],[14,18],[15,19],[17,20],[0,12],[8,21],[10,22]] +// [[5,6],[15,18],[17,19],[1,14],[7,20],[11,22]] +// [[16,18],[2,15],[1,12],[6,19],[8,20],[11,21]] +// [[17,18],[2,14],[3,16],[7,19],[10,20]] +// [[2,13],[4,17],[5,18],[8,19],[11,20]] +// [[2,12],[5,17],[4,16],[3,13],[9,19]] +// [[5,16],[3,12],[4,14],[10,19]] +// [[5,15],[4,12],[11,19],[9,16],[10,17]] +// [[5,14],[8,15],[11,18],[10,16]] +// [[5,13],[7,14],[11,17]] +// [[5,12],[6,13],[8,14],[11,16]] +// [[6,12],[8,13],[10,14],[11,15]] +// [[7,12],[9,13],[11,14]] +// [[8,12],[11,13]] +// [[9,12]] +// [[10,12]] +// [[11,12]] + +// This is graphed in 89 columns. + +static void fsort25(int *data) +{ + cmpswp(data+1, data+2); + cmpswp(data+4, data+5); + cmpswp(data+7, data+8); + cmpswp(data+10, data+11); + cmpswp(data+13, data+14); + cmpswp(data+16, data+17); + cmpswp(data+19, data+20); + cmpswp(data+21, data+22); + cmpswp(data+23, data+24); + + cmpswp(data+0, data+2); + cmpswp(data+3, data+5); + cmpswp(data+6, data+8); + cmpswp(data+9, data+11); + cmpswp(data+12, data+14); + cmpswp(data+15, data+17); + cmpswp(data+18, data+20); + cmpswp(data+21, data+23); + cmpswp(data+22, data+24); + + cmpswp(data+0, data+1); + cmpswp(data+3, data+4); + cmpswp(data+2, data+5); + cmpswp(data+6, data+7); + cmpswp(data+9, data+10); + cmpswp(data+8, data+11); + cmpswp(data+12, data+13); + cmpswp(data+15, data+16); + cmpswp(data+14, data+17); + cmpswp(data+18, data+19); + cmpswp(data+22, data+23); + cmpswp(data+20, data+24); + + cmpswp(data+0, data+3); + cmpswp(data+1, data+4); + cmpswp(data+6, data+9); + cmpswp(data+7, data+10); + cmpswp(data+5, data+11); + cmpswp(data+12, data+15); + cmpswp(data+13, data+16); + cmpswp(data+18, data+22); + cmpswp(data+19, data+23); + cmpswp(data+17, data+24); + + cmpswp(data+2, data+4); + cmpswp(data+1, data+3); + cmpswp(data+8, data+10); + cmpswp(data+7, data+9); + cmpswp(data+0, data+6); + cmpswp(data+14, data+16); + cmpswp(data+13, data+15); + cmpswp(data+18, data+21); + cmpswp(data+20, data+23); + cmpswp(data+11, data+24); + + cmpswp(data+2, data+3); + cmpswp(data+8, data+9); + cmpswp(data+1, data+7); + cmpswp(data+4, data+10); + cmpswp(data+14, data+15); + cmpswp(data+19, data+21); + cmpswp(data+20, data+22); + cmpswp(data+16, data+23); + + cmpswp(data+2, data+8); + cmpswp(data+1, data+6); + cmpswp(data+3, data+9); + cmpswp(data+5, data+10); + cmpswp(data+20, data+21); + cmpswp(data+12, data+19); + cmpswp(data+15, data+22); + cmpswp(data+17, data+23); + + cmpswp(data+2, data+7); + cmpswp(data+4, data+9); + cmpswp(data+12, data+18); + cmpswp(data+13, data+20); + cmpswp(data+14, data+21); + cmpswp(data+16, data+22); + cmpswp(data+10, data+23); + + cmpswp(data+2, data+6); + cmpswp(data+5, data+9); + cmpswp(data+4, data+7); + cmpswp(data+14, data+20); + cmpswp(data+13, data+18); + cmpswp(data+17, data+22); + cmpswp(data+11, data+23); + + cmpswp(data+3, data+6); + cmpswp(data+5, data+8); + cmpswp(data+14, data+19); + cmpswp(data+16, data+20); + cmpswp(data+17, data+21); + cmpswp(data+0, data+13); + cmpswp(data+9, data+22); + + cmpswp(data+5, data+7); + cmpswp(data+4, data+6); + cmpswp(data+14, data+18); + cmpswp(data+15, data+19); + cmpswp(data+17, data+20); + cmpswp(data+0, data+12); + cmpswp(data+8, data+21); + cmpswp(data+10, data+22); + + cmpswp(data+5, data+6); + cmpswp(data+15, data+18); + cmpswp(data+17, data+19); + cmpswp(data+1, data+14); + cmpswp(data+7, data+20); + cmpswp(data+11, data+22); + + cmpswp(data+16, data+18); + cmpswp(data+2, data+15); + cmpswp(data+1, data+12); + cmpswp(data+6, data+19); + cmpswp(data+8, data+20); + cmpswp(data+11, data+21); + + cmpswp(data+17, data+18); + cmpswp(data+2, data+14); + cmpswp(data+3, data+16); + cmpswp(data+7, data+19); + cmpswp(data+10, data+20); + + cmpswp(data+2, data+13); + cmpswp(data+4, data+17); + cmpswp(data+5, data+18); + cmpswp(data+8, data+19); + cmpswp(data+11, data+20); + + cmpswp(data+2, data+12); + cmpswp(data+5, data+17); + cmpswp(data+4, data+16); + cmpswp(data+3, data+13); + cmpswp(data+9, data+19); + + cmpswp(data+5, data+16); + cmpswp(data+3, data+12); + cmpswp(data+4, data+14); + cmpswp(data+10, data+19); + + cmpswp(data+5, data+15); + cmpswp(data+4, data+12); + cmpswp(data+11, data+19); + cmpswp(data+9, data+16); + cmpswp(data+10, data+17); + + cmpswp(data+5, data+14); + cmpswp(data+8, data+15); + cmpswp(data+11, data+18); + cmpswp(data+10, data+16); + + cmpswp(data+5, data+13); + cmpswp(data+7, data+14); + cmpswp(data+11, data+17); + + cmpswp(data+5, data+12); + cmpswp(data+6, data+13); + cmpswp(data+8, data+14); + cmpswp(data+11, data+16); + + cmpswp(data+6, data+12); + cmpswp(data+8, data+13); + cmpswp(data+10, data+14); + cmpswp(data+11, data+15); + + cmpswp(data+7, data+12); + cmpswp(data+9, data+13); + cmpswp(data+11, data+14); + + cmpswp(data+8, data+12); + cmpswp(data+11, data+13); + + cmpswp(data+9, data+12); + + cmpswp(data+10, data+12); + + cmpswp(data+11, data+12); +} + +static int fsort_compare(const void *a, const void *b) +{ + return (*((int *) a)) - (*((int *) b)); +} + +void fsort(int *data, int n) +{ + switch(n) { + case 1: return; + case 9: fsort9(data); return; + case 25: fsort25(data); return; + default: qsort(data, n, sizeof(int), fsort_compare); + } +} diff --git a/github_source/minicv2/src/gif.c b/github_source/minicv2/src/gif.c new file mode 100644 index 0000000..04c3108 --- /dev/null +++ b/github_source/minicv2/src/gif.c @@ -0,0 +1,136 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * A simple GIF encoder. + */ +#include "imlib.h" + +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + +#include "fb_alloc.h" +#include "ff_wrapper.h" +#define BLOCK_SIZE (126) // (2^7) - 2 // (DO NOT CHANGE!) + +void gif_open(FIL *fp, int width, int height, bool color, bool loop) +{ + file_buffer_on(fp); + + write_data(fp, "GIF89a", 6); + write_word(fp, width); + write_word(fp, height); + write_data(fp, (uint8_t []) {0xF6, 0x00, 0x00}, 3); + + if (color) { + for (int i=0; i<128; i++) { + int red = ((((i & 0x60) >> 5) * 255) + 1.5) / 3; + int green = ((((i & 0x1C) >> 2) * 255) + 3.5) / 7; + int blue = (((i & 0x3) * 255) + 1.5) / 3; + write_data(fp, (uint8_t []) {red, green, blue}, 3); + } + } else { + for (int i=0; i<128; i++) { + int gray = ((i * 255) + 63.5) / 127; + write_data(fp, (uint8_t []) {gray, gray, gray}, 3); + } + } + + if (loop) { + write_data(fp, (uint8_t []) {'!', 0xFF, 0x0B}, 3); + write_data(fp, "NETSCAPE2.0", 11); + write_data(fp, (uint8_t []) {0x03, 0x01, 0x00, 0x00, 0x00}, 5); + } + + file_buffer_off(fp); +} + +void gif_add_frame(FIL *fp, image_t *img, uint16_t delay) +{ + file_buffer_on(fp); + + if (delay) { + write_data(fp, (uint8_t []) {'!', 0xF9, 0x04, 0x04}, 4); + write_word(fp, delay); + write_word(fp, 0); // end + } + + write_byte(fp, 0x2C); + write_long(fp, 0); + write_word(fp, img->w); + write_word(fp, img->h); + write_data(fp, (uint8_t []) {0x00, 0x07}, 2); // 7-bits + + int bytes = img->h * img->w; + int blocks = (bytes + BLOCK_SIZE - 1) / BLOCK_SIZE; + + if (IM_IS_GS(img)) { + for (int y=0; ypixels[(y*BLOCK_SIZE)+x]>>1); + } + } + } else if (IM_IS_RGB565(img)) { + for (int y=0; ypixels)[(y*BLOCK_SIZE)+x]; + int red = COLOR_RGB565_TO_R5(pixel)>>3; + int green = COLOR_RGB565_TO_G6(pixel)>>3; + int blue = COLOR_RGB565_TO_B5(pixel)>>3; + write_byte(fp, (red<<5) | (green<<2) | blue); + } + } + } else if (IM_IS_RGB888(img)) { + for (int y=0; ypixels)[(y*BLOCK_SIZE)+x]; + int red = COLOR_RGB888_TO_R8(pixel)>>3; + int green = COLOR_RGB888_TO_G8(pixel)>>3; + int blue = COLOR_RGB888_TO_B8(pixel)>>3; + write_byte(fp, (red<<5) | (green<<2) | blue); + } + } + } else if (img->is_bayer || img->is_yuv) { + for (int y=0; yis_bayer) { + imlib_debayer_line(0, block_size, y, pixels, PIXFORMAT_RGB565, img); + } else { + imlib_deyuv_line(0, block_size, y, pixels, PIXFORMAT_RGB565, img); + } + for (int x=0; x>=3; g >>=3; b >>=3; + write_byte(fp, (r<<5) | (g<<2) | b); + } + } + } + + write_data(fp, (uint8_t []) {0x01, 0x81, 0x00}, 3); // end code + + file_buffer_off(fp); +} + +void gif_close(FIL *fp) +{ + write_byte(fp, ';'); + file_close(fp); +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO diff --git a/github_source/minicv2/src/haar.c b/github_source/minicv2/src/haar.c new file mode 100644 index 0000000..4d1a85d --- /dev/null +++ b/github_source/minicv2/src/haar.c @@ -0,0 +1,304 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Viola-Jones object detector implementation. + * Based on the work of Francesco Comaschi (f.comaschi@tue.nl) + */ +#include +// #include "py/obj.h" +// #include "py/nlr.h" + +// #include "ff.h" +#include "ff_wrapper.h" +#include "xalloc.h" +#include "imlib.h" +// built-in cascades +#include "cascade.h" + +static int eval_weak_classifier(cascade_t *cascade, point_t pt, int t_idx, int w_idx, int r_idx) +{ + int32_t sumw=0; + mw_image_t *sum = cascade->sum; + + /* The node threshold is multiplied by the standard deviation of the sub window */ + int32_t t = cascade->tree_thresh_array[t_idx] * cascade->std; + + for (int i=0; inum_rectangles_array[t_idx]; i++) { + int x = cascade->rectangles_array[r_idx + (i<<2) + 0]; + int y = cascade->rectangles_array[r_idx + (i<<2) + 1]; + int w = cascade->rectangles_array[r_idx + (i<<2) + 2]; + int h = cascade->rectangles_array[r_idx + (i<<2) + 3]; + // Lookup the feature + sumw += imlib_integral_mw_lookup(sum, pt.x+x, y, w, h) * (cascade->weights_array[w_idx + i]<<12); + } + + if (sumw >= t) { + return cascade->alpha2_array[t_idx]; + } + + return cascade->alpha1_array[t_idx]; +} + +static int run_cascade_classifier(cascade_t* cascade, point_t pt) +{ + int win_w = cascade->window.w; + int win_h = cascade->window.h; + uint32_t n = (win_w * win_h); + uint32_t i_s = imlib_integral_mw_lookup (cascade->sum, pt.x, 0, win_w, win_h); + uint32_t i_sq = imlib_integral_mw_lookup(cascade->ssq, pt.x, 0, win_w, win_h); + uint32_t m = i_s/n; + uint32_t v = i_sq/n-(m*m); + + // Skip homogeneous regions. + if (v<(50*50)) { + return 0; + } + + cascade->std = fast_sqrtf(i_sq*n-(i_s*i_s)); + for (int i=0, w_idx=0, r_idx=0, t_idx=0; in_stages; i++) { + int stage_sum = 0; + for (int j=0; jstages_array[i]; j++, t_idx++) { + // Send the shifted window to a haar filter + stage_sum += eval_weak_classifier(cascade, pt, t_idx, w_idx, r_idx); + w_idx += cascade->num_rectangles_array[t_idx]; + r_idx += cascade->num_rectangles_array[t_idx] * 4; + } + // If the sum is below the stage threshold, no objects were detected + if (stage_sum < (cascade->threshold * cascade->stages_thresh_array[i])) { + return 0; + } + } + return 1; +} + +array_t *imlib_detect_objects(image_t *image, cascade_t *cascade, rectangle_t *roi) +{ + // Integral images + mw_image_t sum; + mw_image_t ssq; + + // Detected objects array + array_t *objects; + + // Allocate the objects array + array_alloc(&objects, xfree); + + // Set cascade image pointers + cascade->img = image; + cascade->sum = ∑ + cascade->ssq = &ssq; + + // Set scanning step. + // Viola and Jones achieved best results using a scaling factor + // of 1.25 and a scanning factor proportional to the current scale. + // Start with a step of 5% of the image width and reduce at each scaling step + cascade->step = (roi->w*50)/1000; + + // Make sure step is less than window height + 1 + if (cascade->step > cascade->window.h) { + cascade->step = cascade->window.h; + } + + // Allocate integral images + imlib_integral_mw_alloc(&sum, roi->w, cascade->window.h+1); + imlib_integral_mw_alloc(&ssq, roi->w, cascade->window.h+1); + + // Iterate over the image pyramid + for(float factor=1.0f; ; factor *= cascade->scale_factor) { + // Set the scaled width and height + int szw = roi->w/factor; + int szh = roi->h/factor; + + // Break if scaled image is smaller than feature size + if (szw < cascade->window.w || szh < cascade->window.h) { + break; + } + + // Set the integral images scale + imlib_integral_mw_scale(roi, &sum, szw, szh); + imlib_integral_mw_scale(roi, &ssq, szw, szh); + + // Compute new scaled integral images + imlib_integral_mw_ss(image, &sum, &ssq, roi); + + // Scale the scanning step + cascade->step = cascade->step/factor; + cascade->step = (cascade->step == 0) ? 1 : cascade->step; + + // Process image at the current scale + // When filter window shifts to borders, some margin need to be kept + int y2 = szh - cascade->window.h; + int x2 = szw - cascade->window.w; + + // Shift the filter window over the image. + for (int y=0; ystep) { + for (int x=0; xstep) { + point_t p = {x, y}; + // If an object is detected, record the coordinates of the filter window + if (run_cascade_classifier(cascade, p) > 0) { + array_push_back(objects, + rectangle_alloc(fast_roundf(x*factor) + roi->x, fast_roundf(y*factor) + roi->y, + fast_roundf(cascade->window.w*factor), fast_roundf(cascade->window.h*factor))); + } + } + + // If not last line, shift integral images + if ((y+cascade->step) < y2) { + imlib_integral_mw_shift_ss(image, &sum, &ssq, roi, cascade->step); + } + } + } + + imlib_integral_mw_free(&ssq); + imlib_integral_mw_free(&sum); + + if (array_length(objects) > 1) { + // Merge objects detected at different scales + objects = rectangle_merge(objects); + } + + return objects; +} + +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) +int imlib_load_cascade_from_file(cascade_t *cascade, const char *path) +{ + int i; + FIL fp; + // FRESULT res=FR_OK; + + file_read_open(&fp, path); + file_buffer_on(&fp); + + /* read detection window size */ + read_data(&fp, &cascade->window, sizeof(cascade->window)); + + /* read num stages */ + read_data(&fp, &cascade->n_stages, sizeof(cascade->n_stages)); + + cascade->stages_array = xalloc (sizeof(*cascade->stages_array) * cascade->n_stages); + cascade->stages_thresh_array = xalloc (sizeof(*cascade->stages_thresh_array) * cascade->n_stages); + if (cascade->stages_array == NULL || + cascade->stages_thresh_array == NULL) { + // res = 20; + goto error; + } + + /* read num features in each stages */ + read_data(&fp, cascade->stages_array, sizeof(uint8_t) * cascade->n_stages); + + /* sum num of features in each stages*/ + for (i=0, cascade->n_features=0; in_stages; i++) { + cascade->n_features += cascade->stages_array[i]; + } + + /* alloc features thresh array, alpha1, alpha 2,rects weights and rects*/ + cascade->tree_thresh_array = xalloc (sizeof(*cascade->tree_thresh_array) * cascade->n_features); + cascade->alpha1_array = xalloc (sizeof(*cascade->alpha1_array) * cascade->n_features); + cascade->alpha2_array = xalloc (sizeof(*cascade->alpha2_array) * cascade->n_features); + cascade->num_rectangles_array = xalloc (sizeof(*cascade->num_rectangles_array) * cascade->n_features); + + if (cascade->tree_thresh_array == NULL || + cascade->alpha1_array == NULL || + cascade->alpha2_array == NULL || + cascade->num_rectangles_array == NULL) { + // res = 20; + goto error; + } + + /* read stages thresholds */ + read_data(&fp, cascade->stages_thresh_array, sizeof(int16_t)*cascade->n_stages); + + /* read features thresholds */ + read_data(&fp, cascade->tree_thresh_array, sizeof(*cascade->tree_thresh_array)*cascade->n_features); + + /* read alpha 1 */ + read_data(&fp, cascade->alpha1_array, sizeof(*cascade->alpha1_array)*cascade->n_features); + + /* read alpha 2 */ + read_data(&fp, cascade->alpha2_array, sizeof(*cascade->alpha2_array)*cascade->n_features); + + /* read num rectangles per feature*/ + read_data(&fp, cascade->num_rectangles_array, sizeof(*cascade->num_rectangles_array)*cascade->n_features); + + /* sum num of recatngles per feature*/ + for (i=0, cascade->n_rectangles=0; in_features; i++) { + cascade->n_rectangles += cascade->num_rectangles_array[i]; + } + + cascade->weights_array = xalloc (sizeof(*cascade->weights_array) * cascade->n_rectangles); + cascade->rectangles_array = xalloc (sizeof(*cascade->rectangles_array) * cascade->n_rectangles * 4); + + if (cascade->weights_array == NULL || + cascade->rectangles_array == NULL) { + // res = 20; + goto error; + } + + /* read rectangles weights */ + read_data(&fp, cascade->weights_array, sizeof(*cascade->weights_array)*cascade->n_rectangles); + + /* read rectangles num rectangles * 4 points */ + read_data(&fp, cascade->rectangles_array, sizeof(*cascade->rectangles_array)*cascade->n_rectangles *4); + +error: + file_buffer_off(&fp); + file_close(&fp); + return 0; +} +#endif //(IMLIB_ENABLE_IMAGE_FILE_IO) + +int imlib_load_cascade(cascade_t *cascade, const char *path) +{ + // built-in cascade + if (strcmp(path, "frontalface") == 0) { + cascade->window.w = frontalface_window_w; + cascade->window.h = frontalface_window_h; + cascade->n_stages = frontalface_n_stages; + cascade->stages_array = (uint8_t *)frontalface_stages_array; + cascade->stages_thresh_array = (int16_t *)frontalface_stages_thresh_array; + cascade->tree_thresh_array = (int16_t *)frontalface_tree_thresh_array; + cascade->alpha1_array = (int16_t *)frontalface_alpha1_array; + cascade->alpha2_array = (int16_t *)frontalface_alpha2_array; + cascade->num_rectangles_array= (int8_t *)frontalface_num_rectangles_array; + cascade->weights_array = (int8_t *)frontalface_weights_array; + cascade->rectangles_array = (int8_t *)frontalface_rectangles_array; + } else if (strcmp(path, "eye") == 0) { + cascade->window.w = eye_window_w; + cascade->window.h = eye_window_h; + cascade->n_stages = eye_n_stages; + cascade->stages_array = (uint8_t *)eye_stages_array; + cascade->stages_thresh_array = (int16_t *)eye_stages_thresh_array; + cascade->tree_thresh_array = (int16_t *)eye_tree_thresh_array; + cascade->alpha1_array = (int16_t *)eye_alpha1_array; + cascade->alpha2_array = (int16_t *)eye_alpha2_array; + cascade->num_rectangles_array= (int8_t *)eye_num_rectangles_array; + cascade->weights_array = (int8_t *)eye_weights_array; + cascade->rectangles_array = (int8_t *)eye_rectangles_array; + } else { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + // xml cascade + return imlib_load_cascade_from_file(cascade, path); + #else + return -1; + #endif + } + + int i; + // sum the number of features in all stages + for (i=0, cascade->n_features=0; in_stages; i++) { + cascade->n_features += cascade->stages_array[i]; + } + + // sum the number of recatngles in all features + for (i=0, cascade->n_rectangles=0; in_features; i++) { + cascade->n_rectangles += cascade->num_rectangles_array[i]; + } + // return FR_OK; + return 0; +} diff --git a/github_source/minicv2/src/hog.c b/github_source/minicv2/src/hog.c new file mode 100644 index 0000000..c74367a --- /dev/null +++ b/github_source/minicv2/src/hog.c @@ -0,0 +1,136 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * HoG. + * See Histograms of Oriented Gradients (Navneet Dalal and Bill Triggs) + */ +#include +#include +#include +#include "imlib.h" +#include "fb_alloc.h" +#include "xalloc.h" + +#ifdef IMLIB_ENABLE_HOG +#define N_BINS (9) +typedef struct bin { + int d; + int m; +} bin_t; + +int bin_array_comp(const void *obj0, const void *obj1) +{ + const bin_t *b0 = obj0; + const bin_t *b1 = obj1; + if (b0->m < b1->m) + return -1; + if (b0->m > b1->m) + return 1; + + return 0; +} + +void imlib_find_hog(image_t *src, rectangle_t *roi, int cell_size) +{ + int s = src->w; + int w = roi->x+roi->w-1; + int h = roi->y+roi->h-1; + + int block_size = cell_size * 2; + int x_cells = (roi->w/cell_size); + int y_cells = (roi->h/cell_size); + + // TODO: Assert row->w/h >= cell_size *2; + float *hog = fb_alloc0(x_cells * y_cells * N_BINS * sizeof*hog, FB_ALLOC_NO_HINT); + + //2. Finding Image Gradients + for (int y=roi->y, hog_index=0; yx; x 0 && (y+cy) < h && (x+cx) > 0 && (x+cx) < w) { + // Find horizontal/vertical direction + int vx = src->data[(y+cy+0)*s+(x+cx+1)] - src->data[(y+cy-0)*s+(x+cx-1)]; + int vy = src->data[(y+cy+1)*s+(x+cx+0)] - src->data[(y+cy-1)*s+(x+cx-0)]; + // Find magnitude + float m = fast_sqrtf(vx*vx + vy*vy); + if(((int) m) > 1) { + k += m*m; + // Find and quantize gradient degree + // TODO atan2f is swapped for visualization + int t = ((int) fast_fabsf((atan2f(vx, vy)*180.0f/M_PI))) / 20; + t = (t == 9)? 0 : t; + + // hog[((cy/cell_size) * x_cells + (cx/cell_size)) * N_BINS + t] += m; + hog[hog_index + (((cy/8)*2+(cx/8)) * N_BINS) + t] += m; + } + } + } + } + + // Normalize the last block + k = sqrtf(k); + for (int i=hog_index; i<(hog_index+(N_BINS*4)); i++) { + hog[i] = hog[i]/k; + } + + hog_index += (N_BINS*4); + } + } + + memset(src->pixels, 0, src->w*src->h); + + array_t *gds; + bin_t bins[9]; + array_alloc(&gds, NULL); + + for (int i=0; i 255) { + m = 255; + } else if (m < 0) { + m = 0; + } + bin_t *bin = array_at(gds, (i%N_BINS)); + bin->m = m; + bin->d = ((i%N_BINS)*20); + } + + array_sort(gds, bin_array_comp); + + int x1 = (x+bx) * cell_size + l; + int y1 = (y+by) * cell_size + l; + for (int i=0; id]; + int y2 = l * sin_table[bin->d]; + imlib_draw_line(src, (x1 - x2), (y1 + y2), (x1 + x2), (y1 - y2), bin->m, 1); + } + + hog_index += N_BINS; + } + } + } + } + + xfree(gds); + fb_free(hog); +} +#endif // IMLIB_ENABLE_HOG diff --git a/github_source/minicv2/src/hough.c b/github_source/minicv2/src/hough.c new file mode 100644 index 0000000..efc703f --- /dev/null +++ b/github_source/minicv2/src/hough.c @@ -0,0 +1,954 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Hough Transform feature extraction. + */ +#include "imlib.h" + +#ifdef IMLIB_ENABLE_FIND_LINES +void imlib_find_lines(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, + uint32_t threshold, unsigned int theta_margin, unsigned int rho_margin) +{ + int r_diag_len, r_diag_len_div, theta_size, r_size, hough_divide = 1; // divides theta and rho accumulators + + for (;;) { // shrink to fit... + r_diag_len = fast_roundf(fast_sqrtf((roi->w * roi->w) + (roi->h * roi->h))); + r_diag_len_div = (r_diag_len + hough_divide - 1) / hough_divide; + theta_size = 1 + ((180 + hough_divide - 1) / hough_divide) + 1; // left & right padding + r_size = (r_diag_len_div * 2) + 1; // -r_diag_len to +r_diag_len + if ((sizeof(uint32_t) * theta_size * r_size) <= fb_avail()) break; + hough_divide = hough_divide << 1; // powers of 2... + if (hough_divide > 4) fb_alloc_fail(); // support 1, 2, 4 + } + + uint32_t *acc = fb_alloc0(sizeof(uint32_t) * theta_size * r_size, FB_ALLOC_NO_HINT); + + switch (ptr->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = roi->y + 1, yy = roi->y + roi->h - 1; y < yy; y += y_stride) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride) + 1, xx = roi->x + roi->w - 1; x < xx; x += x_stride) { + int pixel; // Sobel Algorithm Below + int x_acc = 0; + int y_acc = 0; + + row_ptr -= ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + row_ptr += ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + row_ptr += ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + row_ptr -= ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + int mag = (abs(x_acc) + abs(y_acc)) / 2; + if (mag < 126) + continue; + + int theta = fast_roundf((x_acc ? fast_atan2f(y_acc, x_acc) : 1.570796f) * 57.295780) % 180; // * (180 / PI) + if (theta < 0) theta += 180; + int rho = (fast_roundf(((x - roi->x) * cos_table[theta]) + + ((y - roi->y) * sin_table[theta])) / hough_divide) + r_diag_len_div; + int acc_index = (rho * theta_size) + ((theta / hough_divide) + 1); // add offset + acc[acc_index] += mag; + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = roi->y + 1, yy = roi->y + roi->h - 1; y < yy; y += y_stride) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride) + 1, xx = roi->x + roi->w - 1; x < xx; x += x_stride) { + int pixel; // Sobel Algorithm Below + int x_acc = 0; + int y_acc = 0; + + row_ptr -= ptr->w; + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x - 1); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x + 1); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + row_ptr += ptr->w; + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x - 1); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x + 1); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + row_ptr += ptr->w; + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x - 1); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x + 1); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + row_ptr -= ptr->w; + + int mag = (abs(x_acc) + abs(y_acc)) / 2; + if (mag < 126) + continue; + + int theta = fast_roundf((x_acc ? fast_atan2f(y_acc, x_acc) : 1.570796f) * 57.295780) % 180; // * (180 / PI) + if (theta < 0) theta += 180; + int rho = (fast_roundf(((x - roi->x) * cos_table[theta]) + + ((y - roi->y) * sin_table[theta])) / hough_divide) + r_diag_len_div; + int acc_index = (rho * theta_size) + ((theta / hough_divide) + 1); // add offset + acc[acc_index] += mag; + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = roi->y + 1, yy = roi->y + roi->h - 1; y < yy; y += y_stride) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride) + 1, xx = roi->x + roi->w - 1; x < xx; x += x_stride) { + int pixel; // Sobel Algorithm Below + int x_acc = 0; + int y_acc = 0; + + row_ptr -= ptr->w; + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + row_ptr += ptr->w; + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + row_ptr += ptr->w; + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + row_ptr -= ptr->w; + + int mag = (abs(x_acc) + abs(y_acc)) / 2; + if (mag < 126) + continue; + + int theta = fast_roundf((x_acc ? fast_atan2f(y_acc, x_acc) : 1.570796f) * 57.295780) % 180; // * (180 / PI) + if (theta < 0) theta += 180; + int rho = (fast_roundf(((x - roi->x) * cos_table[theta]) + + ((y - roi->y) * sin_table[theta])) / hough_divide) + r_diag_len_div; + int acc_index = (rho * theta_size) + ((theta / hough_divide) + 1); // add offset + acc[acc_index] += mag; + } + } + break; + } + case PIXFORMAT_RGB888: { + for (int y = roi->y + 1, yy = roi->y + roi->h - 1; y < yy; y += y_stride) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride) + 1, xx = roi->x + roi->w - 1; x < xx; x += x_stride) { + int pixel; // Sobel Algorithm Below + int x_acc = 0; + int y_acc = 0; + + row_ptr -= ptr->w; + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + row_ptr += ptr->w; + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + row_ptr += ptr->w; + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + row_ptr -= ptr->w; + + int mag = (abs(x_acc) + abs(y_acc)) / 2; + if (mag < 126) + continue; + + int theta = (int)fast_roundf((x_acc ? fast_atan2f(y_acc, x_acc) : 1.570796f) * 57.295780) % 180; // * (180 / PI) + if (theta < 0) theta += 180; + int rho = (fast_roundf(((x - roi->x) * cos_table[theta]) + + ((y - roi->y) * sin_table[theta])) / hough_divide) + r_diag_len_div; + int acc_index = (rho * theta_size) + ((theta / hough_divide) + 1); // add offset + acc[acc_index] += mag; + } + } + break; + } + default: { + break; + } + } + + imlib_list_init(out, sizeof(find_lines_list_lnk_data_t)); + + for (int y = 1, yy = r_size - 1; y < yy; y++) { + uint32_t *row_ptr = acc + (theta_size * y); + + for (int x = 1, xx = theta_size - 1; x < xx; x++) { + if ((row_ptr[x] >= threshold) + && (row_ptr[x] >= row_ptr[x-theta_size-1]) + && (row_ptr[x] >= row_ptr[x-theta_size]) + && (row_ptr[x] >= row_ptr[x-theta_size+1]) + && (row_ptr[x] >= row_ptr[x-1]) + && (row_ptr[x] >= row_ptr[x+1]) + && (row_ptr[x] >= row_ptr[x+theta_size-1]) + && (row_ptr[x] >= row_ptr[x+theta_size]) + && (row_ptr[x] >= row_ptr[x+theta_size+1])) { + + find_lines_list_lnk_data_t lnk_line; + memset(&lnk_line, 0, sizeof(find_lines_list_lnk_data_t)); + + lnk_line.magnitude = row_ptr[x]; + lnk_line.theta = (x - 1) * hough_divide; // remove offset + lnk_line.rho = (y - r_diag_len_div) * hough_divide; + + list_push_back(out, &lnk_line); + } + } + } + + fb_free(acc); // acc + + for (;;) { // Merge overlapping. + bool merge_occured = false; + + list_t out_temp; + imlib_list_init(&out_temp, sizeof(find_lines_list_lnk_data_t)); + + while (list_size(out)) { + find_lines_list_lnk_data_t lnk_line; + list_pop_front(out, &lnk_line); + + for (size_t k = 0, l = list_size(out); k < l; k++) { + find_lines_list_lnk_data_t tmp_line; + list_pop_front(out, &tmp_line); + + int theta_0_temp = lnk_line.theta; + int theta_1_temp = tmp_line.theta; + int rho_0_temp = lnk_line.rho; + int rho_1_temp = tmp_line.rho; + + if (rho_0_temp < 0) { + rho_0_temp = -rho_0_temp; + theta_0_temp += 180; + } + + if (rho_1_temp < 0) { + rho_1_temp = -rho_1_temp; + theta_1_temp += 180; + } + + int theta_diff = abs(theta_0_temp - theta_1_temp); + int theta_diff_2 = (theta_diff >= 180) ? (360 - theta_diff) : theta_diff; + + bool theta_merge = theta_diff_2 < theta_margin; + bool rho_merge = abs(rho_0_temp - rho_1_temp) < rho_margin; + + if (theta_merge && rho_merge) { + uint32_t magnitude = lnk_line.magnitude + tmp_line.magnitude; + float sin_mean = ((sin_table[theta_0_temp] * lnk_line.magnitude) + + (sin_table[theta_1_temp] * tmp_line.magnitude)) / magnitude; + float cos_mean = ((cos_table[theta_0_temp] * lnk_line.magnitude) + + (cos_table[theta_1_temp] * tmp_line.magnitude)) / magnitude; + + lnk_line.theta = fast_roundf(fast_atan2f(sin_mean, cos_mean) * 57.295780) % 360; // * (180 / PI) + if (lnk_line.theta < 0) lnk_line.theta += 360; + lnk_line.rho = fast_roundf(((rho_0_temp * lnk_line.magnitude) + (rho_1_temp * tmp_line.magnitude)) / magnitude); + lnk_line.magnitude = magnitude / 2; + + if (lnk_line.theta >= 180) { + lnk_line.rho = -lnk_line.rho; + lnk_line.theta -= 180; + } + + merge_occured = true; + } else { + list_push_back(out, &tmp_line); + } + } + + list_push_back(&out_temp, &lnk_line); + } + + list_copy(out, &out_temp); + + if (!merge_occured) { + break; + } + } + + for (size_t i = 0, j = list_size(out); i < j; i++) { + find_lines_list_lnk_data_t lnk_line; + list_pop_front(out, &lnk_line); + + if ((45 <= lnk_line.theta) && (lnk_line.theta < 135)) { + // y = (r - x cos(t)) / sin(t) + lnk_line.line.x1 = 0; + lnk_line.line.y1 = fast_roundf((lnk_line.rho - (lnk_line.line.x1 * cos_table[lnk_line.theta])) / sin_table[lnk_line.theta]); + lnk_line.line.x2 = roi->w - 1; + lnk_line.line.y2 = fast_roundf((lnk_line.rho - (lnk_line.line.x2 * cos_table[lnk_line.theta])) / sin_table[lnk_line.theta]); + } else { + // x = (r - y sin(t)) / cos(t); + lnk_line.line.y1 = 0; + lnk_line.line.x1 = fast_roundf((lnk_line.rho - (lnk_line.line.y1 * sin_table[lnk_line.theta])) / cos_table[lnk_line.theta]); + lnk_line.line.y2 = roi->h - 1; + lnk_line.line.x2 = fast_roundf((lnk_line.rho - (lnk_line.line.y2 * sin_table[lnk_line.theta])) / cos_table[lnk_line.theta]); + } + + if(lb_clip_line(&lnk_line.line, 0, 0, roi->w, roi->h)) { + lnk_line.line.x1 += roi->x; + lnk_line.line.y1 += roi->y; + lnk_line.line.x2 += roi->x; + lnk_line.line.y2 += roi->y; + + // Move rho too. + lnk_line.rho += fast_roundf((roi->x * cos_table[lnk_line.theta]) + (roi->y * sin_table[lnk_line.theta])); + list_push_back(out, &lnk_line); + } + } +} +#endif //IMLIB_ENABLE_FIND_LINES + +#ifdef IMLIB_ENABLE_FIND_LINE_SEGMENTS +// Note this function is not used anymore, see lsd.c +void imlib_find_line_segments(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, + uint32_t threshold, unsigned int theta_margin, unsigned int rho_margin, + uint32_t segment_threshold) +{ + const unsigned int max_theta_diff = 15; + const unsigned int max_gap_pixels = 5; + + list_t temp_out; + imlib_find_lines(&temp_out, ptr, roi, x_stride, y_stride, threshold, theta_margin, rho_margin); + imlib_list_init(out, sizeof(find_lines_list_lnk_data_t)); + + const int r_diag_len = fast_roundf(fast_sqrtf((roi->w * roi->w) + (roi->h * roi->h))) * 2; + int *theta_buffer = fb_alloc(sizeof(int) * r_diag_len, FB_ALLOC_NO_HINT); + uint32_t *mag_buffer = fb_alloc(sizeof(uint32_t) * r_diag_len, FB_ALLOC_NO_HINT); + point_t *point_buffer = fb_alloc(sizeof(point_t) * r_diag_len, FB_ALLOC_NO_HINT); + + for (size_t i = 0; list_size(&temp_out); i++) { + find_lines_list_lnk_data_t lnk_data; + list_pop_front(&temp_out, &lnk_data); + + list_t line_out; + imlib_list_init(&line_out, sizeof(find_lines_list_lnk_data_t)); + + for (int k = -2; k <= 2; k += 2) { + line_t l; + + if (abs(lnk_data.line.x2 - lnk_data.line.x1) >= abs(lnk_data.line.y2 - lnk_data.line.y1)) { + // the line is more horizontal than vertical + l.x1 = lnk_data.line.x1; + l.y1 = lnk_data.line.y1 + k; + l.x2 = lnk_data.line.x2; + l.y2 = lnk_data.line.y2 + k; + } else { + // the line is more vertical than horizontal + l.x1 = lnk_data.line.x1 + k; + l.y1 = lnk_data.line.y1; + l.x2 = lnk_data.line.x2 + k; + l.y2 = lnk_data.line.y2; + } + + if(!lb_clip_line(&l, 0, 0, ptr->w, ptr->h)) { + continue; + } + + find_lines_list_lnk_data_t tmp_line; + tmp_line.magnitude = lnk_data.magnitude; + tmp_line.theta = lnk_data.theta; + tmp_line.rho = lnk_data.rho; + + size_t index = trace_line(ptr, &l, theta_buffer, mag_buffer, point_buffer); + unsigned int max_gap = 0; + + for (size_t j = 0; j < index; j++) { + int theta_diff = abs(tmp_line.theta - theta_buffer[j]); + int theta_diff_2 = (theta_diff >= 90) ? (180 - theta_diff) : theta_diff; + bool ok = (mag_buffer[j] >= segment_threshold) && (theta_diff_2 <= max_theta_diff); + + if (!max_gap) { + if (ok) { + max_gap = max_gap_pixels + 1; // (start) auto connect segments max_gap_pixels px apart... + tmp_line.line.x1 = point_buffer[j].x; + tmp_line.line.y1 = point_buffer[j].y; + tmp_line.line.x2 = point_buffer[j].x; + tmp_line.line.y2 = point_buffer[j].y; + } + } else { + if (ok) { + max_gap = max_gap_pixels + 1; // (reset) auto connect segments max_gap_pixels px apart... + tmp_line.line.x2 = point_buffer[j].x; + tmp_line.line.y2 = point_buffer[j].y; + } else if (!--max_gap) { + list_push_back(&line_out, &tmp_line); + } + } + } + + if (max_gap) { + list_push_back(&line_out, &tmp_line); + } + } + + merge_alot(&line_out, max_gap_pixels + 1, max_theta_diff); + + while (list_size(&line_out)) { + find_lines_list_lnk_data_t lnk_line; + list_pop_front(&line_out, &lnk_line); + list_push_back(out, &lnk_line); + } + } + + merge_alot(out, max_gap_pixels + 1, max_theta_diff); + + fb_free(point_buffer); // point_buffer + fb_free(mag_buffer); // mag_buffer + fb_free(theta_buffer); // theta_buffer +} +#endif //IMLIB_ENABLE_FIND_LINE_SEGMENTS + +#ifdef IMLIB_ENABLE_FIND_CIRCLES +void imlib_find_circles(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, + uint32_t threshold, unsigned int x_margin, unsigned int y_margin, unsigned int r_margin, + unsigned int r_min, unsigned int r_max, unsigned int r_step) +{ + uint16_t *theta_acc = fb_alloc0(sizeof(uint16_t) * roi->w * roi->h, FB_ALLOC_NO_HINT); + uint16_t *magnitude_acc = fb_alloc0(sizeof(uint16_t) * roi->w * roi->h, FB_ALLOC_NO_HINT); + + switch (ptr->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = roi->y + 1, yy = roi->y + roi->h - 1; y < yy; y += y_stride) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride) + 1, xx = roi->x + roi->w - 1; x < xx; x += x_stride) { + int pixel; // Sobel Algorithm Below + int x_acc = 0; + int y_acc = 0; + + row_ptr -= ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + row_ptr += ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + row_ptr += ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + row_ptr -= ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + int theta = fast_roundf((x_acc ? fast_atan2f(y_acc, x_acc) : 1.570796f) * 57.295780) % 360; // * (180 / PI) + if (theta < 0) theta += 360; + int magnitude = fast_roundf(fast_sqrtf((x_acc * x_acc) + (y_acc * y_acc))); + int index = (roi->w * (y - roi->y)) + (x - roi->x); + + theta_acc[index] = theta; + magnitude_acc[index] = magnitude; + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = roi->y + 1, yy = roi->y + roi->h - 1; y < yy; y += y_stride) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride) + 1, xx = roi->x + roi->w - 1; x < xx; x += x_stride) { + int pixel; // Sobel Algorithm Below + int x_acc = 0; + int y_acc = 0; + + row_ptr -= ptr->w; + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x - 1); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x + 1); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + row_ptr += ptr->w; + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x - 1); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x + 1); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + row_ptr += ptr->w; + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x - 1); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x + 1); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + row_ptr -= ptr->w; + + int theta = fast_roundf((x_acc ? fast_atan2f(y_acc, x_acc) : 1.570796f) * 57.295780) % 360; // * (180 / PI) + if (theta < 0) theta += 360; + int magnitude = fast_roundf(fast_sqrtf((x_acc * x_acc) + (y_acc * y_acc))); + int index = (roi->w * (y - roi->y)) + (x - roi->x); + + theta_acc[index] = theta; + magnitude_acc[index] = magnitude; + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = roi->y + 1, yy = roi->y + roi->h - 1; y < yy; y += y_stride) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride) + 1, xx = roi->x + roi->w - 1; x < xx; x += x_stride) { + int pixel; // Sobel Algorithm Below + int x_acc = 0; + int y_acc = 0; + + row_ptr -= ptr->w; + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + row_ptr += ptr->w; + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + row_ptr += ptr->w; + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + row_ptr -= ptr->w; + + int theta = fast_roundf((x_acc ? fast_atan2f(y_acc, x_acc) : 1.570796f) * 57.295780) % 360; // * (180 / PI) + if (theta < 0) theta += 360; + int magnitude = fast_roundf(fast_sqrtf((x_acc * x_acc) + (y_acc * y_acc))); + int index = (roi->w * (y - roi->y)) + (x - roi->x); + + theta_acc[index] = theta; + magnitude_acc[index] = magnitude; + } + } + break; + } + case PIXFORMAT_RGB888: { + for (int y = roi->y + 1, yy = roi->y + roi->h - 1; y < yy; y += y_stride) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride) + 1, xx = roi->x + roi->w - 1; x < xx; x += x_stride) { + int pixel; // Sobel Algorithm Below + int x_acc = 0; + int y_acc = 0; + + row_ptr -= ptr->w; + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + row_ptr += ptr->w; + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + row_ptr += ptr->w; + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x - 1)); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x + 1)); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + row_ptr -= ptr->w; + + int theta = (int)fast_roundf((x_acc ? fast_atan2f(y_acc, x_acc) : 1.570796f) * 57.295780) % 360; // * (180 / PI) + if (theta < 0) theta += 360; + int magnitude = fast_roundf(fast_sqrtf((x_acc * x_acc) + (y_acc * y_acc))); + int index = (roi->w * (y - roi->y)) + (x - roi->x); + + theta_acc[index] = theta; + magnitude_acc[index] = magnitude; + } + } + break; + } + default: { + break; + } + } + + // Theta Direction (% 180) + // + // 0,0 X_MAX + // + // 090 + // 000 000 + // 090 + // + // Y_MAX + + // Theta Direction (% 360) + // + // 0,0 X_MAX + // + // 090 + // 000 180 + // 270 + // + // Y_MAX + + imlib_list_init(out, sizeof(find_circles_list_lnk_data_t)); + + for (int r = r_min, rr = r_max; r < rr; r += r_step) { // ignore r = 0/1 + int a_size, b_size, hough_divide = 1; // divides a and b accumulators + int hough_shift = 0; + int w_size = roi->w - (2 * r); + int h_size = roi->h - (2 * r); + + for (;;) { // shrink to fit... + a_size = 1 + ((w_size + hough_divide - 1) / hough_divide) + 1; // left & right padding + b_size = 1 + ((h_size + hough_divide - 1) / hough_divide) + 1; // top & bottom padding + if ((sizeof(uint32_t) * a_size * b_size) <= fb_avail()) break; + hough_divide = hough_divide << 1; // powers of 2... + hough_shift++; + if (hough_divide > 4) fb_alloc_fail(); // support 1, 2, 4 + } + + uint32_t *acc = fb_alloc0(sizeof(uint32_t) * a_size * b_size, FB_ALLOC_NO_HINT); + int16_t *rcos = fb_alloc(sizeof(int16_t)*360, FB_ALLOC_NO_HINT); + int16_t *rsin = fb_alloc(sizeof(int16_t)*360, FB_ALLOC_NO_HINT); + for (int i=0; i<360; i++) + { + rcos[i] = (int16_t)roundf(r * cos_table[i]); + rsin[i] = (int16_t)roundf(r * sin_table[i]); + } + + for (int y = 0, yy = roi->h; y < yy; y++) { + for (int x = 0, xx = roi->w; x < xx; x++) { + int index = (roi->w * y) + x; + int theta = theta_acc[index]; + int magnitude = magnitude_acc[index]; + if (!magnitude) continue; + + // We have to do the below step twice because the gradient may be pointing inside or outside the circle. + // Only graidents pointing inside of the circle sum up to produce a large magnitude. + for (;;) { // Hi to lo edge direction + int a = x + rcos[theta] - r; + if ((a < 0) || (w_size <= a)) break; // circle doesn't fit in the window + int b = y + rsin[theta] - r; + if ((b < 0) || (h_size <= b)) break; // circle doesn't fit in the window + int acc_index = (((b >> hough_shift) + 1) * a_size) + ((a >> hough_shift) + 1); // add offset + + int acc_value = acc[acc_index] += magnitude; + acc[acc_index] = acc_value; + break; + } + + for (;;) { // Lo to hi edge direction + int a = x - rcos[theta] - r; + if ((a < 0) || (w_size <= a)) break; // circle doesn't fit in the window + int b = y - rsin[theta] - r; + if ((b < 0) || (h_size <= b)) break; // circle doesn't fit in the window + int acc_index = (((b >> hough_shift) + 1) * a_size) + ((a >> hough_shift) + 1); // add offset + + int acc_value = acc[acc_index] += magnitude; + acc[acc_index] = acc_value; + break; + } + } + } + + for (int y = 1, yy = b_size - 1; y < yy; y++) { + uint32_t *row_ptr = acc + (a_size * y); + uint32_t val; + for (int x = 1, xx = a_size - 1; x < xx; x++) { + val = row_ptr[x]; + if ((val >= threshold) + && (val >= row_ptr[x-a_size-1]) + && (val >= row_ptr[x-a_size]) + && (val >= row_ptr[x-a_size+1]) + && (val >= row_ptr[x-1]) + && (val >= row_ptr[x+1]) + && (val >= row_ptr[x+a_size-1]) + && (val >= row_ptr[x+a_size]) + && (val >= row_ptr[x+a_size+1])) { + + find_circles_list_lnk_data_t lnk_data; + lnk_data.magnitude = val; + lnk_data.p.x = ((x - 1) << hough_shift) + r + roi->x; // remove offset + lnk_data.p.y = ((y - 1) << hough_shift) + r + roi->y; // remove offset + lnk_data.r = r; + + list_push_back(out, &lnk_data); + if (val > row_ptr[x+1]) + x++; // can skip the next pixel + } + } + } + + fb_free(rsin); // rsin + fb_free(rcos); // rcos + fb_free(acc); // acc + } + + fb_free(magnitude_acc); // magnitude_acc + fb_free(theta_acc); // theta_acc + + for (;;) { // Merge overlapping. + bool merge_occured = false; + + list_t out_temp; + imlib_list_init(&out_temp, sizeof(find_circles_list_lnk_data_t)); + + while (list_size(out)) { + find_circles_list_lnk_data_t lnk_data; + list_pop_front(out, &lnk_data); + + for (size_t k = 0, l = list_size(out); k < l; k++) { + find_circles_list_lnk_data_t tmp_data; + list_pop_front(out, &tmp_data); + + bool x_diff_ok = abs(lnk_data.p.x - tmp_data.p.x) < x_margin; + bool y_diff_ok = abs(lnk_data.p.y - tmp_data.p.y) < y_margin; + bool r_diff_ok = abs(lnk_data.r - tmp_data.r) < r_margin; + + if (x_diff_ok && y_diff_ok && r_diff_ok) { + uint32_t magnitude = lnk_data.magnitude + tmp_data.magnitude; + lnk_data.p.x = ((lnk_data.p.x * lnk_data.magnitude) + (tmp_data.p.x * tmp_data.magnitude)) / magnitude; + lnk_data.p.y = ((lnk_data.p.y * lnk_data.magnitude) + (tmp_data.p.y * tmp_data.magnitude)) / magnitude; + lnk_data.r = ((lnk_data.r * lnk_data.magnitude) + (tmp_data.r * tmp_data.magnitude)) / magnitude; + lnk_data.magnitude = magnitude / 2; + merge_occured = true; + } else { + list_push_back(out, &tmp_data); + } + } + + list_push_back(&out_temp, &lnk_data); + } + + list_copy(out, &out_temp); + + if (!merge_occured) { + break; + } + } +} +#endif //IMLIB_ENABLE_FIND_CIRCLES diff --git a/github_source/minicv2/src/imlib.c b/github_source/minicv2/src/imlib.c new file mode 100644 index 0000000..8ea644c --- /dev/null +++ b/github_source/minicv2/src/imlib.c @@ -0,0 +1,1803 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Image library. + */ +#include +// #include "py/obj.h" +// #include "py/runtime.h" + +#include "font.h" +#include "array.h" +// #include "ff_wrapper.h" +#include "imlib.h" +#include "common.h" +#include "omv_boardconfig.h" + + +void imlib_init_all() +{ + #if (OMV_HARDWARE_JPEG == 1) + imlib_jpeg_compress_init(); + #endif + fb_alloc_init0(); + fmath_init(); +} + +void imlib_deinit_all() +{ + #ifdef IMLIB_ENABLE_DMA2D + imlib_draw_row_deinit_all(); + #endif + #if (OMV_HARDWARE_JPEG == 1) + imlib_jpeg_compress_deinit(); + #endif + fb_alloc_close0(); +} + +///////////////// +// Point Stuff // +///////////////// + +void point_init(point_t *ptr, int x, int y) +{ + ptr->x = x; + ptr->y = y; +} + +void point_copy(point_t *dst, point_t *src) +{ + memcpy(dst, src, sizeof(point_t)); +} + +bool point_equal_fast(point_t *ptr0, point_t *ptr1) +{ + return !memcmp(ptr0, ptr1, sizeof(point_t)); +} + +int point_quadrance(point_t *ptr0, point_t *ptr1) +{ + int delta_x = ptr0->x - ptr1->x; + int delta_y = ptr0->y - ptr1->y; + return (delta_x * delta_x) + (delta_y * delta_y); +} + +int point_quadrance_i(point_t *ptr0, point_t *ptr1) +{ + int delta_m = point_quadrance(ptr0, ptr1); + return (int)fast_sqrtf((float)delta_m); +} + +void point_rotate(int x, int y, float r, int center_x, int center_y, int16_t *new_x, int16_t *new_y) +{ + x -= center_x; + y -= center_y; + *new_x = (x * cosf(r)) - (y * sinf(r)) + center_x; + *new_y = (x * sinf(r)) + (y * cosf(r)) + center_y; +} + +void point_min_area_rectangle(point_t *corners, point_t *new_corners, int corners_len) // Corners need to be sorted! +{ + int i_min = 0; + int i_min_area = INT_MAX; + int i_x0 = 0, i_y0 = 0; + int i_x1 = 0, i_y1 = 0; + int i_x2 = 0, i_y2 = 0; + int i_x3 = 0, i_y3 = 0; + float i_r = 0; + + // This algorithm aligns the 4 edges produced by the 4 corners to the x axis and then computes the + // min area rect for each alignment. The smallest rect is choosen and then re-rotated and returned. + for (int i = 0; i < corners_len; i++) { + int16_t x0 = corners[i].x, y0 = corners[i].y; + int x_diff = corners[(i+1)%corners_len].x - corners[i].x; + int y_diff = corners[(i+1)%corners_len].y - corners[i].y; + float r = -fast_atan2f(y_diff, x_diff); + + int16_t x1[corners_len-1]; + int16_t y1[corners_len-1]; + for (int j = 0, jj = corners_len - 1; j < jj; j++) { + point_rotate(corners[(i+j+1)%corners_len].x, corners[(i+j+1)%corners_len].y, r, x0, y0, x1 + j, y1 + j); + } + + int minx = x0; + int maxx = x0; + int miny = y0; + int maxy = y0; + for (int j = 0, jj = corners_len - 1; j < jj; j++) { + minx = IM_MIN(minx, x1[j]); + maxx = IM_MAX(maxx, x1[j]); + miny = IM_MIN(miny, y1[j]); + maxy = IM_MAX(maxy, y1[j]); + } + + int area = (maxx - minx + 1) * (maxy - miny + 1); + if (area < i_min_area) { + i_min = i; + i_min_area = area; + i_x0 = minx, i_y0 = miny; + i_x1 = maxx, i_y1 = miny; + i_x2 = maxx, i_y2 = maxy; + i_x3 = minx, i_y3 = maxy; + i_r = r; + } + } + + point_rotate(i_x0, i_y0, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[0].x, &new_corners[0].y); + point_rotate(i_x1, i_y1, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[1].x, &new_corners[1].y); + point_rotate(i_x2, i_y2, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[2].x, &new_corners[2].y); + point_rotate(i_x3, i_y3, -i_r, corners[i_min].x, corners[i_min].y, &new_corners[3].x, &new_corners[3].y); +} + +//////////////// +// Line Stuff // +//////////////// + +// http://www.skytopia.com/project/articles/compsci/clipping.html +bool lb_clip_line(line_t *l, int x, int y, int w, int h) // line is drawn if this returns true +{ + int xdelta = l->x2 - l->x1, ydelta = l->y2 - l->y1, p[4], q[4]; + float umin = 0, umax = 1; + + p[0] = -(xdelta); + p[1] = +(xdelta); + p[2] = -(ydelta); + p[3] = +(ydelta); + + q[0] = l->x1 - (x); + q[1] = (x + w - 1) - l->x1; + q[2] = l->y1 - (y); + q[3] = (y + h - 1) - l->y1; + + for (int i = 0; i < 4; i++) { + if (p[i]) { + float u = ((float) q[i]) / ((float) p[i]); + + if (p[i] < 0) { // outside to inside + if (u > umax) return false; + if (u > umin) umin = u; + } + + if (p[i] > 0) { // inside to outside + if (u < umin) return false; + if (u < umax) umax = u; + } + + } else if (q[i] < 0) { + return false; + } + } + + if (umax < umin) return false; + + int x1_c = l->x1 + (xdelta * umin); + int y1_c = l->y1 + (ydelta * umin); + int x2_c = l->x1 + (xdelta * umax); + int y2_c = l->y1 + (ydelta * umax); + l->x1 = x1_c; + l->y1 = y1_c; + l->x2 = x2_c; + l->y2 = y2_c; + + return true; +} + +///////////////////// +// Rectangle Stuff // +///////////////////// + +void rectangle_init(rectangle_t *ptr, int x, int y, int w, int h) +{ + ptr->x = x; + ptr->y = y; + ptr->w = w; + ptr->h = h; +} + +void rectangle_copy(rectangle_t *dst, rectangle_t *src) +{ + memcpy(dst, src, sizeof(rectangle_t)); +} + +bool rectangle_equal_fast(rectangle_t *ptr0, rectangle_t *ptr1) +{ + return !memcmp(ptr0, ptr1, sizeof(rectangle_t)); +} + +bool rectangle_overlap(rectangle_t *ptr0, rectangle_t *ptr1) +{ + int x0 = ptr0->x; + int y0 = ptr0->y; + int w0 = ptr0->w; + int h0 = ptr0->h; + int x1 = ptr1->x; + int y1 = ptr1->y; + int w1 = ptr1->w; + int h1 = ptr1->h; + return (x0 < (x1 + w1)) && (y0 < (y1 + h1)) && (x1 < (x0 + w0)) && (y1 < (y0 + h0)); +} + +void rectangle_intersected(rectangle_t *dst, rectangle_t *src) +{ + int leftX = IM_MAX(dst->x, src->x); + int topY = IM_MAX(dst->y, src->y); + int rightX = IM_MIN(dst->x + dst->w, src->x + src->w); + int bottomY = IM_MIN(dst->y + dst->h, src->y + src->h); + dst->x = leftX; + dst->y = topY; + dst->w = rightX - leftX; + dst->h = bottomY - topY; +} + +void rectangle_united(rectangle_t *dst, rectangle_t *src) +{ + int leftX = IM_MIN(dst->x, src->x); + int topY = IM_MIN(dst->y, src->y); + int rightX = IM_MAX(dst->x + dst->w, src->x + src->w); + int bottomY = IM_MAX(dst->y + dst->h, src->y + src->h); + dst->x = leftX; + dst->y = topY; + dst->w = rightX - leftX; + dst->h = bottomY - topY; +} + +///////////////// +// Image Stuff // +///////////////// + +void imlib_image_init(image_t *ptr, int w, int h, pixformat_t pixfmt, uint32_t size, void *pixels) +{ + ptr->w = w; + ptr->h = h; + ptr->pixfmt = pixfmt; + ptr->size = size; + ptr->pixels = pixels; + ptr->is_data_alloc = false; +} + +image_t* imlib_image_create(int w, int h, pixformat_t pixfmt, uint32_t size, void *pixels, bool is_data_alloc) +{ + image_t *ptr = xalloc(sizeof(image_t)); + ptr->w = w; + ptr->h = h; + ptr->pixfmt = pixfmt; + ptr->size = size; + ptr->pixels = pixels; + ptr->is_data_alloc = is_data_alloc; + if(ptr->is_data_alloc) + { + ptr->size = image_size(ptr); + ptr->pixels = xalloc(ptr->size); + ptr->is_data_alloc = is_data_alloc; + } + return ptr; +} +image_t* imlib_image_create_fb_buff(int w, int h, pixformat_t pixfmt, uint32_t size, void *pixels, bool is_data_alloc) +{ + image_t *ptr = fb_alloc(sizeof(image_t), 0); + ptr->w = w; + ptr->h = h; + ptr->pixfmt = pixfmt; + ptr->size = size; + ptr->pixels = pixels; + ptr->is_data_alloc = is_data_alloc; + if(ptr->is_data_alloc) + { + ptr->size = image_size(ptr); + ptr->pixels = fb_alloc(ptr->size, 0); + ptr->is_data_alloc = is_data_alloc; + } + return ptr; +} +void imlib_image_destroy_fb_buff(image_t **obj) +{ + if (*obj) + { + if ((*obj)->is_data_alloc) + { + fb_free((*obj)->data); + (*obj)->data = NULL; + } + fb_free(*obj); + *obj = NULL; + } +} +void imlib_image_destroy(image_t **obj) +{ + if (*obj) + { + if ((*obj)->is_data_alloc) + { + xfree((*obj)->data); + (*obj)->data = NULL; + } + xfree(*obj); + *obj = NULL; + } +} +void image_copy(image_t *dst, image_t *src) +{ + memcpy(dst, src, sizeof(image_t)); +} + +size_t image_size(image_t *ptr) +{ + switch (ptr->pixfmt) { + case PIXFORMAT_BINARY: { + return IMAGE_BINARY_LINE_LEN_BYTES(ptr) * ptr->h; + } + case PIXFORMAT_GRAYSCALE: + case PIXFORMAT_BAYER_ANY: { // re-use + return IMAGE_GRAYSCALE_LINE_LEN_BYTES(ptr) * ptr->h; + } + case PIXFORMAT_RGB565: + case PIXFORMAT_YUV_ANY: { // re-use + return IMAGE_RGB565_LINE_LEN_BYTES(ptr) * ptr->h; + } + case PIXFORMAT_COMPRESSED_ANY: { + return ptr->size; + } + case PIXFORMAT_RGB888: { + return IMAGE_RGB888_LINE_LEN_BYTES(ptr) * ptr->h; + } + default: { + return 0; + } + } +} + +bool image_get_mask_pixel(image_t *ptr, int x, int y) +{ + if ((0 <= x) && (x < ptr->w) && (0 <= y) && (y < ptr->h)) { + switch (ptr->pixfmt) { + case PIXFORMAT_BINARY: { + return IMAGE_GET_BINARY_PIXEL(ptr, x, y); + } + case PIXFORMAT_GRAYSCALE: { + return COLOR_GRAYSCALE_TO_BINARY(IMAGE_GET_GRAYSCALE_PIXEL(ptr, x, y)); + } + case PIXFORMAT_RGB565: { + return COLOR_RGB565_TO_BINARY(IMAGE_GET_RGB565_PIXEL(ptr, x, y)); + } + case PIXFORMAT_RGB888: { + return COLOR_RGB888_TO_BINARY(IMAGE_GET_RGB888_PIXEL(ptr, x, y)); + } + default: { + return false; + } + } + } + + return false; +} + +// Gamma uncompress +extern const float xyz_table[256]; + +const int8_t kernel_gauss_3[3*3] = { + 1, 2, 1, + 2, 4, 2, + 1, 2, 1, +}; + +const int8_t kernel_gauss_5[5*5] = { + 1, 4, 6, 4, 1, + 4, 16, 24, 16, 4, + 6, 24, 36, 24, 6, + 4, 16, 24, 16, 4, + 1, 4, 6, 4, 1 +}; + +const int kernel_laplacian_3[3*3] = { + -1, -1, -1, + -1, 8, -1, + -1, -1, -1 +}; + +const int kernel_high_pass_3[3*3] = { + -1, -1, -1, + -1, +8, -1, + -1, -1, -1 +}; + +// This function fills a grayscale image from an array of floating point numbers that are scaled +// between min and max. The image w*h must equal the floating point array w*h. +void imlib_fill_image_from_float(image_t *img, int w, int h, float *data, float min, float max, + bool mirror, bool flip, bool dst_transpose, bool src_transpose) +{ + float tmp = min; + min = (min < max) ? min : max; + max = (max > tmp) ? max : tmp; + + float diff = 255.f / (max - min); + int w_1 = w - 1; + int h_1 = h - 1; + + if (!src_transpose) { + for (int y = 0; y < h; y++) { + int y_dst = flip ? (h_1 - y) : y; + float *raw_row = data + (y * w); + uint8_t *row_pointer = ((uint8_t *) img->data) + (y_dst * w); + uint8_t *t_row_pointer = ((uint8_t *) img->data) + y_dst; + + for (int x = 0; x < w; x++) { + int x_dst = mirror ? (w_1 - x) : x; + float raw = raw_row[x]; + + if (raw < min) { + raw = min; + } + + if (raw > max) { + raw = max; + } + + int pixel = fast_roundf((raw - min) * diff); + pixel = __USAT(pixel, 8); + + if (!dst_transpose) { + row_pointer[x_dst] = pixel; + } else { + t_row_pointer[x_dst * h] = pixel; + } + } + } + } else { + for (int x = 0; x < w; x++) { + int x_dst = mirror ? (w_1 - x) : x; + float *raw_row = data + (x * h); + uint8_t *t_row_pointer = ((uint8_t *) img->data) + (x_dst * h); + uint8_t *row_pointer = ((uint8_t *) img->data) + x_dst; + + for (int y = 0; y < h; y++) { + int y_dst = flip ? (h_1 - y) : y; + float raw = raw_row[y]; + + if (raw < min) { + raw = min; + } + + if (raw > max) { + raw = max; + } + + int pixel = fast_roundf((raw - min) * diff); + pixel = __USAT(pixel, 8); + + if (!dst_transpose) { + row_pointer[y_dst * w] = pixel; + } else { + t_row_pointer[y_dst] = pixel; + } + } + } + } +} + +int8_t imlib_rgb565_to_l(uint16_t pixel) +{ + float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)]; + float g_lin = xyz_table[COLOR_RGB565_TO_G8(pixel)]; + float b_lin = xyz_table[COLOR_RGB565_TO_B8(pixel)]; + + float y = ((r_lin * 0.2126f) + (g_lin * 0.7152f) + (b_lin * 0.0722f)) * (1.0f / 100.000f); + + y = (y>0.008856f) ? fast_cbrtf(y) : ((y * 7.787037f) + 0.137931f); + + return IM_LIMIT(fast_floorf(116 * y) - 16, COLOR_L_MIN, COLOR_L_MAX); +} + +int8_t imlib_rgb565_to_a(uint16_t pixel) +{ + float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)]; + float g_lin = xyz_table[COLOR_RGB565_TO_G8(pixel)]; + float b_lin = xyz_table[COLOR_RGB565_TO_B8(pixel)]; + + float x = ((r_lin * 0.4124f) + (g_lin * 0.3576f) + (b_lin * 0.1805f)) * (1.0f / 095.047f); + float y = ((r_lin * 0.2126f) + (g_lin * 0.7152f) + (b_lin * 0.0722f)) * (1.0f / 100.000f); + + x = (x>0.008856f) ? fast_cbrtf(x) : ((x * 7.787037f) + 0.137931f); + y = (y>0.008856f) ? fast_cbrtf(y) : ((y * 7.787037f) + 0.137931f); + + return IM_LIMIT(fast_floorf(500 * (x-y)), COLOR_A_MIN, COLOR_A_MAX); +} + +int8_t imlib_rgb565_to_b(uint16_t pixel) +{ + float r_lin = xyz_table[COLOR_RGB565_TO_R8(pixel)]; + float g_lin = xyz_table[COLOR_RGB565_TO_G8(pixel)]; + float b_lin = xyz_table[COLOR_RGB565_TO_B8(pixel)]; + + float y = ((r_lin * 0.2126f) + (g_lin * 0.7152f) + (b_lin * 0.0722f)) * (1.0f / 100.000f); + float z = ((r_lin * 0.0193f) + (g_lin * 0.1192f) + (b_lin * 0.9505f)) * (1.0f / 108.883f); + + y = (y>0.008856f) ? fast_cbrtf(y) : ((y * 7.787037f) + 0.137931f); + z = (z>0.008856f) ? fast_cbrtf(z) : ((z * 7.787037f) + 0.137931f); + + return IM_LIMIT(fast_floorf(200 * (y-z)), COLOR_B_MIN, COLOR_B_MAX); +} +int8_t imlib_rgb888_to_l(uint32_t pixel) +{ + float r_lin = xyz_table[COLOR_RGB888_TO_R8(pixel)]; + float g_lin = xyz_table[COLOR_RGB888_TO_G8(pixel)]; + float b_lin = xyz_table[COLOR_RGB888_TO_B8(pixel)]; + + float y = ((r_lin * 0.2126f) + (g_lin * 0.7152f) + (b_lin * 0.0722f)) * (1.0f / 100.000f); + + y = (y>0.008856f) ? fast_cbrtf(y) : ((y * 7.787037f) + 0.137931f); + + return IM_LIMIT(fast_floorf(116 * y) - 16, COLOR_L_MIN, COLOR_L_MAX); +} + +int8_t imlib_rgb888_to_a(uint32_t pixel) +{ + float r_lin = xyz_table[COLOR_RGB888_TO_R8(pixel)]; + float g_lin = xyz_table[COLOR_RGB888_TO_G8(pixel)]; + float b_lin = xyz_table[COLOR_RGB888_TO_B8(pixel)]; + + float x = ((r_lin * 0.4124f) + (g_lin * 0.3576f) + (b_lin * 0.1805f)) * (1.0f / 095.047f); + float y = ((r_lin * 0.2126f) + (g_lin * 0.7152f) + (b_lin * 0.0722f)) * (1.0f / 100.000f); + + x = (x>0.008856f) ? fast_cbrtf(x) : ((x * 7.787037f) + 0.137931f); + y = (y>0.008856f) ? fast_cbrtf(y) : ((y * 7.787037f) + 0.137931f); + + return IM_LIMIT(fast_floorf(500 * (x-y)), COLOR_A_MIN, COLOR_A_MAX); +} + +int8_t imlib_rgb888_to_b(uint32_t pixel) +{ + float r_lin = xyz_table[COLOR_RGB888_TO_R8(pixel)]; + float g_lin = xyz_table[COLOR_RGB888_TO_G8(pixel)]; + float b_lin = xyz_table[COLOR_RGB888_TO_B8(pixel)]; + + float y = ((r_lin * 0.2126f) + (g_lin * 0.7152f) + (b_lin * 0.0722f)) * (1.0f / 100.000f); + float z = ((r_lin * 0.0193f) + (g_lin * 0.1192f) + (b_lin * 0.9505f)) * (1.0f / 108.883f); + + y = (y>0.008856f) ? fast_cbrtf(y) : ((y * 7.787037f) + 0.137931f); + z = (z>0.008856f) ? fast_cbrtf(z) : ((z * 7.787037f) + 0.137931f); + + return IM_LIMIT(fast_floorf(200 * (y-z)), COLOR_B_MIN, COLOR_B_MAX); +} +// https://en.wikipedia.org/wiki/Lab_color_space -> CIELAB-CIEXYZ conversions +// https://en.wikipedia.org/wiki/SRGB -> Specification of the transformation +uint16_t imlib_lab_to_rgb(uint8_t l, int8_t a, int8_t b) +{ + float x = ((l + 16) * 0.008621f) + (a * 0.002f); + float y = ((l + 16) * 0.008621f); + float z = ((l + 16) * 0.008621f) - (b * 0.005f); + + x = ((x > 0.206897f) ? (x*x*x) : ((0.128419f * x) - 0.017713f)) * 095.047f; + y = ((y > 0.206897f) ? (y*y*y) : ((0.128419f * y) - 0.017713f)) * 100.000f; + z = ((z > 0.206897f) ? (z*z*z) : ((0.128419f * z) - 0.017713f)) * 108.883f; + + float r_lin = ((x * +3.2406f) + (y * -1.5372f) + (z * -0.4986f)) / 100.0f; + float g_lin = ((x * -0.9689f) + (y * +1.8758f) + (z * +0.0415f)) / 100.0f; + float b_lin = ((x * +0.0557f) + (y * -0.2040f) + (z * +1.0570f)) / 100.0f; + + r_lin = (r_lin>0.0031308f) ? ((1.055f*powf(r_lin, 0.416666f))-0.055f) : (r_lin*12.92f); + g_lin = (g_lin>0.0031308f) ? ((1.055f*powf(g_lin, 0.416666f))-0.055f) : (g_lin*12.92f); + b_lin = (b_lin>0.0031308f) ? ((1.055f*powf(b_lin, 0.416666f))-0.055f) : (b_lin*12.92f); + + uint32_t red = IM_MAX(IM_MIN(fast_floorf(r_lin * COLOR_R8_MAX), COLOR_R8_MAX), COLOR_R8_MIN); + uint32_t green = IM_MAX(IM_MIN(fast_floorf(g_lin * COLOR_G8_MAX), COLOR_G8_MAX), COLOR_G8_MIN); + uint32_t blue = IM_MAX(IM_MIN(fast_floorf(b_lin * COLOR_B8_MAX), COLOR_B8_MAX), COLOR_B8_MIN); + + return COLOR_R8_G8_B8_TO_RGB565(red, green, blue); +} +uint32_t imlib_lab_to_rgb888(uint8_t l, int8_t a, int8_t b) +{ + float x = ((l + 16) * 0.008621f) + (a * 0.002f); + float y = ((l + 16) * 0.008621f); + float z = ((l + 16) * 0.008621f) - (b * 0.005f); + + x = ((x > 0.206897f) ? (x*x*x) : ((0.128419f * x) - 0.017713f)) * 095.047f; + y = ((y > 0.206897f) ? (y*y*y) : ((0.128419f * y) - 0.017713f)) * 100.000f; + z = ((z > 0.206897f) ? (z*z*z) : ((0.128419f * z) - 0.017713f)) * 108.883f; + + float r_lin = ((x * +3.2406f) + (y * -1.5372f) + (z * -0.4986f)) / 100.0f; + float g_lin = ((x * -0.9689f) + (y * +1.8758f) + (z * +0.0415f)) / 100.0f; + float b_lin = ((x * +0.0557f) + (y * -0.2040f) + (z * +1.0570f)) / 100.0f; + + r_lin = (r_lin>0.0031308f) ? ((1.055f*powf(r_lin, 0.416666f))-0.055f) : (r_lin*12.92f); + g_lin = (g_lin>0.0031308f) ? ((1.055f*powf(g_lin, 0.416666f))-0.055f) : (g_lin*12.92f); + b_lin = (b_lin>0.0031308f) ? ((1.055f*powf(b_lin, 0.416666f))-0.055f) : (b_lin*12.92f); + + uint32_t red = IM_MAX(IM_MIN(fast_floorf(r_lin * COLOR_R8_MAX), COLOR_R8_MAX), COLOR_R8_MIN); + uint32_t green = IM_MAX(IM_MIN(fast_floorf(g_lin * COLOR_G8_MAX), COLOR_G8_MAX), COLOR_G8_MIN); + uint32_t blue = IM_MAX(IM_MIN(fast_floorf(b_lin * COLOR_B8_MAX), COLOR_B8_MAX), COLOR_B8_MIN); + + return COLOR_R8_G8_B8_TO_RGB888(red, green, blue); +} +// https://en.wikipedia.org/wiki/YCbCr -> JPEG Conversion +uint16_t imlib_yuv_to_rgb(uint8_t y, int8_t u, int8_t v) +{ + uint32_t r = IM_MAX(IM_MIN(y + ((91881 * v) >> 16), COLOR_R8_MAX), COLOR_R8_MIN); + uint32_t g = IM_MAX(IM_MIN(y - (((22554 * u) + (46802 * v)) >> 16), COLOR_G8_MAX), COLOR_G8_MIN); + uint32_t b = IM_MAX(IM_MIN(y + ((116130 * u) >> 16), COLOR_B8_MAX), COLOR_B8_MIN); + + return COLOR_R8_G8_B8_TO_RGB565(r, g, b); +} +uint32_t imlib_yuv_to_rgb888(uint8_t y, int8_t u, int8_t v) +{ + uint32_t r = IM_MAX(IM_MIN(y + ((91881 * v) >> 16), COLOR_R8_MAX), COLOR_R8_MIN); + uint32_t g = IM_MAX(IM_MIN(y - (((22554 * u) + (46802 * v)) >> 16), COLOR_G8_MAX), COLOR_G8_MIN); + uint32_t b = IM_MAX(IM_MIN(y + ((116130 * u) >> 16), COLOR_B8_MAX), COLOR_B8_MIN); + + return COLOR_R8_G8_B8_TO_RGB888(r, g, b); +} +//////////////////////////////////////////////////////////////////////////////// + +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) +static save_image_format_t imblib_parse_extension(image_t *img, const char *path) +{ + size_t l = strlen(path); + const char *p = path + l; + if (l >= 5) { + if (((p[-1] == 'g') || (p[-1] == 'G')) + && ((p[-2] == 'e') || (p[-2] == 'E')) + && ((p[-3] == 'p') || (p[-3] == 'P')) + && ((p[-4] == 'j') || (p[-4] == 'J')) + && ((p[-5] == '.') || (p[-5] == '.'))) { + // Will convert to JPG if not. + return FORMAT_JPG; + } + } + if (l >= 4) { + if (((p[-1] == 'g') || (p[-1] == 'G')) + && ((p[-2] == 'p') || (p[-2] == 'P')) + && ((p[-3] == 'j') || (p[-3] == 'J')) + && ((p[-4] == '.') || (p[-4] == '.'))) { + // Will convert to JPG if not. + return FORMAT_JPG; + } else if (((p[-1] == 'g') || (p[-1] == 'G')) + && ((p[-2] == 'n') || (p[-2] == 'N')) + && ((p[-3] == 'p') || (p[-3] == 'P')) + && ((p[-4] == '.') || (p[-4] == '.'))) { + // Will convert to PNG if not. + return FORMAT_PNG; + } else if (((p[-1] == 'p') || (p[-1] == 'P')) + && ((p[-2] == 'm') || (p[-2] == 'M')) + && ((p[-3] == 'b') || (p[-3] == 'B')) + && ((p[-4] == '.') || (p[-4] == '.'))) { + if (IM_IS_JPEG(img) || IM_IS_BAYER(img)) { + ERR_PRINT("OSError:Image is not BMP!"); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image is not BMP!")); + } + return FORMAT_BMP; + } else if (((p[-1] == 'm') || (p[-1] == 'M')) + && ((p[-2] == 'p') || (p[-2] == 'P')) + && ((p[-3] == 'p') || (p[-3] == 'P')) + && ((p[-4] == '.') || (p[-4] == '.'))) { + if (!IM_IS_RGB565(img)) { + ERR_PRINT("OSError:Image is not PPM!"); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image is not PPM!")); + } + return FORMAT_PNM; + } else if (((p[-1] == 'm') || (p[-1] == 'M')) + && ((p[-2] == 'p') || (p[-2] == 'P')) + && ((p[-3] == 'p') || (p[-3] == 'P')) + && ((p[-4] == '.') || (p[-4] == '.'))) { + if (!IM_IS_RGB888(img)) { + ERR_PRINT("OSError:Image is not PPM!"); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image is not PPM!")); + } + return FORMAT_PNM; + } else if (((p[-1] == 'm') || (p[-1] == 'M')) + && ((p[-2] == 'g') || (p[-2] == 'G')) + && ((p[-3] == 'p') || (p[-3] == 'P')) + && ((p[-4] == '.') || (p[-4] == '.'))) { + if (!IM_IS_GS(img)) { + ERR_PRINT("OSError:Image is not PGM!"); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image is not PGM!")); + } + return FORMAT_PNM; + } else if (((p[-1] == 'w') || (p[-1] == 'W')) + && ((p[-2] == 'a') || (p[-2] == 'A')) + && ((p[-3] == 'r') || (p[-3] == 'R')) + && ((p[-4] == '.') || (p[-4] == '.'))) { + if (!IM_IS_BAYER(img)) { + ERR_PRINT("OSError:Image is not BAYER!"); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image is not BAYER!")); + } + return FORMAT_RAW; + } + + } + return FORMAT_DONT_CARE; +} + +bool imlib_read_geometry(FIL *fp, image_t *img, const char *path, img_read_settings_t *rs) +{ + file_read_open(fp, path); + char magic[4]; + read_data(fp, &magic, 4); + file_close(fp); + + bool vflipped = false; + if ((magic[0]=='P') + && ((magic[1]=='2') || (magic[1]=='3') + || (magic[1]=='5') || (magic[1]=='6'))) { // PPM + rs->format = FORMAT_PNM; + file_read_open(fp, path); + file_buffer_on(fp); // REMEMBER TO TURN THIS OFF LATER! + ppm_read_geometry(fp, img, path, &rs->ppm_rs); + } else if ((magic[0]=='B') && (magic[1]=='M')) { // BMP + rs->format = FORMAT_BMP; + file_read_open(fp, path); + file_buffer_on(fp); // REMEMBER TO TURN THIS OFF LATER! + vflipped = bmp_read_geometry(fp, img, path, &rs->bmp_rs); + } else if ((magic[0]==0xFF) && (magic[1]==0xD8)) { // JPG + rs->format = FORMAT_JPG; + file_read_open(fp, path); + // Do not use file_buffer_on() here. + jpeg_read_geometry(fp, img, path, &rs->jpg_rs); + file_buffer_on(fp); // REMEMBER TO TURN THIS OFF LATER! + } else if ((magic[0]==0x89) && (magic[1]==0x50) && (magic[2]==0x4E) && (magic[3]==0x47)) { // PNG + rs->format = FORMAT_PNG; + file_read_open(fp, path); + // Do not use file_buffer_on() here. + png_read_geometry(fp, img, path, &rs->png_rs); + file_buffer_on(fp); // REMEMBER TO TURN THIS OFF LATER! + } else { + ff_unsupported_format(NULL); + } + imblib_parse_extension(img, path); // Enforce extension! + return vflipped; +} + +static void imlib_read_pixels(FIL *fp, image_t *img, int n_lines, img_read_settings_t *rs) +{ + switch (rs->format) { + case FORMAT_BMP: + bmp_read_pixels(fp, img, n_lines, &rs->bmp_rs); + break; + case FORMAT_PNM: + ppm_read_pixels(fp, img, n_lines, &rs->ppm_rs); + break; + default: // won't happen + break; + } +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO + +void imlib_image_operation(image_t *img, const char *path, image_t *other, int scalar, line_op_t op, void *data) +{ + if (path) { + #if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + uint32_t size = fb_avail() / 2; + void *alloc = fb_alloc(size, FB_ALLOC_NO_HINT); // We have to do this before the read. + // This code reads a window of an image in at a time and then executes + // the line operation on each line in that window before moving to the + // next window. The vflipped part is here because BMP files can be saved + // vertically flipped resulting in us reading the image backwards. + FIL fp; + image_t temp; + img_read_settings_t rs; + bool vflipped = imlib_read_geometry(&fp, &temp, path, &rs); + if (!IM_EQUAL(img, &temp)) { + // f_close(&fp); + file_close(&fp); + ERR_PRINT("OSError:Images not equal!"); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Images not equal!")); + } + // When processing vertically flipped images the read function will fill + // the window up from the bottom. The read function assumes that the + // window is equal to an image in size. However, since this is not the + // case we shrink the window size to how many lines we're buffering. + temp.pixels = alloc; + // Set the max buffer height to image height. + temp.h = IM_MIN(img->h, (size / (temp.w * temp.bpp))); + // This should never happen unless someone forgot to free. + if ((!temp.pixels) || (!temp.h)) { + ERR_PRINT("OSError:Not enough memory available!"); + // mp_raise_msg(&mp_type_MemoryError, MP_ERROR_TEXT("Not enough memory available!")); + } + for (int i=0; ih; i+=temp.h) { // goes past end + int lines = IM_MIN(temp.h, img->h-i); + imlib_read_pixels(&fp, &temp, lines, &rs); + for (int j=0; jh-i-lines)+j, temp.pixels+(temp.w*temp.bpp*j), data, true); + } + } + } + file_buffer_off(&fp); + file_close(&fp); + fb_free(alloc); + #else + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Image I/O is not supported")); + #endif + } else if (other) { + if (!IM_EQUAL(img, other)) { + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Images not equal!")); + } + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + for (int i=0, ii=img->h; ih; ih; ih; ipixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *row_ptr = fb_alloc(IMAGE_BINARY_LINE_LEN_BYTES(img), FB_ALLOC_NO_HINT); + + for (int i=0, ii=img->w; ih; iw; ih; iw; ih; iw; ih; ipixels, img->w * img->h); + file_close(&fp); + break; + } + case FORMAT_JPG: + jpeg_write(img, path, quality); + break; + case FORMAT_PNG: + png_write(img, path); + break; + case FORMAT_DONT_CARE: + // Path doesn't have an extension. + if (IM_IS_JPEG(img)) { + char *new_path = strcat(strcpy(fb_alloc(strlen(path)+5, FB_ALLOC_NO_HINT), path), ".jpg"); + jpeg_write(img, new_path, quality); + fb_free(NULL); + } else if (img->pixfmt == PIXFORMAT_PNG) { + char *new_path = strcat(strcpy(fb_alloc(strlen(path)+5, FB_ALLOC_NO_HINT), path), ".png"); + png_write(img, new_path); + fb_free(NULL); + } else if (IM_IS_BAYER(img)) { + FIL fp; + char *new_path = strcat(strcpy(fb_alloc(strlen(path)+5, FB_ALLOC_NO_HINT), path), ".raw"); + file_write_open(&fp, new_path); + write_data(&fp, img->pixels, img->w * img->h); + file_close(&fp); + fb_free(NULL); + } else { // RGB or GS, save as BMP. + char *new_path = strcat(strcpy(fb_alloc(strlen(path)+5, FB_ALLOC_NO_HINT), path), ".bmp"); + bmp_write_subimg(img, new_path, roi); + fb_free(NULL); + } + break; + } +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO + +//////////////////////////////////////////////////////////////////////////////// + +void imlib_zero(image_t *img, image_t *mask, bool invert) +{ + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (image_get_mask_pixel(mask, x, y) ^ invert) { + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, 0); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (image_get_mask_pixel(mask, x, y) ^ invert) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, 0); + } + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (image_get_mask_pixel(mask, x, y) ^ invert) { + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, 0); + } + } + } + break; + } + case PIXFORMAT_RGB888: { + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (image_get_mask_pixel(mask, x, y) ^ invert) { + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr, x, 0); + } + } + } + break; + } + default: { + break; + } + } +} + +#ifdef IMLIB_ENABLE_LENS_CORR +// A simple algorithm for correcting lens distortion. +// See http://www.tannerhelland.com/4743/simple-algorithm-correcting-lens-distortion/ +void imlib_lens_corr(image_t *img, float strength, float zoom, float x_corr, float y_corr) +{ + int w = img->w; + int h = img->h; + int halfWidth = w / 2; + int halfHeight = h / 2; + float maximum_diameter = fast_sqrtf((w * w) + (h * h)); + float lens_corr_diameter = strength / maximum_diameter; + zoom = 1 / zoom; + + // Convert percentage offset to pixels from center of image + int x_off = w * x_corr; + int y_off = h * y_corr; + + // Create a tmp copy of the image to pull pixels from. + size_t size = image_size(img); + void *data = fb_alloc(size, FB_ALLOC_NO_HINT); + memcpy(data, img->data, size); + memset(img->data, 0, size); + + int maximum_radius = fast_ceilf(maximum_diameter / 2) + 1; // +1 inclusive of final value + float *precalculated_table = fb_alloc(maximum_radius * sizeof(float), FB_ALLOC_NO_HINT); + + for(int i=0; i < maximum_radius; i++) { + float r = lens_corr_diameter * i; + precalculated_table[i] = (fast_atanf(r) / r) * zoom; + } + + int down_adj = halfHeight + y_off; + int up_adj = h - 1 - halfHeight + y_off; + int right_adj = halfWidth + x_off; + int left_adj = w - 1 - halfWidth + x_off; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *tmp = (uint32_t *) data; + + for (int y = 0; y < halfHeight; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *row_ptr2 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, h-1-y); + int newY = y - halfHeight; + int newY2 = newY * newY; + + for (int x = 0; x < halfWidth; x++) { + int newX = x - halfWidth; + int newX2 = newX * newX; + float precalculated = precalculated_table[(int)fast_sqrtf(newX2 + newY2)]; + int sourceY = fast_roundf(precalculated * newY); // rounding is necessary + int sourceX = fast_roundf(precalculated * newX); // rounding is necessary + int sourceY_down = down_adj + sourceY; + int sourceY_up = up_adj - sourceY; + int sourceX_right = right_adj + sourceX; + int sourceX_left = left_adj - sourceX; + + // plot the 4 symmetrical pixels + // top 2 pixels + if (sourceY_down >= 0 && sourceY_down < h) { + uint32_t *ptr = tmp + (((w + UINT32_T_MASK) >> UINT32_T_SHIFT) * sourceY_down); + + if (sourceX_right >= 0 && sourceX_right < w) { + uint8_t pixel = IMAGE_GET_BINARY_PIXEL_FAST(ptr, sourceX_right); + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, pixel); + } + + if (sourceX_left >= 0 && sourceX_left < w) { + uint8_t pixel = IMAGE_GET_BINARY_PIXEL_FAST(ptr, sourceX_left); + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, w - 1 - x, pixel); + } + } + + // bottom 2 pixels + if (sourceY_up >= 0 && sourceY_up < h) { + uint32_t *ptr = tmp + (((w + UINT32_T_MASK) >> UINT32_T_SHIFT) * sourceY_up); + + if (sourceX_right >= 0 && sourceX_right < w) { + uint8_t pixel = IMAGE_GET_BINARY_PIXEL_FAST(ptr, sourceX_right); + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr2, x, pixel); + } + + if (sourceX_left >= 0 && sourceX_left < w) { + uint8_t pixel = IMAGE_GET_BINARY_PIXEL_FAST(ptr, sourceX_left); + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr2, w - 1 - x, pixel); + } + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *tmp = (uint8_t *) data; + + for (int y = 0; y < halfHeight; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint8_t *row_ptr2 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, h-1-y); + int newY = y - halfHeight; + int newY2 = newY * newY; + + for (int x = 0; x < halfWidth; x++) { + int newX = x - halfWidth; + int newX2 = newX * newX; + float precalculated = precalculated_table[(int)fast_sqrtf(newX2 + newY2)]; + int sourceY = fast_roundf(precalculated * newY); // rounding is necessary + int sourceX = fast_roundf(precalculated * newX); // rounding is necessary + int sourceY_down = down_adj + sourceY; + int sourceY_up = up_adj - sourceY; + int sourceX_right = right_adj + sourceX; + int sourceX_left = left_adj - sourceX; + + // plot the 4 symmetrical pixels + // top 2 pixels + if (sourceY_down >= 0 && sourceY_down < h) { + uint8_t *ptr = tmp + (w * sourceY_down); + + if (sourceX_right >= 0 && sourceX_right < w) { + row_ptr[x] = ptr[sourceX_right]; + } + + if (sourceX_left >= 0 && sourceX_left < w) { + row_ptr[w - 1 - x] = ptr[sourceX_left]; + } + } + + // bottom 2 pixels + if (sourceY_up >= 0 && sourceY_up < h) { + uint8_t *ptr = tmp + (w * sourceY_up); + + if (sourceX_right >= 0 && sourceX_right < w) { + row_ptr2[x] = ptr[sourceX_right]; + } + + if (sourceX_left >= 0 && sourceX_left < w) { + row_ptr2[w - 1 - x] = ptr[sourceX_left]; + } + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *tmp = (uint16_t *) data; + + for (int y = 0; y < halfHeight; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + uint16_t *row_ptr2 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, h-1-y); + int newY = y - halfHeight; + int newY2 = newY * newY; + + for (int x = 0; x < halfWidth; x++) { + int newX = x - halfWidth; + int newX2 = newX * newX; + float precalculated = precalculated_table[(int)fast_sqrtf(newX2 + newY2)]; + int sourceY = fast_roundf(precalculated * newY); // rounding is necessary + int sourceX = fast_roundf(precalculated * newX); // rounding is necessary + int sourceY_down = down_adj + sourceY; + int sourceY_up = up_adj - sourceY; + int sourceX_right = right_adj + sourceX; + int sourceX_left = left_adj - sourceX; + + // plot the 4 symmetrical pixels + // top 2 pixels + if (sourceY_down >= 0 && sourceY_down < h) { + uint16_t *ptr = tmp + (w * sourceY_down); + + if (sourceX_right >= 0 && sourceX_right < w) { + row_ptr[x] = ptr[sourceX_right]; + } + + if (sourceX_left >= 0 && sourceX_left < w) { + row_ptr[w - 1 - x] = ptr[sourceX_left]; + } + } + + // bottom 2 pixels + if (sourceY_up >= 0 && sourceY_up < h) { + uint16_t *ptr = tmp + (w * sourceY_up); + + if (sourceX_right >= 0 && sourceX_right < w) { + row_ptr2[x] = ptr[sourceX_right]; + } + + if (sourceX_left >= 0 && sourceX_left < w) { + row_ptr2[w - 1 - x] = ptr[sourceX_left]; + } + } + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *tmp = (pixel24_t *) data; + + for (int y = 0; y < halfHeight; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + pixel24_t *row_ptr2 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, h-1-y); + int newY = y - halfHeight; + int newY2 = newY * newY; + + for (int x = 0; x < halfWidth; x++) { + int newX = x - halfWidth; + int newX2 = newX * newX; + float precalculated = precalculated_table[(int)fast_sqrtf(newX2 + newY2)]; + int sourceY = fast_roundf(precalculated * newY); // rounding is necessary + int sourceX = fast_roundf(precalculated * newX); // rounding is necessary + int sourceY_down = down_adj + sourceY; + int sourceY_up = up_adj - sourceY; + int sourceX_right = right_adj + sourceX; + int sourceX_left = left_adj - sourceX; + + // plot the 4 symmetrical pixels + // top 2 pixels + if (sourceY_down >= 0 && sourceY_down < h) { + pixel24_t *ptr = tmp + (w * sourceY_down); + + if (sourceX_right >= 0 && sourceX_right < w) { + row_ptr[x] = ptr[sourceX_right]; + } + + if (sourceX_left >= 0 && sourceX_left < w) { + row_ptr[w - 1 - x] = ptr[sourceX_left]; + } + } + + // bottom 2 pixels + if (sourceY_up >= 0 && sourceY_up < h) { + pixel24_t *ptr = tmp + (w * sourceY_up); + + if (sourceX_right >= 0 && sourceX_right < w) { + row_ptr2[x] = ptr[sourceX_right]; + } + + if (sourceX_left >= 0 && sourceX_left < w) { + row_ptr2[w - 1 - x] = ptr[sourceX_left]; + } + } + } + } + break; + } + default: { + break; + } + } + fb_free(precalculated_table); // precalculated_table + fb_free(data); // data +} +#endif //IMLIB_ENABLE_LENS_CORR + +//////////////////////////////////////////////////////////////////////////////// + +int imlib_image_mean(image_t *src, int *r_mean, int *g_mean, int *b_mean) +{ + int r_s = 0; + int g_s = 0; + int b_s = 0; + int n = src->w * src->h; + + switch (src->pixfmt) { + case PIXFORMAT_BINARY: { + // Can't run this on a binary image. + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int i=0; ipixels[i]; + } + *r_mean = r_s/n; + *g_mean = r_s/n; + *b_mean = r_s/n; + break; + } + case PIXFORMAT_RGB565: { + for (int i=0; ipixels)[i]; + r_s += COLOR_RGB565_TO_R8(p); + g_s += COLOR_RGB565_TO_G8(p); + b_s += COLOR_RGB565_TO_B8(p); + } + *r_mean = r_s/n; + *g_mean = g_s/n; + *b_mean = b_s/n; + break; + } + case PIXFORMAT_RGB888: { + for (int i=0; ipixels)[i]; + r_s += COLOR_RGB888_TO_R8(pixel24232(p)); + g_s += COLOR_RGB888_TO_G8(pixel24232(p)); + b_s += COLOR_RGB888_TO_B8(pixel24232(p)); + } + *r_mean = r_s/n; + *g_mean = g_s/n; + *b_mean = b_s/n; + break; + } + default: { + break; + } + } + + return 0; +} + +// One pass standard deviation. +int imlib_image_std(image_t *src) +{ + int w=src->w; + int h=src->h; + int n=w*h; + uint8_t *data=src->pixels; + + uint32_t s=0, sq=0; + for (int i=0; iw * sizeof(*buffer) * 2, FB_ALLOC_NO_HINT); + + // NOTE: This doesn't deal with borders right now. Adding if + // statements in the inner loop will slow it down significantly. + for (int y=0; yh-ksize; y++) { + for (int x=0; xw; x++) { + int acc=0; + //if (IM_X_INSIDE(img, x+k) && IM_Y_INSIDE(img, y+j)) + acc = __SMLAD(krn[0], IM_GET_GS_PIXEL(img, x, y + 0), acc); + acc = __SMLAD(krn[1], IM_GET_GS_PIXEL(img, x, y + 1), acc); + acc = __SMLAD(krn[2], IM_GET_GS_PIXEL(img, x, y + 2), acc); + buffer[((y%2)*img->w) + x] = acc; + } + if (y > 0) { + // flush buffer + for (int x=0; xw-ksize; x++) { + int acc = 0; + acc = __SMLAD(krn[0], buffer[((y-1)%2) * img->w + x + 0], acc); + acc = __SMLAD(krn[1], buffer[((y-1)%2) * img->w + x + 1], acc); + acc = __SMLAD(krn[2], buffer[((y-1)%2) * img->w + x + 2], acc); + acc = (acc * m) + b; // scale, offset, and clamp + acc = IM_MAX(IM_MIN(acc, IM_MAX_GS), 0); + IM_SET_GS_PIXEL(img, (x+1), (y), acc); + } + } + } + fb_free(buffer); +} + + + + +// 下面的函数是 maixpy 的一些函数 有一些定制函数 + + +typedef struct xylf +{ + int16_t x, y, l, r; +} +xylf_t; + + +size_t __imlib_flood_fill_int(image_t *out, image_t *img, int x, int y, + int seed_threshold, int floating_threshold, + flood_fill_call_back_t cb, void *data) +{ + lifo_t lifo; + size_t lifo_len; + lifo_alloc_all(&lifo, &lifo_len, sizeof(xylf_t)); + size_t count = 0; + + for(int seed_pixel = IMAGE_GET_GRAYSCALE_PIXEL(img, x, y);;) { + int left = x, right = x; + uint8_t *row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint32_t *out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y); + + while ((left > 0) + && (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, left - 1)) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, left - 1), seed_pixel, seed_threshold) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, left - 1), + IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, left), floating_threshold)) { + left--; + } + + while ((right < (img->w - 1)) + && (!IMAGE_GET_BINARY_PIXEL_FAST(out_row, right + 1)) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, right + 1), seed_pixel, seed_threshold) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, right + 1), + IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, right), floating_threshold)) { + right++; + } + + for (int i = left; i <= right; i++) { + IMAGE_SET_BINARY_PIXEL_FAST(out_row, i); + } + count += (right-left+1); + + bool break_out = false; + for(;;) { + if (lifo_size(&lifo) < lifo_len) { + uint8_t *old_row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + + if (y > 0) { + row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y - 1); + out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y - 1); + + bool recurse = false; + for (int i = left; i <= right; i++) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), seed_pixel, seed_threshold) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), + IMAGE_GET_GRAYSCALE_PIXEL_FAST(old_row, i), floating_threshold)) { + xylf_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + lifo_enqueue(&lifo, &context); + x = i; + y = y - 1; + recurse = true; + break; + } + } + if (recurse) { + break; + } + } + + if (y < (img->h - 1)) { + row = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y + 1); + out_row = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(out, y + 1); + + bool recurse = false; + for (int i = left; i <= right; i++) { + if ((!IMAGE_GET_BINARY_PIXEL_FAST(out_row, i)) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), seed_pixel, seed_threshold) + && COLOR_BOUND_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row, i), + IMAGE_GET_GRAYSCALE_PIXEL_FAST(old_row, i), floating_threshold)) { + xylf_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + lifo_enqueue(&lifo, &context); + x = i; + y = y + 1; + recurse = true; + break; + } + } + if (recurse) { + break; + } + } + } + + if (cb) cb(img, y, left, right, data); + + if (!lifo_size(&lifo)) { + break_out = true; + break; + } + + xylf_t context; + lifo_dequeue(&lifo, &context); + x = context.x; + y = context.y; + left = context.l; + right = context.r; + } + + if (break_out) { + break; + } + } + + + lifo_free(&lifo); + return count; +} + + + +// #ifdef IMLIB_ENABLE_FLOOD_FILL + +size_t __imlib_flood_fill(image_t *img, int x, int y, + float seed_threshold, float floating_threshold, + int c, bool invert, bool clear_background, image_t *mask) +{ + size_t count = 0; + if ((0 <= x) && (x < img->w) && (0 <= y) && (y < img->h)) { + image_t out; + out.w = img->w; + out.h = img->h; + out.pixfmt = PIXFORMAT_BINARY; + out.data = fb_alloc0(image_size(&out), FB_ALLOC_NO_HINT); + + if (mask) { + for (int y = 0, yy = out.h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&out, y); + for (int x = 0, xx = out.w; x < xx; x++) { + if (image_get_mask_pixel(mask, x, y)) IMAGE_SET_BINARY_PIXEL_FAST(row_ptr, x); + } + } + } + + int color_seed_threshold = 0; + int color_floating_threshold = 0; + + switch(img->pixfmt) { + // case PIXFORMAT_BINARY: { + // color_seed_threshold = fast_roundf(seed_threshold * COLOR_BINARY_MAX); + // color_floating_threshold = fast_roundf(floating_threshold * COLOR_BINARY_MAX); + // break; + // } + case PIXFORMAT_GRAYSCALE: { + color_seed_threshold = fast_roundf(seed_threshold * COLOR_GRAYSCALE_MAX); + color_floating_threshold = fast_roundf(floating_threshold * COLOR_GRAYSCALE_MAX); + break; + } + // case PIXFORMAT_RGB565: { + // color_seed_threshold = COLOR_R5_G6_B5_TO_RGB565(fast_roundf(seed_threshold * COLOR_R5_MAX), + // fast_roundf(seed_threshold * COLOR_G6_MAX), + // fast_roundf(seed_threshold * COLOR_B5_MAX)); + // color_floating_threshold = COLOR_R5_G6_B5_TO_RGB565(fast_roundf(floating_threshold * COLOR_R5_MAX), + // fast_roundf(floating_threshold * COLOR_G6_MAX), + // fast_roundf(floating_threshold * COLOR_B5_MAX)); + // break; + // } + default: { + break; + } + } + + count = __imlib_flood_fill_int(&out, img, x, y, color_seed_threshold, color_floating_threshold, NULL, NULL); + + switch(img->bpp) { + // case PIXFORMAT_BINARY: { + // for (int y = 0, yy = out.h; y < yy; y++) { + // uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + // uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&out, y); + // for (int x = 0, xx = out.w; x < xx; x++) { + // if (IMAGE_GET_BINARY_PIXEL_FAST(out_row_ptr, x) ^ invert) { + // IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, c); + // } else if (clear_background) { + // IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, 0); + // } + // } + // } + // break; + // } + case PIXFORMAT_GRAYSCALE: { + for (int y = 0, yy = out.h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&out, y); + for (int x = 0, xx = out.w; x < xx; x++) { + if (IMAGE_GET_BINARY_PIXEL_FAST(out_row_ptr, x) ^ invert) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, c); + } else if (clear_background) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, 0); + } + } + } + break; + } + // case PIXFORMAT_RGB565: { + // for (int y = 0, yy = out.h; y < yy; y++) { + // uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + // uint32_t *out_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(&out, y); + // for (int x = 0, xx = out.w; x < xx; x++) { + // if (IMAGE_GET_BINARY_PIXEL_FAST(out_row_ptr, x) ^ invert) { + // IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, c); + // } else if (clear_background) { + // IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, 0); + // } + // } + // } + // break; + // } + default: { + break; + } + } + + fb_free(out.data); + } + return count; +} +// #endif // IMLIB_ENABLE_FLOOD_FILL + + + +// #ifndef OMV_MINIMUM +//输入灰度图像,找出面积最大的连通域 +#define MAX_DOMAIN_CNT 254 + +static void _get_hv_pixel(image_t* img, uint16_t* h0, uint16_t* h1, \ + uint16_t* h2, uint16_t* v0, uint16_t* v1, uint16_t* v2){ + uint8_t* data = img->pixels; + uint16_t w = img->w; + uint16_t h = img->h; + + uint16_t minx=w; + uint16_t miny=h; + uint16_t maxx=0; + uint16_t maxy=0; + uint16_t _h1=0; uint16_t _h2=0; uint16_t _v1=0; uint16_t _v2=0; + + for (int y = 0, yy = img->h; y < yy; y++) { + //uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + uint32_t *img_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + if (IMAGE_GET_BINARY_PIXEL_FAST(img_row_ptr, x) ) { + if(ymaxy) maxy=y; + if(xmaxx) maxx=x; + if(y==0) _h1+=1; else if(y==h-1) _h2+=1; + if(x==0) _v1+=1; else if(x==w-1) _v2+=1; + } + } + } + + *h0 = maxx-minx+1; + *v0 = maxy-miny+1; + *h1 = _h1; *h2 = _h2; + *v1 = _v1; *v2 = _v2; + + return; +} + + + +#define EDGE_GATE 0.7 +void imlib_find_domain(image_t* img, image_t** dst, float edge_gate) +{ + if(img->pixfmt != PIXFORMAT_GRAYSCALE) + imlib_printf(0, "only support grayscale pic"); + + uint16_t w = img->w; + uint16_t h = img->h; + uint8_t* pic = img->pixels; + + uint16_t domian_cnt[MAX_DOMAIN_CNT]; + uint8_t p_x[MAX_DOMAIN_CNT]; + uint8_t p_y[MAX_DOMAIN_CNT]; + for(int i=0; i < MAX_DOMAIN_CNT; i++) { + domian_cnt[i] = 0; + } + + uint8_t color_idx = 0; + + for(int y=0; y=MAX_DOMAIN_CNT) + break; + for(int x=0; x color_idx){ + domian_cnt[color_idx] = __imlib_flood_fill(img, x, y, 0, 0,color_idx+1, 0, 0, NULL); + p_x[color_idx] = x; + p_y[color_idx] = y; + //printf("(%d, %d) -> %d, count=%d\r\n",x,y,color_idx+1,domian_cnt[color_idx]); + color_idx += 1; + + } + } + } + + int maxcnt = 0; + int maxidx = -1; + int max2cnt = 0; + int max2idx = -1; + for(int i=0; i< MAX_DOMAIN_CNT; i++){ + if(domian_cnt[i]>maxcnt) { + max2cnt= maxcnt; + max2idx= maxidx; + maxcnt = domian_cnt[i]; + maxidx = i; + } else if(domian_cnt[i]>max2cnt) { + max2cnt = domian_cnt[i]; + max2idx = i; + } + } + //printf("max domain idx=%d, cnt=%d\r\n",maxidx, maxcnt); + if(maxcnt == 0) { //没有连通域 + *dst = NULL; + return ; + } else if (max2cntw, img->h, PIXFORMAT_BINARY, NULL, NULL, true); + + __imlib_flood_fill_int(out, img, p_x[maxidx], p_y[maxidx], 0, 0, NULL, NULL); + *dst = out; + return ; + } else { //有超过两块较大的连通域 + image_t out0, out1; + out0.w = out1.w = img->w; + out0.h = out1.h = img->h; + out0.pixfmt = out1.pixfmt = PIXFORMAT_BINARY; + out0.data = fb_alloc0(image_size(&out0), FB_ALLOC_NO_HINT); + out1.data = fb_alloc0(image_size(&out1), FB_ALLOC_NO_HINT); + __imlib_flood_fill_int(&out0, img, p_x[maxidx], p_y[maxidx], 0, 0, NULL, NULL); + __imlib_flood_fill_int(&out1, img, p_x[max2idx], p_y[max2idx], 0, 0, NULL, NULL); + uint16_t r0_h0,r0_h1,r0_h2; //水平方向像素范围,上边界像素数,下边界像素数 + uint16_t r0_v0,r0_v1,r0_v2; // + uint16_t r1_h0,r1_h1,r1_h2; //水平方向像素范围,上边界像素数,下边界像素数 + uint16_t r1_v0,r1_v1,r1_v2; // + _get_hv_pixel(&out0, &r0_h0,&r0_h1,&r0_h2, &r0_v0,&r0_v1,&r0_v2); + _get_hv_pixel(&out1, &r1_h0,&r1_h1,&r1_h2, &r1_v0,&r1_v1,&r1_v2); + uint16_t r0_h, r0_v, r1_h, r1_v; + r0_h = r0_h1>r0_h2 ? r0_h1 : r0_h2; + r1_h = r1_h1>r1_h2 ? r1_h1 : r1_h2; + r0_v = r0_v1>r0_v2 ? r0_v1 : r0_v2; + r1_v = r1_v1>r1_v2 ? r1_v1 : r1_v2; + float r0_hrate, r0_vrate, r1_hrate, r1_vrate; + r0_hrate = 1.0*r0_h/r0_h0; r0_vrate = 1.0*r0_v/r0_v0; + r1_hrate = 1.0*r1_h/r1_h0; r1_vrate = 1.0*r1_v/r1_v0; + // printf("r0cnt=%04d; h0=%03d, h1=%03d, h2=%03d; v0=%03d, v1=%03d, v2=%03d; hrate=%.3f, vrate=%.3f\r\n", + // maxcnt, r0_h0, r0_h1, r0_h2, r0_v0, r0_v1, r0_v2, r0_hrate, r0_vrate ); + // printf("r1cnt=%04d; h0=%03d, h1=%03d, h2=%03d; v0=%03d, v1=%03d, v2=%03d; hrate=%.3f, vrate=%.3f\r\n", + // max2cnt, r1_h0, r1_h1, r1_h2, r1_v0, r1_v1, r1_v2, r1_hrate, r1_vrate ); + int r_flag = -1; + if(r0_hrate < edge_gate || r0_vrate < edge_gate) { //r0是线 + r_flag = 0; //使用r0 + } else if(r1_hrate < edge_gate || r1_vrate < edge_gate) { //r0不是线,r1是线 + r_flag = 1; + } else { //两个都不是线,选面积大的 + r_flag = 0; + } + // printf("rflag=%d\r\n\r\n", r_flag); + + image_t *out = imlib_image_create(w, h, PIXFORMAT_BINARY, NULL, NULL, true); + // image_copy(out, r_flag ? &out1 : &out0); + memcpy(out->data, r_flag?out1.data:out0.data, image_size(&out0)); + // uint8_t* data = xalloc(image_size(&out0)); + // mp_obj_t image = py_image(w, h, PIXFORMAT_BINARY, data); + // memcpy(((py_image_obj_t *)image)->_cobj.data, r_flag?out1.data:out0.data, image_size(&out0)); + fb_free(NULL); + fb_free(NULL); + *dst = out; + return ; + } +} \ No newline at end of file diff --git a/github_source/minicv2/src/imlib_io.c b/github_source/minicv2/src/imlib_io.c new file mode 100644 index 0000000..6271e78 --- /dev/null +++ b/github_source/minicv2/src/imlib_io.c @@ -0,0 +1,89 @@ +#include "imlib_io.h" +#include +#include +#include +#include "imlib_config.h" + +#ifdef IMLIB_ENABLE_PRINT + +static int _out_level = 1; + +int imlib_printf(int level, char *fmt, ...) +{ + static char sprint_buf[1024]; + + va_list args; + int n = 0; + if (level > _out_level) return n; + va_start(args, fmt); + n = vsprintf(sprint_buf, fmt, args); + va_end(args); + switch (level) + { + case 0: + fprintf(stderr, "%s", sprint_buf); + abort(); + break; + case 1: + fprintf(stdout, "%s", sprint_buf); + break; + case 2: + fprintf(stdout, "%s", sprint_buf); + break; + case 3: + fprintf(stdout, "%s", sprint_buf); + break; + case 4: + fprintf(stdout, "%s", sprint_buf); + break; + case 5: + fprintf(stdout, "%s", sprint_buf); + break; + default: + break; + } + return n; +} + +void imlib_set_print_out_level(int le) +{ + _out_level = le; +} + +#else + +int imlib_printf(int level, char *fmt, ...) +{ + +} + +void imlib_set_print_out_level(int le) +{ + +} + +#endif + +#include "imlib.h" + +void imlib_printf_image_info(void *img) +{ + image_t *imlib_img = (image_t *)img; + switch (imlib_img->pixfmt) + { + case PIXFORMAT_GRAYSCALE: + printf("imlib image info:\n\t\twidth:%d height:%d mode:PIXFORMAT_GRAYSCALE size:%d pixels:%p is_data_alloc:%d \n", imlib_img->w, imlib_img->h, imlib_img->size, imlib_img->pixels, imlib_img->is_data_alloc); + break; + case PIXFORMAT_RGB565: + printf("imlib image info:\n\t\twidth:%d height:%d mode:PIXFORMAT_RGB565 size:%d pixels:%p is_data_alloc:%d \n", imlib_img->w, imlib_img->h, imlib_img->size, imlib_img->pixels, imlib_img->is_data_alloc); + break; + case PIXFORMAT_RGB888: + printf("imlib image info:\n\t\twidth:%d height:%d mode:PIXFORMAT_RGB888 size:%d pixels:%p is_data_alloc:%d \n", imlib_img->w, imlib_img->h, imlib_img->size, imlib_img->pixels, imlib_img->is_data_alloc); + break; + default: + printf("image is not info\n"); + break; + } +} + + diff --git a/github_source/minicv2/src/imlib_resize.c b/github_source/minicv2/src/imlib_resize.c new file mode 100644 index 0000000..6ab1073 --- /dev/null +++ b/github_source/minicv2/src/imlib_resize.c @@ -0,0 +1,743 @@ +// #include +// // ********************************************************************* +// // * 函数名称: BspDouToFix +// // * 功能描述: 将指定的浮点数 转化为 定点数 +// // * 算法描述: 无 +// // * 输入参数: ucType 0表示无符号 1表示有符号 +// // * ucInteger 表示整数占几个bit +// // * ucdecimal 表示小数占几个bit +// // * dbDou 为待转化的浮点数 +// // * 输出参数: 无 +// // * 返 回 值: 转化后的定点数 +// // ********************************************************************* + +// #define VOID void +// #define UCHAR int +// #define DOUBLE double +// #define UINT64 uint64_t + +// VOID BspDouToFix(UCHAR ucType, UCHAR ucInteger, UCHAR ucdecimal, DOUBLE dbDou, UINT64 *pllfix) +// { +// UINT64 lltemp = 0; +// DOUBLE dbtemp = 0; + +// dbtemp = dbDou; +// if(dbtemp < 0) /* 有符号正数 或者 无符号数 */ +// { +// lltemp = (UINT64)(-dbDou*(1< 0) /* 有符号负数 */ +// { +// *pllfix = (UINT64)(dbDou * (1<h / dst->h; + float w_scale = 1.f * src->w / dst->w; + + if (hist & IMAGE_HINT_AREA) //临近插值 + { + switch (src->pixfmt) + { + case PIXFORMAT_BINARY:{ + uint32_t *dst32, *src32; + for (dst_y_index = 0; dst_y_index < dst->h; dst_y_index ++) + { + dst32 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, dst_y_index); + src_y_index = GET_SITE_PIXEL_FAST(dst_y_index, h_scale); + src32 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src, src_y_index); + for (dst_x_index = 0; dst_x_index < dst->w; dst_x_index ++) + { + src_x_index = GET_SITE_PIXEL_FAST(dst_x_index, w_scale); + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, dst_x_index, IMAGE_GET_BINARY_PIXEL_FAST(src32, src_x_index)); + } + } + break; + } + case PIXFORMAT_GRAYSCALE:{ + uint8_t *dst8, *src8; + for (dst_y_index = 0; dst_y_index < dst->h; dst_y_index ++) + { + dst8 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(dst, dst_y_index); + src_y_index = GET_SITE_PIXEL_FAST(dst_y_index, h_scale); + src8 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src, src_y_index); + for (dst_x_index = 0; dst_x_index < dst->w; dst_x_index ++) + { + int src_x_index = GET_SITE_PIXEL_FAST(dst_x_index, w_scale); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst8, dst_x_index, IMAGE_GET_GRAYSCALE_PIXEL_FAST(src8, src_x_index)); + } + } + break; + } + case PIXFORMAT_RGB565:{ + uint16_t *dst16, *src16; + for (dst_y_index = 0; dst_y_index < dst->h; dst_y_index ++) + { + dst16 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, dst_y_index); + src_y_index = GET_SITE_PIXEL_FAST(dst_y_index, h_scale); + src16 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src, src_y_index); + for (dst_x_index = 0; dst_x_index < dst->w; dst_x_index ++) + { + src_x_index = GET_SITE_PIXEL_FAST(dst_x_index, w_scale); + IMAGE_PUT_RGB565_PIXEL_FAST(dst16, dst_x_index, IMAGE_GET_RGB565_PIXEL_FAST(src16, src_x_index)); + } + } + break; + } + case PIXFORMAT_RGB888:{ + pixel24_t *dst24, *src24; + for (dst_y_index = 0; dst_y_index < dst->h; dst_y_index ++) + { + dst24 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(dst, dst_y_index); + src_y_index = GET_SITE_PIXEL_FAST(dst_y_index, h_scale); + src24 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src, src_y_index); + for (dst_x_index = 0; dst_x_index < dst->w; dst_x_index ++) + { + src_x_index = GET_SITE_PIXEL_FAST(dst_x_index, w_scale); + IMAGE_PUT_RGB888_PIXEL_FAST_(dst24, dst_x_index, IMAGE_GET_RGB888_PIXEL_FAST_(src24, src_x_index)); + } + } + break; + } + default: + { + return; + } + } + }else if(hist & IMAGE_HINT_BILINEAR){ //双线性缩放 + // +---+---+ + // | C | x | + // +---+---+ + // | x | x | + // +---+---+ + switch (src->pixfmt){ + case PIXFORMAT_BINARY:{ + //For binary images, linear interpolation does not make much sense + int src_x_index, src_y_index; + uint32_t *dst32, *src32; + for (dst_y_index = 0; dst_y_index < dst->h; dst_y_index ++){ + dst32 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, dst_y_index); + src_y_index = GET_SITE_PIXEL_FAST(dst_y_index, h_scale); + src32 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src, src_y_index); + for (dst_x_index = 0; dst_x_index < dst->w; dst_x_index ++){ + src_x_index = GET_SITE_PIXEL_FAST(dst_x_index, w_scale); + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, dst_x_index, IMAGE_GET_BINARY_PIXEL_FAST(src32, src_x_index)); + } + } + break; + } + case PIXFORMAT_GRAYSCALE:{ + uint8_t *src_row_ptr_0, *src_row_ptr_1; + int w_limit = src->w - 2; + int h_limit = src->h - 2; + for (dst_y_index = 0; dst_y_index < dst->h; ++ dst_y_index){ + uint8_t *dst_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(dst, dst_y_index); + float f_src_y_index = (float)((dst_y_index + 0.5) * h_scale - 0.5); + src_y_index = fast_floorf(f_src_y_index); + f_src_y_index = f_src_y_index - src_y_index; + if(src_y_index <= 0){ + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src, 0); + } + else if(src_y_index >= h_limit){ + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src, src_y_index); + } + else{ + int src_y_index_p_1 = src_y_index + 1; + src_row_ptr_0 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src, src_y_index); + src_row_ptr_1 = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src, src_y_index_p_1); + } + short cbufy[2]; + cbufy[0] = IM_MAX(IM_MIN((1.f - f_src_y_index) * 2048, 2048), 0); + cbufy[1] = 2048 - cbufy[0]; + for (dst_x_index = 0; dst_x_index < dst->w; ++ dst_x_index){ + float f_src_x_index = (float)((dst_x_index + 0.5) * w_scale - 0.5); + src_x_index = fast_floorf(f_src_x_index); + f_src_x_index = f_src_x_index - src_x_index; + short cbufx[2]; + cbufx[0] = IM_MAX(IM_MIN((1.f - f_src_x_index) * 2048, 2048), 0); + cbufx[1] = 2048 - cbufx[0]; + int pixel_00, pixel_10, pixel_01, pixel_11; + if (src_x_index < 0) { + pixel_00 = pixel_10 = src_row_ptr_0[0]; + pixel_01 = pixel_11 = src_row_ptr_1[0]; + } else if (src_x_index >= w_limit) { + pixel_00 = pixel_10 = src_row_ptr_0[src_x_index]; + pixel_01 = pixel_11 = src_row_ptr_1[src_x_index]; + } else { // get 4 neighboring pixels + int src_x_index_p_1 = src_x_index + 1; + pixel_00 = src_row_ptr_0[src_x_index]; pixel_10 = src_row_ptr_0[src_x_index_p_1]; + pixel_01 = src_row_ptr_1[src_x_index]; pixel_11 = src_row_ptr_1[src_x_index_p_1]; + } + int pixel = (pixel_00 * cbufx[0] * cbufy[0] + + pixel_01 * cbufx[0] * cbufy[1] + + pixel_10 * cbufx[1] * cbufy[0] + + pixel_11 * cbufx[1] * cbufy[1]) >> 22; + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + } + case PIXFORMAT_RGB565:{ + uint16_t *src_row_ptr_0, *src_row_ptr_1; + int w_limit = src->w - 2; + int h_limit = src->h - 2; + for (dst_y_index = 0; dst_y_index < dst->h; ++ dst_y_index){ + uint16_t *dst_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, dst_y_index); + float f_src_y_index = (float)((dst_y_index + 0.5) * h_scale - 0.5); + src_y_index = fast_floorf(f_src_y_index); + f_src_y_index = f_src_y_index - src_y_index; + if(src_y_index <= 0){ + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src, 0); + } + else if(src_y_index >= h_limit){ + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src, src_y_index); + } + else{ + int src_y_index_p_1 = src_y_index + 1; + src_row_ptr_0 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src, src_y_index); + src_row_ptr_1 = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src, src_y_index_p_1); + } + short cbufy[2]; + cbufy[0] = IM_MAX(IM_MIN((1.f - f_src_y_index) * 2048, 2048), 0); + cbufy[1] = 2048 - cbufy[0]; + for (dst_x_index = 0; dst_x_index < dst->w; ++ dst_x_index){ + float f_src_x_index = (float)((dst_x_index + 0.5) * w_scale - 0.5); + src_x_index = fast_floorf(f_src_x_index); + f_src_x_index = f_src_x_index - src_x_index; + short cbufx[2]; + cbufx[0] = IM_MAX(IM_MIN((1.f - f_src_x_index) * 2048, 2048), 0); + cbufx[1] = 2048 - cbufx[0]; + + uint16_t pixel_00, pixel_10, pixel_01, pixel_11; + + if (src_x_index <= 0) { + pixel_00 = pixel_10 = src_row_ptr_0[0]; + pixel_01 = pixel_11 = src_row_ptr_1[0]; + } else if (src_x_index >= w_limit) { + pixel_00 = pixel_10 = src_row_ptr_0[src_x_index]; + pixel_01 = pixel_11 = src_row_ptr_1[src_x_index]; + } else { // get 4 neighboring pixels + int src_x_index_p_1 = src_x_index + 1; + pixel_00 = src_row_ptr_0[src_x_index]; pixel_10 = src_row_ptr_0[src_x_index_p_1]; + pixel_01 = src_row_ptr_1[src_x_index]; pixel_11 = src_row_ptr_1[src_x_index_p_1]; + } + int pixel_r = (COLOR_RGB565_TO_R8(pixel_00) * cbufx[0] * cbufy[0] + + COLOR_RGB565_TO_R8(pixel_01) * cbufx[0] * cbufy[1] + + COLOR_RGB565_TO_R8(pixel_10) * cbufx[1] * cbufy[0] + + COLOR_RGB565_TO_R8(pixel_11) * cbufx[1] * cbufy[1]) >> 22; + int pixel_g = (COLOR_RGB565_TO_G8(pixel_00) * cbufx[0] * cbufy[0] + + COLOR_RGB565_TO_G8(pixel_01) * cbufx[0] * cbufy[1] + + COLOR_RGB565_TO_G8(pixel_10) * cbufx[1] * cbufy[0] + + COLOR_RGB565_TO_G8(pixel_11) * cbufx[1] * cbufy[1]) >> 22; + int pixel_b = (COLOR_RGB565_TO_B8(pixel_00) * cbufx[0] * cbufy[0] + + COLOR_RGB565_TO_B8(pixel_01) * cbufx[0] * cbufy[1] + + COLOR_RGB565_TO_B8(pixel_10) * cbufx[1] * cbufy[0] + + COLOR_RGB565_TO_B8(pixel_11) * cbufx[1] * cbufy[1]) >> 22; + IMAGE_PUT_RGB565_PIXEL_FAST(dst_row_ptr, dst_x_index, COLOR_R8_G8_B8_TO_RGB565(pixel_r, pixel_g, pixel_b)); + } + } + break; + } + case PIXFORMAT_RGB888:{ + pixel24_t *src_row_ptr_0, *src_row_ptr_1; + int w_limit = src->w - 2; + int h_limit = src->h - 2; + for (dst_y_index = 0; dst_y_index < dst->h; ++ dst_y_index){ + pixel24_t *dst_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(dst, dst_y_index); + float f_src_y_index = (float)((dst_y_index + 0.5) * h_scale - 0.5); + src_y_index = fast_floorf(f_src_y_index); + f_src_y_index = f_src_y_index - src_y_index; + if(src_y_index <= 0){ + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src, 0); + } + else if(src_y_index >= h_limit){ + src_row_ptr_0 = src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src, src_y_index); + } + else{ + int src_y_index_p_1 = src_y_index + 1; + src_row_ptr_0 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src, src_y_index); + src_row_ptr_1 = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src, src_y_index_p_1); + } + short cbufy[2]; + cbufy[0] = IM_MAX(IM_MIN((1.f - f_src_y_index) * 2048, 2048), 0); + cbufy[1] = 2048 - cbufy[0]; + for (dst_x_index = 0; dst_x_index < dst->w; ++ dst_x_index){ + float f_src_x_index = (float)((dst_x_index + 0.5) * w_scale - 0.5); + src_x_index = fast_floorf(f_src_x_index); + f_src_x_index = f_src_x_index - src_x_index; + short cbufx[2]; + cbufx[0] = IM_MAX(IM_MIN((1.f - f_src_x_index) * 2048, 2048), 0); + cbufx[1] = 2048 - cbufx[0]; + pixel24_t pixel_00, pixel_10, pixel_01, pixel_11; + if (src_x_index <= 0) { + pixel_00 = pixel_10 = src_row_ptr_0[0]; + pixel_01 = pixel_11 = src_row_ptr_1[0]; + } else if (src_x_index >= w_limit) { + pixel_00 = pixel_10 = src_row_ptr_0[src_x_index]; + pixel_01 = pixel_11 = src_row_ptr_1[src_x_index]; + } else { // get 4 neighboring pixels + int src_x_index_p_1 = src_x_index + 1; + pixel_00 = src_row_ptr_0[src_x_index]; pixel_10 = src_row_ptr_0[src_x_index_p_1]; + pixel_01 = src_row_ptr_1[src_x_index]; pixel_11 = src_row_ptr_1[src_x_index_p_1]; + } + int pixel_r = (pixel_00.red * cbufx[0] * cbufy[0] + + pixel_01.red * cbufx[0] * cbufy[1] + + pixel_10.red * cbufx[1] * cbufy[0] + + pixel_11.red * cbufx[1] * cbufy[1]) >> 22; + int pixel_g = (pixel_00.green * cbufx[0] * cbufy[0] + + pixel_01.green * cbufx[0] * cbufy[1] + + pixel_10.green * cbufx[1] * cbufy[0] + + pixel_11.green * cbufx[1] * cbufy[1]) >> 22; + int pixel_b = (pixel_00.blue * cbufx[0] * cbufy[0] + + pixel_01.blue * cbufx[0] * cbufy[1] + + pixel_10.blue * cbufx[1] * cbufy[0] + + pixel_11.blue * cbufx[1] * cbufy[1]) >> 22; + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr, dst_x_index, COLOR_R8_G8_B8_TO_RGB888(pixel_r, pixel_g, pixel_b)); + } + } + break; + } + default:{ + return; + } + } + + }else if(hist & IMAGE_HINT_BICUBIC){ //三线性缩放 + // +---+---+---+---+ + // | x | x | x | x | + // +---+---+---+---+ + // | x | C | x | x | + // +---+---+---+---+ + // | x | x | x | x | + // +---+---+---+---+ + // | x | x | x | x | + // +---+---+---+---+ + //目前忽略这个实现 + switch (src->pixfmt) + { + case PIXFORMAT_BINARY:{ + int src_x_index, src_y_index; + uint32_t *dst32, *src32; + for (dst_y_index = 0; dst_y_index < dst->h; dst_y_index ++) + { + dst32 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, dst_y_index); + src_y_index = GET_SITE_PIXEL_FAST(dst_y_index, h_scale); + src32 = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src, src_y_index); + for (dst_x_index = 0; dst_x_index < dst->w; dst_x_index ++) + { + src_x_index = GET_SITE_PIXEL_FAST(dst_x_index, w_scale); + IMAGE_PUT_BINARY_PIXEL_FAST(dst32, dst_x_index, IMAGE_GET_BINARY_PIXEL_FAST(src32, src_x_index)); + } + } + break; + } + case PIXFORMAT_GRAYSCALE:{ + + + break; + } + case PIXFORMAT_RGB565:{ + + + break; + } + case PIXFORMAT_RGB888:{ + + + break; + } + + default: + break; + } + } + +#undef GET_SITE_PIXEL_FAST +} + +/* +//https://www.cnblogs.com/GoldBeetle/archive/2018/09/17/9662871.html +int is_in_array(short x, short y, short height, short width) +{ + if (x >= 0 && x < width && y >= 0 && y < height) + return 1; + else + return 0; +} + +void bilinera_interpolation(short** in_array, short height, short width, + short** out_array, short out_height, short out_width) +{ + double h_times = (double)out_height / (double)height, + w_times = (double)out_width / (double)width; + short x1, y1, x2, y2, f11, f12, f21, f22; + double x, y; + + for (int i = 0; i < out_height; i++){ + for (int j = 0; j < out_width; j++){ + x = j / w_times; + y = i / h_times; + x1 = (short)(x - 1); + x2 = (short)(x + 1); + y1 = (short)(y + 1); + y2 = (short)(y - 1); + f11 = is_in_array(x1, y1, height, width) ? in_array[y1][x1] : 0; + f12 = is_in_array(x1, y2, height, width) ? in_array[y2][x1] : 0; + f21 = is_in_array(x2, y1, height, width) ? in_array[y1][x2] : 0; + f22 = is_in_array(x2, y2, height, width) ? in_array[y2][x2] : 0; + out_array[i][j] = (short)(((f11 * (x2 - x) * (y2 - y)) + + (f21 * (x - x1) * (y2 - y)) + + (f12 * (x2 - x) * (y - y1)) + + (f22 * (x - x1) * (y - y1))) / ((x2 - x1) * (y2 - y1))); + } + } +} + + + + + +void TruncToInt32 (int &ival, float fval) +{ + undefined ival = *(int *)&fval; + +// 提取尾数 +// 注意实际的尾数前面还有一个被省略掉的1 +int mantissa = (ival & 0x07fffff) | 0x800000; + +// 提取指数 +// 以23分界,指数大于23则左移,否则右移 +// 由于指数用偏移表示,所以23+127=150 +int exponent = 150 - ((ival >> 23) & 0xff); + +if (exponent < 0) +ival = (mantissa << -exponent); +else +ival = (mantissa >> exponent); + +// 如果小于0,则将结果取反 +if ((*(int *)&fval) & 0x80000000) +ival = -ival; +} + + +#include +#include +#include +#include +#include +using namespace std; +using namespace cv; +float BiCubicPloy(float x); +//BiCubic基函数 + +float BiCubicPloy(float x) +{ + float abs_x = abs(x);//取x的绝对值 + float a = -0.5; + if (abs_x <= 1.0) + { + return (a + 2)*pow(abs_x, 3) - (a + 3)*pow(abs_x, 2) + 1; + } + else if (abs_x < 2.0) + { + return a*pow(abs_x, 3) - 5 * a*pow(abs_x, 2) + 8 * a*abs_x - 4 * a; + } + else + return 0.0; +} + +Mat BiCubicInter(Mat &srcImage, double kx, double ky) +{ + //获取输出图像的分辨率 + int nRows = cvRound(srcImage.rows*kx); + int nCols = cvRound(srcImage.cols*ky); + Mat resultImage(nRows, nCols, srcImage.type()); + for (int i = 0; i < nRows; i++) + { + for (int j = 0; j < nCols; j++) + { + //获取目标图像(i,j)在原图中的坐标 + int xm = i / kx; + int ym = j / ky; + + //取出映射到原图的整数部分 + int xi = (int)xm; + int yi = (int)ym; + + //取出映射到原图中的点的四周的16个点的坐标 + int x0 = xi - 1; + int y0 = yi - 1; + int x1 = xi; + int y1 = yi ; + int x2 = xi + 1; + int y2 = yi + 1; + int x3 = xi + 2; + int y3 = yi + 2; + if ((x0 >= 0) && (x3 < srcImage.rows) && (y0 >= 0) && (y3 < srcImage.cols)) + { + //求出行和列所对应的系数 + float dist_x0 = BiCubicPloy(xm - x0); + float dist_x1 = BiCubicPloy(xm - x1); + float dist_x2 = BiCubicPloy(xm - x2); + float dist_x3 = BiCubicPloy(xm - x3); + float dist_y0 = BiCubicPloy(ym - y0); + float dist_y1 = BiCubicPloy(ym - y1); + float dist_y2 = BiCubicPloy(ym - y2); + float dist_y3 = BiCubicPloy(ym - y3); + + //k_i_j=k_i*k_j + float dist_x0y0 = dist_x0 * dist_y0; + float dist_x0y1 = dist_x0 * dist_y1; + float dist_x0y2 = dist_x0 * dist_y2; + float dist_x0y3 = dist_x0 * dist_y3; + float dist_x1y0 = dist_x1 * dist_y0; + float dist_x1y1 = dist_x1 * dist_y1; + float dist_x1y2 = dist_x1 * dist_y2; + float dist_x1y3 = dist_x1 * dist_y3; + float dist_x2y0 = dist_x2 * dist_y0; + float dist_x2y1 = dist_x2 * dist_y1; + float dist_x2y2 = dist_x2 * dist_y2; + float dist_x2y3 = dist_x2 * dist_y3; + float dist_x3y0 = dist_x3 * dist_y0; + float dist_x3y1 = dist_x3 * dist_y1; + float dist_x3y2 = dist_x3 * dist_y2; + float dist_x3y3 = dist_x3 * dist_y3; + + resultImage.at(i, j) = (srcImage.at(x0, y0)*dist_x0y0 + + srcImage.at(x0, y1)*dist_x0y1 + + srcImage.at(x0, y2)*dist_x0y2 + + srcImage.at(x0, y3)*dist_x0y3 + + srcImage.at(x1, y0)*dist_x1y0 + + srcImage.at(x1, y1)*dist_x1y1 + + srcImage.at(x1, y2)*dist_x1y2 + + srcImage.at(x1, y3)*dist_x1y3 + + srcImage.at(x2, y0)*dist_x2y0 + + srcImage.at(x2, y1)*dist_x2y1 + + srcImage.at(x2, y2)*dist_x2y2 + + srcImage.at(x2, y3)*dist_x2y3 + + srcImage.at(x3, y0)*dist_x3y0 + + srcImage.at(x3, y1)*dist_x3y1 + + srcImage.at(x3, y2)*dist_x3y2 + + srcImage.at(x3, y3)*dist_x3y3 ); + } + } + } + return resultImage; +} +int main() +{ + Mat srcImage = imread("lakeWater.jpg"); + if (!srcImage.data) + { + printf("image could not load...\n"); + return -1; + } + imshow("srcImage", srcImage); + Mat resultImage = BiCubicInter(srcImage, 0.5, 0.5); + imshow("resultImage", resultImage); + waitKey(0); + return 0; +} + + + + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned long DWORD; +typedef long LONG; + +typedef struct tagBITMAPFILEHEADER{ + WORD bfType; //must be 0x4D42 + WORD bfSizeLow; //the size of the whole bitmap file,low 16bits. + WORD bfSizeHigh; //the size of the whole bitmap file,high 16bits. + WORD bfReserved1; //reserved + WORD bfReserved2; //reserved + WORD bfOffBitsLow; //the sum bits of BITMAPFILEHEADER,BITMAPINFOHEADER and RGBQUAD;the index byte of the image data,low 16bits. + WORD bfOffBitsHigh; //the sum bits of BITMAPFILEHEADER,BITMAPINFOHEADER and RGBQUAD;the index byte of the image data,high 16bits. +}BITMAPFILEHEADER; + +typedef struct tagBITMAPINFOHEADER { + DWORD biSize; //the size of this struct.it is 40 bytes. + LONG biWidth; //the width of image data. the unit is pixel. + LONG biHeight; //the height of image data. the unit is pixel. + WORD biPlanes; //must be 1. + WORD biBitCount; //the bit count of each pixel.usually be 1,4,8,or 24. + DWORD biCompression; //is this image compressed.0 indicates no compression. + DWORD biSizeImage; //the size of image data, the unit is byte. + LONG biXPelsPerMeter; //resolution in X direction. + LONG biYPelsPerMeter; //resolution in Y direction. + DWORD biClrUsed; //the number of colors.0 represents the default value.(2^). + DWORD biClrImportant; //the number of important colors.0 means all important colors. +} BITMAPINFOHEADER; + +#include +#include +#include +#include + +#include "bitmapfile.h" + +#define DST_WIDTH 3840 //width of the destination image +#define DST_HEIGHT 2160 //height of the destination image + +#define WIDTHBYTE(bits) ((bits+31)/32*4) + +void NearestInterpolation(double dSrcX,double dSrcY,int iDesX,int iDesY,int iSrcWidth,int iSrcHeight,BYTE* pSrcMemData,BYTE* pDesMemData,int iSrcMemWidth,int iDesMemWidth); + +void main() +{ + BITMAPFILEHEADER SrcFileHead, DesFileHead; + BITMAPINFOHEADER SrcInfoHead, DesInfoHead; + + FILE* pSrcFile=0; + FILE* pDesFile=0; + + //open source image file + errno_t SrcFileOpenError; + SrcFileOpenError = fopen_s(&pSrcFile,"../SrcPic.bmp","rb"); + if (SrcFileOpenError != 0) + { + printf("fail to open source image file"); + return; + } + + //read bit map file header + fread(&SrcFileHead,sizeof(BITMAPFILEHEADER),1,pSrcFile); + if (SrcFileHead.bfType != 0x4D42) + { + printf("source file is not bmp file"); + return; + } + //read bit map information header + fread(&SrcInfoHead,sizeof(BITMAPINFOHEADER),1,pSrcFile); + + //allocate memory space for source image + int SrcWidth = SrcInfoHead.biWidth; + int SrcHeight = SrcInfoHead.biHeight; + //the actual number of bytes per row of the source image + int SrcWidthMemByte = WIDTHBYTE(SrcWidth * SrcInfoHead.biBitCount); + int SrcTotalMemByte = SrcHeight * SrcWidthMemByte; + BYTE* pSrcData = (BYTE*)malloc(SrcTotalMemByte); + memset(pSrcData,0,SrcTotalMemByte); + fread(pSrcData, 1, SrcTotalMemByte, pSrcFile); + + //allocate memory space for destination image + int DesWidth = DST_WIDTH; + int DesHeight = DST_HEIGHT; + //the actual number of bytes per row of the destination image + int DesWidthMemByte = WIDTHBYTE(DesWidth* SrcInfoHead.biBitCount); + int DesTotalMemByte = DesHeight * DesWidthMemByte; + BYTE* pDesData = (BYTE*)malloc(DesTotalMemByte); + memset(pDesData,0,DesTotalMemByte); + + //width and height ratio of source image and destination image + double dFactorWidth = (double)SrcWidth / DesWidth; + double dFactorHeight = (double)SrcHeight / DesHeight; + + //interpolation + for(int y = 0; y < DesHeight; y++) + for (int x = 0; x < DesWidth; x++) + { + double dSrcImageX = x * dFactorWidth; + double dSrcImageY = y * dFactorHeight; + //nearest interpolation + NearestInterpolation(dSrcImageX, dSrcImageY, x, y, SrcWidth, SrcHeight, pSrcData, pDesData, SrcWidthMemByte, DesWidthMemByte); + } + + //destination image bit map file header + DesFileHead = SrcFileHead; + DWORD bfSize = (SrcFileHead.bfOffBitsHigh << 16) + SrcFileHead.bfOffBitsLow + DesTotalMemByte; + DesFileHead.bfSizeLow = bfSize & 0xFFFF; + DesFileHead.bfSizeHigh = bfSize >> 16; + //destination image bit map information header + DesInfoHead = SrcInfoHead; + DesInfoHead.biWidth = DesWidth; + DesInfoHead.biHeight = DesHeight; + DesInfoHead.biSizeImage = DesTotalMemByte; + //open destination image file + errno_t DesFileOpenError; + DesFileOpenError = fopen_s(&pDesFile, "../DesPic.bmp", "wb"); + if (DesFileOpenError != 0) + { + printf("fail to open destination image file"); + return; + } + //write destination image into bitmapfile + fwrite(&DesFileHead,1,sizeof(BITMAPFILEHEADER), pDesFile); + fwrite(&DesInfoHead,1,sizeof(BITMAPINFOHEADER), pDesFile); + fwrite(pDesData,1, DesTotalMemByte, pDesFile); + + fclose(pSrcFile); + fclose(pDesFile); + + printf("finish image scale\n"); + return; +} + +void NearestInterpolation(double dSrcX,double dSrcY,int iDesX,int iDesY,int iSrcWidth,int iSrcHeight,BYTE* pSrcMemData,BYTE* pDesMemData,int iSrcMemWidth,int iDesMemWidth) +{ + //source image pixel position + int iSrcX = dSrcX + 0.5; + int iSrcY = dSrcY + 0.5; + //image boundary processing + if (iSrcX > (iSrcWidth-1)) + { + iSrcX = iSrcWidth - 1; + } + if (iSrcY > (iSrcHeight - 1)) + { + iSrcY = iSrcHeight - 1; + } + int iSrcPixelPoint = iSrcY * iSrcMemWidth + iSrcX * 3; + + //destination image + int iDesPixelPoint = iDesY * iDesMemWidth + iDesX * 3; + pDesMemData[iDesPixelPoint] = pSrcMemData[iSrcPixelPoint]; + pDesMemData[iDesPixelPoint+1] = pSrcMemData[iSrcPixelPoint+1]; + pDesMemData[iDesPixelPoint+2] = pSrcMemData[iSrcPixelPoint+2]; + + return; +} +*/ \ No newline at end of file diff --git a/github_source/minicv2/src/ini.c b/github_source/minicv2/src/ini.c new file mode 100644 index 0000000..991de5e --- /dev/null +++ b/github_source/minicv2/src/ini.c @@ -0,0 +1,793 @@ +#ifdef ENABLE_OPENMV_INI + +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Initialization file parser. + */ +#include +#include +#include +#include "ini.h" + +/*------------------------------------------------------------------------- + _isppace.c - part of ctype.h + + Written By - Sandeep Dutta . sandeep.dutta@usa.net (1999) + + This library is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by the + Free Software Foundation; either version 2, 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 Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this program; if not, write to the Free Software + Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + In other words, you are welcome to use, share and improve this program. + You are forbidden to forbid anyone else to use, share and improve + what you give them. Help stamp out software-hoarding! +-------------------------------------------------------------------------*/ + +char ini_isspace (unsigned char c) +{ + if ( c == ' ' + || c == '\f' + || c == '\n' + || c == '\r' + || c == '\t' + || c == '\v' ) + return 1; + + return 0; +} + +/* + * atoi.c -- + * + * Source code for the "atoi" library procedure. + * + * Copyright 1988 Regents of the University of California + * Permission to use, copy, modify, and distribute this + * software and its documentation for any purpose and without + * fee is hereby granted, provided that the above copyright + * notice appear in all copies. The University of California + * makes no representations about the suitability of this + * software for any purpose. It is provided "as is" without + * express or implied warranty. + */ + +/* + *---------------------------------------------------------------------- + * + * atoi -- + * + * Convert an ASCII string into an integer. + * + * Results: + * The return value is the integer equivalent of string. If there + * are no decimal digits in string, then 0 is returned. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +ini_atoi(string) + const char *string; /* String of ASCII digits, possibly + * preceded by white space. For bases + * greater than 10, either lower- or + * upper-case digits may be used. + */ +{ + register int result = 0; + register unsigned int digit; + int sign; + + /* + * Skip any leading blanks. + */ + + while (ini_isspace(*string)) { + string += 1; + } + + /* + * Check for a sign. + */ + + if (*string == '-') { + sign = 1; + string += 1; + } else { + sign = 0; + if (*string == '+') { + string += 1; + } + } + + for (;*string; string++) { + digit = *string - '0'; + if ((digit < 0) || (digit > 9)) { + break; + } + result = (10*result) + digit; + } + + if (sign) { + return -result; + } + return result; +} + +bool ini_is_true(const char *value) +{ + int i = ini_atoi(value); + if (i) return true; + if (strlen(value) != 4) return false; + if ((value[0] != 'T') && (value[0] != 't')) return false; + if ((value[1] != 'R') && (value[1] != 'r')) return false; + if ((value[2] != 'U') && (value[2] != 'u')) return false; + if ((value[3] != 'E') && (value[3] != 'e')) return false; + return true; +} + +/* + * Copyright (c) 2000-2002 Opsycon AB (www.opsycon.se) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Opsycon AB. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + * + */ + +/** ini_fgetc(fp) -- get char from stream */ + +// int +// ini_fgetc(FIL *fp) +// { +// char c; +// UINT b; + +// if (f_read (fp, &c, 1, &b) != FR_OK || b != 1) +// return (EOF); +// return (c); +// } + +/* + * Copyright (c) 2000-2002 Opsycon AB (www.opsycon.se) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Opsycon AB. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. + * + */ + +/** char *ini_fgets(dst,max,fp) -- get string from stream */ + +char * +ini_fgets(char *dst, int max, FIL *fp) +{ + char *p; + int c=EOF; + + /* get max bytes or upto a newline */ + + for (p = dst, max--; max > 0; max--) { + if ((c = fgetc(*fp)) == EOF) + break; + *p++ = c; + if (c == '\n') + break; + } + *p = 0; + if (p == dst || c == EOF) + return NULL; + return (p); +} + +/* inih -- simple .INI file parser + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include + +#if !INI_USE_STACK +#include +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Used by ini_parse_string() to keep track of string parsing state. */ +typedef struct { + const char* ptr; + size_t num_left; +} ini_parse_string_ctx; + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && ini_isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && ini_isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to null at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = ini_isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + strncpy(dest, src, size - 1); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; + int max_line = INI_MAX_LINE; +#else + char* line; + int max_line = INI_INITIAL_ALLOC; +#endif +#if INI_ALLOW_REALLOC + char* new_line; + int offset; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)xalloc(INI_INITIAL_ALLOC); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, max_line, stream) != NULL) { +#if INI_ALLOW_REALLOC + offset = strlen(line); + while (offset == max_line - 1 && line[offset - 1] != '\n') { + max_line *= 2; + if (max_line > INI_MAX_LINE) + max_line = INI_MAX_LINE; + new_line = xrealloc(line, max_line); + if (!new_line) { + xfree(line); + return -2; + } + line = new_line; + if (reader(line + offset, max_line - offset, stream) == NULL) + break; + if (max_line >= INI_MAX_LINE) + break; + offset += strlen(line + offset); + } +#endif + + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (*start == ';' || *start == '#') { + /* Per Python configparser, allow both ; and # comments at the + start of a line */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + value = lskip(value); + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + xfree(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FIL* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)ini_fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(FATFS *fs, const char* filename, ini_handler handler, void* user) +{ + FIL file; + int error; + + FRESULT res = f_open(fs, &file, filename, FA_READ | FA_OPEN_EXISTING); + if (res != FR_OK) + return -1; + error = ini_parse_file(&file, handler, user); + res = f_close(&file); + if (res != FR_OK) + return -1; + return error; +} + +/* An ini_reader function to read the next line from a string buffer. This + is the ini_fgets() equivalent used by ini_parse_string(). */ +static char* ini_reader_string(char* str, int num, void* stream) { + ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; + const char* ctx_ptr = ctx->ptr; + size_t ctx_num_left = ctx->num_left; + char* strp = str; + char c; + + if (ctx_num_left == 0 || num < 2) + return NULL; + + while (num > 1 && ctx_num_left != 0) { + c = *ctx_ptr++; + ctx_num_left--; + *strp++ = c; + if (c == '\n') + break; + num--; + } + + *strp = '\0'; + ctx->ptr = ctx_ptr; + ctx->num_left = ctx_num_left; + return str; +} + +/* See documentation in header file. */ +int ini_parse_string(const char* string, ini_handler handler, void* user) { + ini_parse_string_ctx ctx; + + ctx.ptr = string; + ctx.num_left = strlen(string); + return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, + user); +} + +#else +/** + * Copyright (c) 2016 rxi + * + * 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. + */ +//还未接入到 openmv 的环境中,但是可以先使用 linux 环境 + +#include +#include +#include +#include + +#include "ini.h" + +struct ini_t { + char *data; + char *end; +}; + + +/* Case insensitive string compare */ +static int strcmpci(const char *a, const char *b) { + for (;;) { + int d = tolower(*a) - tolower(*b); + if (d != 0 || !*a) { + return d; + } + a++, b++; + } +} + +/* Returns the next string in the split data */ +static char* next(ini_t *ini, char *p) { + p += strlen(p); + while (p < ini->end && *p == '\0') { + p++; + } + return p; +} + +static void trim_back(ini_t *ini, char *p) { + while (p >= ini->data && (*p == ' ' || *p == '\t' || *p == '\r')) { + *p-- = '\0'; + } +} + +static char* discard_line(ini_t *ini, char *p) { + while (p < ini->end && *p != '\n') { + *p++ = '\0'; + } + return p; +} + + +static char *unescape_quoted_value(ini_t *ini, char *p) { + /* Use `q` as write-head and `p` as read-head, `p` is always ahead of `q` + * as escape sequences are always larger than their resultant data */ + char *q = p; + p++; + while (p < ini->end && *p != '"' && *p != '\r' && *p != '\n') { + if (*p == '\\') { + /* Handle escaped char */ + p++; + switch (*p) { + default : *q = *p; break; + case 'r' : *q = '\r'; break; + case 'n' : *q = '\n'; break; + case 't' : *q = '\t'; break; + case '\r' : + case '\n' : + case '\0' : goto end; + } + + } else { + /* Handle normal char */ + *q = *p; + } + q++, p++; + } +end: + return q; +} + + +/* Splits data in place into strings containing section-headers, keys and + * values using one or more '\0' as a delimiter. Unescapes quoted values */ +static void split_data(ini_t *ini) { + char *value_start, *line_start; + char *p = ini->data; + + while (p < ini->end) { + switch (*p) { + case '\r': + case '\n': + case '\t': + case ' ': + *p = '\0'; + /* Fall through */ + + case '\0': + p++; + break; + + case '[': + p += strcspn(p, "]\n"); + *p = '\0'; + break; + + case ';': + p = discard_line(ini, p); + break; + + default: + line_start = p; + p += strcspn(p, "=\n"); + + /* Is line missing a '='? */ + if (*p != '=') { + p = discard_line(ini, line_start); + break; + } + trim_back(ini, p - 1); + + /* Replace '=' and whitespace after it with '\0' */ + do { + *p++ = '\0'; + } while (*p == ' ' || *p == '\r' || *p == '\t'); + + /* Is a value after '=' missing? */ + if (*p == '\n' || *p == '\0') { + p = discard_line(ini, line_start); + break; + } + + if (*p == '"') { + /* Handle quoted string value */ + value_start = p; + p = unescape_quoted_value(ini, p); + + /* Was the string empty? */ + if (p == value_start) { + p = discard_line(ini, line_start); + break; + } + + /* Discard the rest of the line after the string value */ + p = discard_line(ini, p); + + } else { + /* Handle normal value */ + p += strcspn(p, "\n"); + trim_back(ini, p - 1); + } + break; + } + } +} + + + +ini_t* ini_load(const char *filename) { + ini_t *ini = NULL; + FILE *fp = NULL; + int n, sz; + + /* Init ini struct */ + ini = malloc(sizeof(*ini)); + if (!ini) { + goto fail; + } + memset(ini, 0, sizeof(*ini)); + + /* Open file */ + fp = fopen(filename, "rb"); + if (!fp) { + goto fail; + } + + /* Get file size */ + fseek(fp, 0, SEEK_END); + sz = ftell(fp); + rewind(fp); + + /* Load file content into memory, null terminate, init end var */ + ini->data = malloc(sz + 1); + ini->data[sz] = '\0'; + ini->end = ini->data + sz; + n = fread(ini->data, 1, sz, fp); + if (n != sz) { + goto fail; + } + + /* Prepare data */ + split_data(ini); + + /* Clean up and return */ + fclose(fp); + return ini; + +fail: + if (fp) fclose(fp); + if (ini) ini_free(ini); + return NULL; +} + + +void ini_free(ini_t *ini) { + free(ini->data); + free(ini); +} + + +const char* ini_get(ini_t *ini, const char *section, const char *key) { + char *current_section = ""; + char *val; + char *p = ini->data; + + if (*p == '\0') { + p = next(ini, p); + } + + while (p < ini->end) { + if (*p == '[') { + /* Handle section */ + current_section = p + 1; + + } else { + /* Handle key */ + val = next(ini, p); + if (!section || !strcmpci(section, current_section)) { + if (!strcmpci(p, key)) { + return val; + } + } + p = val; + } + + p = next(ini, p); + } + + return NULL; +} + + +int ini_sget( + ini_t *ini, const char *section, const char *key, + const char *scanfmt, void *dst +) { + const char *val = ini_get(ini, section, key); + if (!val) { + return 0; + } + if (scanfmt) { + sscanf(val, scanfmt, dst); + } else { + *((const char**) dst) = val; + } + return 1; +} + + +#endif \ No newline at end of file diff --git a/github_source/minicv2/src/integral.c b/github_source/minicv2/src/integral.c new file mode 100644 index 0000000..bfcd870 --- /dev/null +++ b/github_source/minicv2/src/integral.c @@ -0,0 +1,118 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Integral image. + */ +#include +#include +// #include +#include "imlib.h" +#include "fb_alloc.h" + +void imlib_integral_image_alloc(i_image_t *sum, int w, int h) +{ + sum->w = w; + sum->h = h; + sum->data = fb_alloc(w * h * sizeof(*sum->data), FB_ALLOC_NO_HINT); +} + +void imlib_integral_image_free(i_image_t *sum) +{ + // 1 allocation + fb_free(sum->data); +} + +void imlib_integral_image(image_t *src, i_image_t *sum) +{ + typeof(*src->data) *img_data = src->data; + typeof(*sum->data) *sum_data = sum->data; + + // Compute first column to avoid branching + for (int s=0, x=0; xw; x++) { + /* sum of the current row (integer) */ + s += img_data[x]; + sum_data[x] = s; + } + + for (int y=1; yh; y++) { + /* loop over the number of columns */ + for (int s=0, x=0; xw; x++) { + /* sum of the current row (integer) */ + s += img_data[y*src->w+x]; + sum_data[y*src->w+x] = s+sum_data[(y-1)*src->w+x]; + } + } +} + +void imlib_integral_image_scaled(image_t *src, i_image_t *sum) +{ + typeof(*src->data) *img_data = src->data; + typeof(*sum->data) *sum_data = sum->data; + + int x_ratio = (int)((src->w<<16)/sum->w) +1; + int y_ratio = (int)((src->h<<16)/sum->h) +1; + + // Compute first column to avoid branching + for (int s=0, x=0; xw; x++) { + int sx = (x*x_ratio)>>16; + /* sum of the current row (integer) */ + s += img_data[sx]; + sum_data[x] = s; + } + + for (int y=1; yh; y++) { + int sy = (y*y_ratio)>>16; + /* loop over the number of columns */ + for (int s=0, x=0; xw; x++) { + int sx = (x*x_ratio)>>16; + + /* sum of the current row (integer) */ + s += img_data[sy*src->w+sx]; + sum_data[y*sum->w+x] = s+sum_data[(y-1)*sum->w+x]; + } + } +} + +void imlib_integral_image_sq(image_t *src, i_image_t *sum) +{ + typeof(*src->data) *img_data = src->data; + typeof(*sum->data) *sum_data = sum->data; + + // Compute first column to avoid branching + for (uint32_t s=0, x=0; xw; x++) { + /* sum of the current row (integer) */ + s += img_data[x] * img_data[x]; + sum_data[x] = s; + } + + for (uint32_t y=1; yh; y++) { + /* loop over the number of columns */ + for (uint32_t s=0, x=0; xw; x++) { + /* sum of the current row (integer) */ + s += img_data[y*src->w+x] * img_data[y*src->w+x]; + sum_data[y*src->w+x] = s+sum_data[(y-1)*src->w+x]; + } + } + +} + +uint32_t imlib_integral_lookup(i_image_t *sum, int x, int y, int w, int h) +{ +#define PIXEL_AT(x,y)\ + (sum->data[((y)-1)*sum->w+((x)-1)]) + if (x==0 && y==0) { + return PIXEL_AT(w,h); + } else if (y==0) { + return PIXEL_AT(w+x, h) - PIXEL_AT(x, h); + } else if (x==0) { + return PIXEL_AT(w, h+y) - PIXEL_AT(w, y); + } else { + return PIXEL_AT(w+x, h+y) + PIXEL_AT(x, y) - PIXEL_AT(w+x, y) - PIXEL_AT(x, h+y); + } +#undef PIXEL_AT +} diff --git a/github_source/minicv2/src/integral_mw.c b/github_source/minicv2/src/integral_mw.c new file mode 100644 index 0000000..2ba4929 --- /dev/null +++ b/github_source/minicv2/src/integral_mw.c @@ -0,0 +1,313 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * An integral image using a moving window. + * + * The high level steps are: + * 1) Start with an array of pointers[n] where n = feature height. + * 2) Compute the first n lines of the integral image. + * 3) Do some processing with the integral image. + * 4) Call integral_mw_image_shift(n) + * + * This will shift the pointers by n and calculate n new lines, example: + * Assuming feature height is 4: + * mw_i_image[0] -> mem[0] + * mw_i_image[1] -> mem[1] + * mw_i_image[2] -> mem[2] + * mw_i_image[3] -> mem[3] + * + * After shifting by 1 line, it looks like this: + * mw_i_image[0] -> mem[1] + * mw_i_image[1] -> mem[2] + * mw_i_image[2] -> mem[3] + * mw_i_image[3] -> mem[0] + * Line 3 will be computed as normal using line 2 which now + * points to the last integral image line computed initially. + * + * After shifting by second line, it looks like this: + * mw_i_image[0] -> mem[2] + * mw_i_image[1] -> mem[3] + * mw_i_image[2] -> mem[0] + * mw_i_image[3] -> mem[1] + * Line 3 will be computed as usual using line 2 which now + * points to the last integral image line computed in the previous shift. + * + * Notes: + * The mw integral must Not be shifted more than image_height - feature_height, s_lines + * must be < feature_height-1 to keep at least one row for integral image calculations. + * + * This only requires (image_width * (feature_height+1) * 4) bytes. Assuming a 24x24 + * feature, the required memory is 320*25*4 (i.e. ~32KBs) instead of 320*240*4 (300KBs). + * + * Functions without a suffix compute/shift summed images, _sq suffix compute/shift + * summed squared images, and _ss compute/shift both summed and squared in a single pass. + */ +#include +#include +#include +#include "imlib.h" +// #include "fb_alloc.h" + +// This macro swaps two pointers. +#define SWAP_PTRS(a, b) \ + ({ __typeof__ (a) _t;\ + (_t) = ( a); \ + ( a) = ( b); \ + ( b) = (_t); }) + +void imlib_integral_mw_alloc(mw_image_t *sum, int w, int h) +{ + sum->w = w; + sum->h = h; + sum->y_offs = 0; + sum->x_ratio = (1<<16)+1; + sum->y_ratio = (1<<16)+1; + sum->data = xalloc(h * sizeof(*sum->data)); + // swap is used when shifting the image pointers + // to avoid overwriting the image rows in sum->data + sum->swap = xalloc(h * sizeof(*sum->data)); + + for (int i=0; idata[i] = xalloc(w * sizeof(**sum->data)); + } +} + +void imlib_integral_mw_free(mw_image_t *sum) +{ + for (int i=0; ih; i++) { + xfree(sum->data[i]); // Free h lines + } + xfree(sum->data); // Free data + xfree(sum->swap); // Free swap +} + +void imlib_integral_mw_scale(rectangle_t *roi, mw_image_t *sum, int w, int h) +{ + // Set new width + // Note: height doesn't change + sum->w = w; + // Reset y offset + sum->y_offs = 0; + // Set scaling ratios + sum->x_ratio = (int)((roi->w<<16)/w)+1; + sum->y_ratio = (int)((roi->h<<16)/h)+1; +} + +void imlib_integral_mw(image_t *src, mw_image_t *sum) +{ + // Image pointers + typeof(*sum->data) *sum_data = sum->data; + + // Compute the first row to avoid branching + for (int sx, s=0, x=0; xw; x++) { + // X offset + sx = (x*sum->x_ratio)>>16; + + // Accumulate row data + s += IM_TO_GS_PIXEL(src, sx, 0); + sum_data[0][x] = s; + } + + // Compute the remaining rows + for (int sy, y=1; yh; y++) { + // Y offset + sy = (y*sum->y_ratio)>>16; + + // Sum the current row + for (int sx, s=0, x=0; xw; x++) { + // X offset + sx = (x*sum->x_ratio)>>16; + + // Accumulate row data + s += IM_TO_GS_PIXEL(src, sx, sy); + sum_data[y][x] = s + sum_data[y-1][x]; + } + } + + sum->y_offs = sum->h; +} + +void imlib_integral_mw_sq(image_t *src, mw_image_t *sum) +{ + // Image pointers + typeof(*sum->data) *sum_data = sum->data; + + // Compute the first row to avoid branching + for (int sx, s=0, x=0; xw; x++) { + // X offset + sx = (x*sum->x_ratio)>>16; + + // Accumulate row data + s += IM_TO_GS_PIXEL(src, sx, 0) * IM_TO_GS_PIXEL(src, sx, 0); + sum_data[0][x] = s; + } + + // Compute the remaining rows + for (int sy, y=1; yh; y++) { + // Y offset + sy = (y*sum->y_ratio)>>16; + + // Sum the current row + for (int sx, s=0, x=0; xw; x++) { + // X offset + sx = (x*sum->x_ratio)>>16; + + // Accumulate row data + s += IM_TO_GS_PIXEL(src, sx, sy) * IM_TO_GS_PIXEL(src, sx, sy); + sum_data[y][x] = s + sum_data[y-1][x]; + } + } + + sum->y_offs = sum->h; +} + +void imlib_integral_mw_shift(image_t *src, mw_image_t *sum, int n) +{ + // Shift integral image rows by n lines + for (int y=0; yh; y++) { + sum->swap[y] = sum->data[(y+n) % sum->h]; + } + + // Swap the data and swap pointers + SWAP_PTRS(sum->data, sum->swap); + + // Pointer to the current sum data + typeof(*sum->data) *sum_data = sum->data; + + // Compute the last n lines + for (int sy, y=(sum->h - n); yh; y++, sum->y_offs++) { + // Y offset + sy = (sum->y_offs*sum->y_ratio)>>16; + + // Sum the current row + for (int sx, s=0, x=0; xw; x++) { + // X offset + sx = (x*sum->x_ratio)>>16; + + // Accumulate row data + s += IM_TO_GS_PIXEL(src, sx, sy); + sum_data[y][x] = s + sum_data[y-1][x]; + } + } +} + +void imlib_integral_mw_shift_sq(image_t *src, mw_image_t *sum, int n) +{ + // Shift integral image rows by n lines + for (int y=0; yh; y++) { + sum->swap[y] = sum->data[(y+n) % sum->h]; + } + + // Swap data and swap pointers + SWAP_PTRS(sum->data, sum->swap); + + // Pointer to the current sum data + typeof(*sum->data) *sum_data = sum->data; + + // Compute the last n lines + for (int sy, y=(sum->h - n); yh; y++, sum->y_offs++) { + // The y offset is set to the last line + 1 + sy = (sum->y_offs*sum->y_ratio)>>16; + + // Sum the current row + for (int sx, s=0, x=0; xw; x++) { + // X offset + sx = (x*sum->x_ratio)>>16; + + // Accumulate row data + s += IM_TO_GS_PIXEL(src, sx, sy) * IM_TO_GS_PIXEL(src, sx, sy); + sum_data[y][x] = (s + sum_data[y-1][x]); + } + } +} + +void imlib_integral_mw_ss(image_t *src, mw_image_t *sum, mw_image_t *ssq, rectangle_t *roi) +{ + // Image data pointers + typeof(*sum->data) *sum_data = sum->data; + typeof(*sum->data) *ssq_data = ssq->data; + + // Compute the first row to avoid branching + for (int sx, s=0, sq=0, x=0; xw; x++) { + // X offset + sx = roi->x+((x*sum->x_ratio)>>16); + + // Accumulate row data + s += IM_TO_GS_PIXEL(src, sx, roi->y); + sq += IM_TO_GS_PIXEL(src, sx, roi->y) * IM_TO_GS_PIXEL(src, sx, roi->y); + + sum_data[0][x] = s; + ssq_data[0][x] = sq; + } + + // Compute the last n lines + for (int sy, y=1; yh; y++) { + // Y offset + sy = roi->y+((y*sum->y_ratio)>>16); + + // Sum the current row + for (int sx, s=0, sq=0, x=0; xw; x++) { + // X offset + sx = roi->x+((x*sum->x_ratio)>>16); + + // Accumulate row data + s += IM_TO_GS_PIXEL(src, sx, sy); + sq += IM_TO_GS_PIXEL(src, sx, sy) * IM_TO_GS_PIXEL(src, sx, sy); + + sum_data[y][x] = s + sum_data[y-1][x]; + ssq_data[y][x] = sq + ssq_data[y-1][x]; + } + } + + sum->y_offs = sum->h; + ssq->y_offs = sum->h; +} + +void imlib_integral_mw_shift_ss(image_t *src, mw_image_t *sum, mw_image_t *ssq, rectangle_t *roi, int n) +{ + // Shift integral image rows by n lines + for (int y=0; yh; y++) { + sum->swap[y] = sum->data[(y+n) % sum->h]; + ssq->swap[y] = ssq->data[(y+n) % ssq->h]; + } + + // Swap the data and swap pointers + SWAP_PTRS(sum->data, sum->swap); + SWAP_PTRS(ssq->data, ssq->swap); + + // Pointer to the current sum and ssq data + typeof(*sum->data) *sum_data = sum->data; + typeof(*ssq->data) *ssq_data = ssq->data; + + // Compute the last n lines + for (int sy, y=(sum->h - n); yh; y++, sum->y_offs++, ssq->y_offs++) { + // The y offset is set to the last line + 1 + sy = roi->y+((sum->y_offs*sum->y_ratio)>>16); + + // Sum of the current row + for (int sx, s=0, sq=0, x=0; xw; x++) { + // X offset + sx = roi->x+((x*sum->x_ratio)>>16); + + // Accumulate row data + s += IM_TO_GS_PIXEL(src, sx, sy); + sq += IM_TO_GS_PIXEL(src, sx, sy) * IM_TO_GS_PIXEL(src, sx, sy); + + sum_data[y][x] = s + sum_data[y-1][x]; + ssq_data[y][x] = sq + ssq_data[y-1][x]; + } + } +} + +long imlib_integral_mw_lookup(mw_image_t *sum, int x, int y, int w, int h) +{ +#define PIXEL_AT(x,y)\ + (sum->data[(y)][x]) + return PIXEL_AT(w+x, h+y) + PIXEL_AT(x, y) - PIXEL_AT(w+x, y) - PIXEL_AT(x, h+y); +#undef PIXEL_AT +} diff --git a/github_source/minicv2/src/isp.c b/github_source/minicv2/src/isp.c new file mode 100644 index 0000000..deb032b --- /dev/null +++ b/github_source/minicv2/src/isp.c @@ -0,0 +1,1152 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2023 Ibrahim Abdelkader + * Copyright (c) 2013-2023 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * AWB Functions + */ +#include "imlib.h" + +#ifdef IMLIB_ENABLE_ISP_OPS + +static void imlib_rgb_avg(image_t *img, uint32_t *r_out, uint32_t *g_out, uint32_t *b_out) { + uint32_t area = img->w * img->h; + uint32_t r_acc = 0, g_acc = 0, b_acc = 0; + + switch (img->pixfmt) { + case PIXFORMAT_RGB565: { + uint16_t *ptr = (uint16_t *) img->data; + long n = area; // must be signed for count down loop + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 1; n -= 2) { + uint32_t pixels = *ptr32++; + + long r = (pixels >> 11) & 0x1F001F; + r_acc = __USADA8(r, 0, r_acc); + + long g = (pixels >> 5) & 0x3F003F; + g_acc = __USADA8(g, 0, g_acc); + + long b = pixels & 0x1F001F; + b_acc = __USADA8(b, 0, b_acc); + } + + ptr = (uint16_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + int pixel = *ptr++; + r_acc += COLOR_RGB565_TO_R5(pixel); + g_acc += COLOR_RGB565_TO_G6(pixel); + b_acc += COLOR_RGB565_TO_B5(pixel); + } + + break; + } + case PIXFORMAT_BAYER_BGGR: { + uint8_t *ptr = (uint8_t *) img->data; + for (int y = 0, yy = img->h; y < yy; y++) { + if (y % 2) { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long g = __UXTB16_RORn(pixels, 0); + g_acc = __USADA8(g, 0, g_acc); + + long b = __UXTB16_RORn(pixels, 8); + b_acc = __USADA8(b, 0, b_acc); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + b_acc += *ptr++; + } else { + g_acc += *ptr++; + } + } + } else { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long r = __UXTB16_RORn(pixels, 0); + r_acc = __USADA8(r, 0, r_acc); + + long g = __UXTB16_RORn(pixels, 8); + g_acc = __USADA8(g, 0, g_acc); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + g_acc += *ptr++; + } else { + r_acc += *ptr++; + } + } + } + } + + break; + } + case PIXFORMAT_BAYER_GBRG: { + uint8_t *ptr = (uint8_t *) img->data; + for (int y = 0, yy = img->h; y < yy; y++) { + if (y % 2) { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long b = __UXTB16_RORn(pixels, 0); + b_acc = __USADA8(b, 0, b_acc); + + long g = __UXTB16_RORn(pixels, 8); + g_acc = __USADA8(g, 0, g_acc); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + g_acc += *ptr++; + } else { + b_acc += *ptr++; + } + } + } else { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long g = __UXTB16_RORn(pixels, 0); + g_acc = __USADA8(g, 0, g_acc); + + long r = __UXTB16_RORn(pixels, 8); + r_acc = __USADA8(r, 0, r_acc); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + r_acc += *ptr++; + } else { + g_acc += *ptr++; + } + } + } + } + + break; + } + case PIXFORMAT_BAYER_GRBG: { + uint8_t *ptr = (uint8_t *) img->data; + for (int y = 0, yy = img->h; y < yy; y++) { + if (y % 2) { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long r = __UXTB16_RORn(pixels, 0); + r_acc = __USADA8(r, 0, r_acc); + + long g = __UXTB16_RORn(pixels, 8); + g_acc = __USADA8(g, 0, g_acc); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + g_acc += *ptr++; + } else { + r_acc += *ptr++; + } + } + } else { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long g = __UXTB16_RORn(pixels, 0); + g_acc = __USADA8(g, 0, g_acc); + + long b = __UXTB16_RORn(pixels, 8); + b_acc = __USADA8(b, 0, b_acc); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + b_acc += *ptr++; + } else { + g_acc += *ptr++; + } + } + } + } + + break; + } + case PIXFORMAT_BAYER_RGGB: { + uint8_t *ptr = (uint8_t *) img->data; + for (int y = 0, yy = img->h; y < yy; y++) { + if (y % 2) { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long g = __UXTB16_RORn(pixels, 0); + g_acc = __USADA8(g, 0, g_acc); + + long r = __UXTB16_RORn(pixels, 8); + r_acc = __USADA8(r, 0, r_acc); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + r_acc += *ptr++; + } else { + g_acc += *ptr++; + } + } + } else { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long b = __UXTB16_RORn(pixels, 0); + b_acc = __USADA8(b, 0, b_acc); + + long g = __UXTB16_RORn(pixels, 8); + g_acc = __USADA8(g, 0, g_acc); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + g_acc += *ptr++; + } else { + b_acc += *ptr++; + } + } + } + } + + break; + } + default: { + break; + } + } + + if (img->is_bayer) { + *r_out = ((r_acc * 4) + (area >> 1)) / area; + *g_out = ((g_acc * 2) + (area >> 1)) / area; + *b_out = ((b_acc * 4) + (area >> 1)) / area; + } else { + *r_out = ((r_acc * 2) + (area >> 1)) / area; + *g_out = (g_acc + (area >> 1)) / area; + *b_out = ((b_acc * 2) + (area >> 1)) / area; + } +} + +static void imlib_rgb_max(image_t *img, uint32_t *r_out, uint32_t *g_out, uint32_t *b_out) { + uint32_t area = img->w * img->h; + uint32_t r_acc = 0, g_acc = 0, b_acc = 0; + + switch (img->pixfmt) { + case PIXFORMAT_RGB565: { + uint16_t *ptr = (uint16_t *) img->data; + long n = area; // must be signed for count down loop + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 1; n -= 2) { + uint32_t pixels = *ptr32++; + + long r = (pixels >> 11) & 0x1F001F; + long r_tmp = __USUB8(r, r_acc); (void) r_tmp; + r_acc = __SEL(r, r_acc); + + long g = (pixels >> 5) & 0x3F003F; + long g_tmp = __USUB8(g, g_acc); (void) g_tmp; + g_acc = __SEL(g, g_acc); + + long b = pixels & 0x1F001F; + long b_tmp = __USUB8(b, b_acc); (void) b_tmp; + b_acc = __SEL(b, b_acc); + } + + long r_tmp = r_acc >> 16; + long r_tmp2 = __USUB8(r_tmp, r_acc); (void) r_tmp2; + r_acc = __SEL(r_tmp, r_acc) & 0xff; + + long g_tmp = g_acc >> 16; + long g_tmp2 = __USUB8(g_tmp, g_acc); (void) g_tmp2; + g_acc = __SEL(g_tmp, g_acc) & 0xff; + + long b_tmp = b_acc >> 16; + long b_tmp2 = __USUB8(b_tmp, b_acc); (void) b_tmp2; + b_acc = __SEL(b_tmp, b_acc) & 0xff; + + ptr = (uint16_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + int pixel = *ptr++; + int r = COLOR_RGB565_TO_R5(pixel); + r_acc = (r > r_acc) ? r : r_acc; + int g = COLOR_RGB565_TO_G6(pixel); + g_acc = (g > g_acc) ? g : g_acc; + int b = COLOR_RGB565_TO_B5(pixel); + b_acc = (b > b_acc) ? b : b_acc; + } + + break; + } + case PIXFORMAT_BAYER_BGGR: { + uint8_t *ptr = (uint8_t *) img->data; + for (int y = 0, yy = img->h; y < yy; y++) { + if (y % 2) { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long g = __UXTB16_RORn(pixels, 0); + long g_tmp = __USUB8(g, g_acc); (void) g_tmp; + g_acc = __SEL(g, g_acc); + + long b = __UXTB16_RORn(pixels, 8); + long b_tmp = __USUB8(b, b_acc); (void) b_tmp; + b_acc = __SEL(b, b_acc); + } + + long g_tmp = g_acc >> 16; + long g_tmp2 = __USUB8(g_tmp, g_acc); (void) g_tmp2; + g_acc = __SEL(g_tmp, g_acc) & 0xff; + + long b_tmp = b_acc >> 16; + long b_tmp2 = __USUB8(b_tmp, b_acc); (void) b_tmp2; + b_acc = __SEL(b_tmp, b_acc) & 0xff; + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + int b = *ptr++; + b_acc = (b > b_acc) ? b : b_acc; + } else { + int g = *ptr++; + g_acc = (g > g_acc) ? g : g_acc; + } + } + } else { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long r = __UXTB16_RORn(pixels, 0); + long r_tmp = __USUB8(r, r_acc); (void) r_tmp; + r_acc = __SEL(r, r_acc); + + long g = __UXTB16_RORn(pixels, 8); + long g_tmp = __USUB8(g, g_acc); (void) g_tmp; + g_acc = __SEL(g, g_acc); + } + + long r_tmp = r_acc >> 16; + long r_tmp2 = __USUB8(r_tmp, r_acc); (void) r_tmp2; + r_acc = __SEL(r_tmp, r_acc) & 0xff; + + long g_tmp = g_acc >> 16; + long g_tmp2 = __USUB8(g_tmp, g_acc); (void) g_tmp2; + g_acc = __SEL(g_tmp, g_acc) & 0xff; + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + int g = *ptr++; + g_acc = (g > g_acc) ? g : g_acc; + } else { + int r = *ptr++; + r_acc = (r > r_acc) ? r : r_acc; + } + } + } + } + + break; + } + case PIXFORMAT_BAYER_GBRG: { + uint8_t *ptr = (uint8_t *) img->data; + for (int y = 0, yy = img->h; y < yy; y++) { + if (y % 2) { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long b = __UXTB16_RORn(pixels, 0); + long b_tmp = __USUB8(b, b_acc); (void) b_tmp; + b_acc = __SEL(b, b_acc); + + long g = __UXTB16_RORn(pixels, 8); + long g_tmp = __USUB8(g, g_acc); (void) g_tmp; + g_acc = __SEL(g, g_acc); + } + + long b_tmp = b_acc >> 16; + long b_tmp2 = __USUB8(b_tmp, b_acc); (void) b_tmp2; + b_acc = __SEL(b_tmp, b_acc) & 0xff; + + long g_tmp = g_acc >> 16; + long g_tmp2 = __USUB8(g_tmp, g_acc); (void) g_tmp2; + g_acc = __SEL(g_tmp, g_acc) & 0xff; + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + int g = *ptr++; + g_acc = (g > g_acc) ? g : g_acc; + } else { + int b = *ptr++; + b_acc = (b > b_acc) ? b : b_acc; + } + } + } else { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long g = __UXTB16_RORn(pixels, 0); + long g_tmp = __USUB8(g, g_acc); (void) g_tmp; + g_acc = __SEL(g, g_acc); + + long r = __UXTB16_RORn(pixels, 8); + long r_tmp = __USUB8(r, r_acc); (void) r_tmp; + r_acc = __SEL(r, r_acc); + } + + long g_tmp = g_acc >> 16; + long g_tmp2 = __USUB8(g_tmp, g_acc); (void) g_tmp2; + g_acc = __SEL(g_tmp, g_acc) & 0xff; + + long r_tmp = r_acc >> 16; + long r_tmp2 = __USUB8(r_tmp, r_acc); (void) r_tmp2; + r_acc = __SEL(r_tmp, r_acc) & 0xff; + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + int r = *ptr++; + r_acc = (r > r_acc) ? r : r_acc; + } else { + int g = *ptr++; + g_acc = (g > g_acc) ? g : g_acc; + } + } + } + } + + break; + } + case PIXFORMAT_BAYER_GRBG: { + uint8_t *ptr = (uint8_t *) img->data; + for (int y = 0, yy = img->h; y < yy; y++) { + if (y % 2) { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long r = __UXTB16_RORn(pixels, 0); + long r_tmp = __USUB8(r, r_acc); (void) r_tmp; + r_acc = __SEL(r, r_acc); + + long g = __UXTB16_RORn(pixels, 8); + long g_tmp = __USUB8(g, g_acc); (void) g_tmp; + g_acc = __SEL(g, g_acc); + } + + long r_tmp = r_acc >> 16; + long r_tmp2 = __USUB8(r_tmp, r_acc); (void) r_tmp2; + r_acc = __SEL(r_tmp, r_acc) & 0xff; + + long g_tmp = g_acc >> 16; + long g_tmp2 = __USUB8(g_tmp, g_acc); (void) g_tmp2; + g_acc = __SEL(g_tmp, g_acc) & 0xff; + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + int g = *ptr++; + g_acc = (g > g_acc) ? g : g_acc; + } else { + int r = *ptr++; + r_acc = (r > r_acc) ? r : r_acc; + } + } + } else { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long g = __UXTB16_RORn(pixels, 0); + long g_tmp = __USUB8(g, g_acc); (void) g_tmp; + g_acc = __SEL(g, g_acc); + + long b = __UXTB16_RORn(pixels, 8); + long b_tmp = __USUB8(b, b_acc); (void) b_tmp; + b_acc = __SEL(b, b_acc); + } + + long g_tmp = g_acc >> 16; + long g_tmp2 = __USUB8(g_tmp, g_acc); (void) g_tmp2; + g_acc = __SEL(g_tmp, g_acc) & 0xff; + + long b_tmp = b_acc >> 16; + long b_tmp2 = __USUB8(b_tmp, b_acc); (void) b_tmp2; + b_acc = __SEL(b_tmp, b_acc) & 0xff; + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + int b = *ptr++; + b_acc = (b > b_acc) ? b : b_acc; + } else { + int g = *ptr++; + g_acc = (g > g_acc) ? g : g_acc; + } + } + } + } + + break; + } + case PIXFORMAT_BAYER_RGGB: { + uint8_t *ptr = (uint8_t *) img->data; + for (int y = 0, yy = img->h; y < yy; y++) { + if (y % 2) { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long g = __UXTB16_RORn(pixels, 0); + long g_tmp = __USUB8(g, g_acc); (void) g_tmp; + g_acc = __SEL(g, g_acc); + + long r = __UXTB16_RORn(pixels, 8); + long r_tmp = __USUB8(r, r_acc); (void) r_tmp; + r_acc = __SEL(r, r_acc); + } + + long g_tmp = g_acc >> 16; + long g_tmp2 = __USUB8(g_tmp, g_acc); (void) g_tmp2; + g_acc = __SEL(g_tmp, g_acc) & 0xff; + + long r_tmp = r_acc >> 16; + long r_tmp2 = __USUB8(r_tmp, r_acc); (void) r_tmp2; + r_acc = __SEL(r_tmp, r_acc) & 0xff; + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + int r = *ptr++; + r_acc = (r > r_acc) ? r : r_acc; + } else { + int g = *ptr++; + g_acc = (g > g_acc) ? g : g_acc; + } + } + } else { + int n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32++; + + long b = __UXTB16_RORn(pixels, 0); + long b_tmp = __USUB8(b, b_acc); (void) b_tmp; + b_acc = __SEL(b, b_acc); + + long g = __UXTB16_RORn(pixels, 8); + long g_tmp = __USUB8(g, g_acc); (void) g_tmp; + g_acc = __SEL(g, g_acc); + } + + long b_tmp = b_acc >> 16; + long b_tmp2 = __USUB8(b_tmp, b_acc); (void) b_tmp2; + b_acc = __SEL(b_tmp, b_acc) & 0xff; + + long g_tmp = g_acc >> 16; + long g_tmp2 = __USUB8(g_tmp, g_acc); (void) g_tmp2; + g_acc = __SEL(g_tmp, g_acc) & 0xff; + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + if (n % 2) { + int g = *ptr++; + g_acc = (g > g_acc) ? g : g_acc; + } else { + int b = *ptr++; + b_acc = (b > b_acc) ? b : b_acc; + } + } + } + } + + break; + } + default: { + break; + } + } + + if (img->is_bayer) { + *r_out = r_acc; + *g_out = g_acc; + *b_out = b_acc; + } else { + *r_out = r_acc * 2; + *g_out = g_acc; + *b_out = b_acc * 2; + } +} + +void imlib_awb(image_t *img, bool max) { + uint32_t area = img->w * img->h; + uint32_t r_out, g_out, b_out; + + if (max) { + imlib_rgb_max(img, &r_out, &g_out, &b_out); // white patch algorithm + } else { + imlib_rgb_avg(img, &r_out, &g_out, &b_out); // gray world algorithm + } + + int red_gain = IM_DIV(g_out * 32, r_out); + red_gain = IM_MIN(red_gain, 128); + int blue_gain = IM_DIV(g_out * 32, b_out); + blue_gain = IM_MIN(blue_gain, 128); + + switch (img->pixfmt) { + case PIXFORMAT_RGB565: { + uint16_t *ptr = (uint16_t *) img->data; + long n = area; // must be signed for count down loop + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 1; n -= 2) { + long pixels = *ptr32; + long r_pixels = (__USAT16(((pixels >> 11) & 0x1F001F) * red_gain, 10) << 6) & 0xF800F800; + long g_pixels = pixels & 0x7E007E0; + long b_pixels = (__USAT16((pixels & 0x1F001F) * blue_gain, 10) >> 5) & 0x1F001F; + *ptr32++ = r_pixels | g_pixels | b_pixels; + } + + ptr = (uint16_t *) ptr32; + #endif + + for (; n > 0; n -= 1) { + int pixel = *ptr; + int r = __USAT_ASR(COLOR_RGB565_TO_R5(pixel) * red_gain, 5, 5); + int g = COLOR_RGB565_TO_G6(pixel); + int b = __USAT_ASR(COLOR_RGB565_TO_B5(pixel) * blue_gain, 5, 5); + *ptr++ = COLOR_R5_G6_B5_TO_RGB565(r, g, b); + } + + break; + } + case PIXFORMAT_BAYER_BGGR: { + uint8_t *ptr = (uint8_t *) img->data; + for (int y = 0, yy = img->h; y < yy; y++) { + if (y % 2) { + long n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32; + long b = __USAT16(__UXTB16_RORn(pixels, 8) * blue_gain, 13) << 3; + long tmp = __USUB8(0xFF00FF, pixels); (void) tmp; + *ptr32++ = __SEL(pixels, b); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1, ptr++) { + if (n % 2) { + *ptr = __USAT_ASR(*ptr * blue_gain, 8, 5); + } + } + } else { + long n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32; + long r = __USAT16(__UXTB16_RORn(pixels, 0) * red_gain, 13) >> 5; + long tmp = __USUB8(0xFF00FF00, pixels); (void) tmp; + *ptr32++ = __SEL(pixels, r); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1, ptr++) { + if (!(n % 2)) { + *ptr = __USAT_ASR(*ptr * red_gain, 8, 5); + } + } + } + } + + break; + } + case PIXFORMAT_BAYER_GBRG: { + uint8_t *ptr = (uint8_t *) img->data; + for (int y = 0, yy = img->h; y < yy; y++) { + if (y % 2) { + long n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32; + long b = __USAT16(__UXTB16_RORn(pixels, 0) * blue_gain, 13) >> 5; + long tmp = __USUB8(0xFF00FF00, pixels); (void) tmp; + *ptr32++ = __SEL(pixels, b); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1, ptr++) { + if (!(n % 2)) { + *ptr = __USAT_ASR(*ptr * blue_gain, 8, 5); + } + } + } else { + long n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32; + long r = __USAT16(__UXTB16_RORn(pixels, 8) * red_gain, 13) << 3; + long tmp = __USUB8(0xFF00FF, pixels); (void) tmp; + *ptr32++ = __SEL(pixels, r); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1, ptr++) { + if (n % 2) { + *ptr = __USAT_ASR(*ptr * red_gain, 8, 5); + } + } + } + } + + break; + } + case PIXFORMAT_BAYER_GRBG: { + uint8_t *ptr = (uint8_t *) img->data; + for (int y = 0, yy = img->h; y < yy; y++) { + if (y % 2) { + long n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32; + long r = __USAT16(__UXTB16_RORn(pixels, 0) * red_gain, 13) >> 5; + long tmp = __USUB8(0xFF00FF00, pixels); (void) tmp; + *ptr32++ = __SEL(pixels, r); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1, ptr++) { + if (!(n % 2)) { + *ptr = __USAT_ASR(*ptr * red_gain, 8, 5); + } + } + } else { + long n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32; + long b = __USAT16(__UXTB16_RORn(pixels, 8) * blue_gain, 13) << 3; + long tmp = __USUB8(0xFF00FF, pixels); (void) tmp; + *ptr32++ = __SEL(pixels, b); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1, ptr++) { + if (n % 2) { + *ptr = __USAT_ASR(*ptr * blue_gain, 8, 5); + } + } + } + } + + break; + } + case PIXFORMAT_BAYER_RGGB: { + uint8_t *ptr = (uint8_t *) img->data; + for (int y = 0, yy = img->h; y < yy; y++) { + if (y % 2) { + long n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32; + long r = __USAT16(__UXTB16_RORn(pixels, 8) * red_gain, 13) << 3; + long tmp = __USUB8(0xFF00FF, pixels); (void) tmp; + *ptr32++ = __SEL(pixels, r); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1, ptr++) { + if (!(n % 2)) { + *ptr = __USAT_ASR(*ptr * red_gain, 8, 5); + } + } + } else { + long n = img->w; + + #if defined(ARM_MATH_DSP) + uint32_t *ptr32 = (uint32_t *) ptr; + + for (; n > 3; n -= 4) { + uint32_t pixels = *ptr32; + long b = __USAT16(__UXTB16_RORn(pixels, 0) * blue_gain, 13) >> 5; + long tmp = __USUB8(0xFF00FF00, pixels); (void) tmp; + *ptr32++ = __SEL(pixels, b); + } + + ptr = (uint8_t *) ptr32; + #endif + + for (; n > 0; n -= 1, ptr++) { + if (!(n % 2)) { + *ptr = __USAT_ASR(*ptr * blue_gain, 8, 5); + } + } + } + } + + break; + } + default: { + break; + } + } +} + +void imlib_ccm(image_t *img, float *ccm, bool offset) { + float rr = ccm[0], rg = ccm[3], rb = ccm[6], ro = 0.f; + float gr = ccm[1], gg = ccm[4], gb = ccm[7], go = 0.f; + float br = ccm[2], bg = ccm[5], bb = ccm[8], bo = 0.f; + + if (offset) { + ro = ccm[9]; + go = ccm[10]; + bo = ccm[11]; + } + + int i_rr = IM_MIN(fast_roundf(rr * 64), 1024); + int i_rg = IM_MIN(fast_roundf(rg * 32), 512); + int i_rb = IM_MIN(fast_roundf(rb * 64), 1024); + + int i_gr = IM_MIN(fast_roundf(gr * 64), 1024); + int i_gg = IM_MIN(fast_roundf(gg * 32), 512); + int i_gb = IM_MIN(fast_roundf(gb * 64), 1024); + + int i_br = IM_MIN(fast_roundf(br * 64), 1024); + int i_bg = IM_MIN(fast_roundf(bg * 32), 512); + int i_bb = IM_MIN(fast_roundf(bb * 64), 1024); + + int i_ro = IM_MIN(fast_roundf(ro * 64), 1024); + int i_go = IM_MIN(fast_roundf(go * 32), 512); + int i_bo = IM_MIN(fast_roundf(bo * 64), 1024); + + #if defined(ARM_MATH_DSP) + long smuad_rr_rb = __PKHBT(i_rb, i_rr, 16); + long smuad_gr_gb = __PKHBT(i_gb, i_gr, 16); + long smuad_br_bb = __PKHBT(i_bb, i_br, 16); + #endif + + switch (img->pixfmt) { + case PIXFORMAT_RGB565: { + uint16_t *ptr = (uint16_t *) img->data; + long n = img->w * img->h; // must be signed for count down loop + + if (offset) { + for (; n > 0; n -= 1) { + int pixel = *ptr; + int g = COLOR_RGB565_TO_G6(pixel); + #if defined(ARM_MATH_DSP) + // This code is only slightly faster than the non-DSP version... + int r_b = __PKHBT(pixel & 0x1F, pixel, 5); + int new_r = __USAT_ASR(__SMLAD(r_b, smuad_rr_rb, (i_rg * g) + i_ro), 5, 6); + int new_g = __USAT_ASR(__SMLAD(r_b, smuad_gr_gb, (i_gg * g) + i_go), 6, 5); + int new_b = __USAT_ASR(__SMLAD(r_b, smuad_br_bb, (i_bg * g) + i_bo), 5, 6); + #else + int r = COLOR_RGB565_TO_R5(pixel); + int b = COLOR_RGB565_TO_B5(pixel); + int new_r = __USAT_ASR((i_rr * r) + (i_rg * g) + (i_rb * b) + i_ro, 5, 6); + int new_g = __USAT_ASR((i_gr * r) + (i_gg * g) + (i_gb * b) + i_go, 6, 5); + int new_b = __USAT_ASR((i_br * r) + (i_bg * g) + (i_bb * b) + i_bo, 5, 6); + #endif + *ptr++ = COLOR_R5_G6_B5_TO_RGB565(new_r, new_g, new_b); + } + } else { + for (; n > 0; n -= 1) { + int pixel = *ptr; + int g = COLOR_RGB565_TO_G6(pixel); + #if defined(ARM_MATH_DSP) + // This code is only slightly faster than the non-DSP version... + int r_b = __PKHBT(pixel & 0x1F, pixel, 5); + int new_r = __USAT_ASR(__SMLAD(r_b, smuad_rr_rb, i_rg * g), 5, 6); + int new_g = __USAT_ASR(__SMLAD(r_b, smuad_gr_gb, i_gg * g), 6, 5); + int new_b = __USAT_ASR(__SMLAD(r_b, smuad_br_bb, i_bg * g), 5, 6); + #else + int r = COLOR_RGB565_TO_R5(pixel); + int b = COLOR_RGB565_TO_B5(pixel); + int new_r = __USAT_ASR((i_rr * r) + (i_rg * g) + (i_rb * b), 5, 6); + int new_g = __USAT_ASR((i_gr * r) + (i_gg * g) + (i_gb * b), 6, 5); + int new_b = __USAT_ASR((i_br * r) + (i_bg * g) + (i_bb * b), 5, 6); + #endif + *ptr++ = COLOR_R5_G6_B5_TO_RGB565(new_r, new_g, new_b); + } + } + + break; + } + default: { + break; + } + } +} + +void imlib_gamma(image_t *img, float gamma, float contrast, float brightness) { + gamma = IM_DIV(1.0, gamma); + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + float pScale = COLOR_BINARY_MAX - COLOR_BINARY_MIN; + float pDiv = 1 / pScale; + int *p_lut = fb_alloc((COLOR_BINARY_MAX - COLOR_BINARY_MIN + 1) * sizeof(int), FB_ALLOC_NO_HINT); + + for (int i = COLOR_BINARY_MIN; i <= COLOR_BINARY_MAX; i++) { + int p = ((fast_powf(i * pDiv, gamma) * contrast) + brightness) * pScale; + p_lut[i] = IM_MIN(IM_MAX(p, COLOR_BINARY_MIN), COLOR_BINARY_MAX); + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int dataPixel = IMAGE_GET_BINARY_PIXEL_FAST(data, x); + int p = p_lut[dataPixel]; + IMAGE_PUT_BINARY_PIXEL_FAST(data, x, p); + } + } + + fb_free(); + break; + } + case PIXFORMAT_GRAYSCALE: + case PIXFORMAT_BAYER_ANY: + case PIXFORMAT_YUV_ANY: { + float pScale = COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN; + float pDiv = 1 / pScale; + int *p_lut = fb_alloc((COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1) * sizeof(int), FB_ALLOC_NO_HINT); + + for (int i = COLOR_GRAYSCALE_MIN; i <= COLOR_GRAYSCALE_MAX; i++) { + int p = ((fast_powf(i * pDiv, gamma) * contrast) + brightness) * pScale; + p_lut[i] = IM_MIN(IM_MAX(p, COLOR_GRAYSCALE_MIN), COLOR_GRAYSCALE_MAX); + } + + uint8_t *ptr = (uint8_t *) img->data; + int n = img->w * img->h; + + if (img->bpp == 2) { + for (; n > 0; n--, ptr += 2) { + *ptr = p_lut[*ptr]; + } + } else { + for (; n > 0; n--, ptr += 1) { + *ptr = p_lut[*ptr]; + } + } + + fb_free(); + break; + } + case PIXFORMAT_RGB565: { + float rScale = COLOR_R5_MAX - COLOR_R5_MIN; + float gScale = COLOR_G6_MAX - COLOR_G6_MIN; + float bScale = COLOR_B5_MAX - COLOR_B5_MIN; + float rDiv = 1 / rScale; + float gDiv = 1 / gScale; + float bDiv = 1 / bScale; + int *r_lut = fb_alloc((COLOR_R5_MAX - COLOR_R5_MIN + 1) * sizeof(int), FB_ALLOC_NO_HINT); + int *g_lut = fb_alloc((COLOR_G6_MAX - COLOR_G6_MIN + 1) * sizeof(int), FB_ALLOC_NO_HINT); + int *b_lut = fb_alloc((COLOR_B5_MAX - COLOR_B5_MIN + 1) * sizeof(int), FB_ALLOC_NO_HINT); + + for (int i = COLOR_R5_MIN; i <= COLOR_R5_MAX; i++) { + int r = ((fast_powf(i * rDiv, gamma) * contrast) + brightness) * rScale; + r_lut[i] = IM_MIN(IM_MAX(r, COLOR_R5_MIN), COLOR_R5_MAX); + } + + for (int i = COLOR_G6_MIN; i <= COLOR_G6_MAX; i++) { + int g = ((fast_powf(i * gDiv, gamma) * contrast) + brightness) * gScale; + g_lut[i] = IM_MIN(IM_MAX(g, COLOR_G6_MIN), COLOR_G6_MAX); + } + + for (int i = COLOR_B5_MIN; i <= COLOR_B5_MAX; i++) { + int b = ((fast_powf(i * bDiv, gamma) * contrast) + brightness) * bScale; + b_lut[i] = IM_MIN(IM_MAX(b, COLOR_B5_MIN), COLOR_B5_MAX); + } + + uint16_t *ptr = (uint16_t *) img->data; + int n = img->w * img->h; + + for (; n > 0; n--) { + int dataPixel = *ptr; + int r = r_lut[COLOR_RGB565_TO_R5(dataPixel)]; + int g = g_lut[COLOR_RGB565_TO_G6(dataPixel)]; + int b = b_lut[COLOR_RGB565_TO_B5(dataPixel)]; + *ptr++ = COLOR_R5_G6_B5_TO_RGB565(r, g, b); + } + + fb_free(); + fb_free(); + fb_free(); + break; + } + default: { + break; + } + } +} + +#endif // IMLIB_ENABLE_ISP_OPS \ No newline at end of file diff --git a/github_source/minicv2/src/jpeg.c b/github_source/minicv2/src/jpeg.c new file mode 100644 index 0000000..ae22abd --- /dev/null +++ b/github_source/minicv2/src/jpeg.c @@ -0,0 +1,1948 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Minimalistic JPEG baseline encoder. + * Ported from public domain JPEG writer by Jon Olick - http://jonolick.com + * DCT implementation is based on Arai, Agui, and Nakajima's algorithm for scaled DCT. + */ +#include + +#include "ff_wrapper.h" +#include "imlib.h" +#include "omv_boardconfig.h" +#include "arm_compat.h" + +#define TIME_JPEG (0) +#if (TIME_JPEG == 1) +// #include "py/mphal.h" +#endif + +#define MCU_W (8) +#define MCU_H (8) +#define JPEG_444_GS_MCU_SIZE ((MCU_W) * (MCU_H)) +#define JPEG_444_YCBCR_MCU_SIZE ((JPEG_444_GS_MCU_SIZE) * 3) + +// Expand 4 bits to 32 for binary to grayscale - process 4 pixels at a time +#if (OMV_HARDWARE_JPEG == 1) +#define JPEG_BINARY_0 0x00 +#define JPEG_BINARY_1 0xFF +static const uint32_t jpeg_expand[16] = {0x0, 0xff, 0xff00, 0xffff, 0xff0000, + 0xff00ff, 0xffff00, 0xffffff, 0xff000000, 0xff0000ff, 0xff00ff00, + 0xff00ffff, 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff}; +#else +#define JPEG_BINARY_0 0x80 +#define JPEG_BINARY_1 0x7F +static const uint32_t jpeg_expand[16] = {0x80808080, 0x8080807f, 0x80807f80, 0x80807f7f, 0x807f8080, + 0x807f807f, 0x807f7f80, 0x807f7f7f, 0x7f808080, 0x7f80807f, 0x7f807f80, + 0x7f807f7f, 0x7f7f8080, 0x7f7f807f, 0x7f7f7f80, 0x7f7f7f7f}; +#endif + +static void jpeg_get_mcu(image_t *src, int x_offset, int y_offset, int dx, int dy, int8_t *Y0, int8_t *CB, int8_t *CR) +{ + switch (src->pixfmt) { + case PIXFORMAT_BINARY: { + if ((dx != MCU_W) || (dy != MCU_H)) { // partial MCU, fill with 0's to start + memset(Y0, 0, JPEG_444_GS_MCU_SIZE); + } + + for (int y = y_offset, yy = y + dy; y < yy; y++) { + uint32_t *rp = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src, y); + uint8_t pixels = rp[x_offset >> UINT32_T_SHIFT] >> (x_offset & UINT32_T_MASK); + + if (dx == MCU_W) { + *((uint32_t *) Y0) = jpeg_expand[pixels & 0xf]; + *(((uint32_t *) Y0) + 1) = jpeg_expand[pixels >> 4]; + } else if (dx >= 4) { + *((uint32_t *) Y0) = jpeg_expand[pixels & 0xf]; + + if (dx >= 6) { + *(((uint16_t *) Y0) + 2) = jpeg_expand[pixels >> 4]; + + if (dx & 1) { + Y0[6] = (pixels & 0x40) ? JPEG_BINARY_1 : JPEG_BINARY_0; + } + } else if (dx & 1) { + Y0[4] = (pixels & 0x10) ? JPEG_BINARY_1 : JPEG_BINARY_0; + } + } else if (dx >= 2) { + *((uint16_t *) Y0) = jpeg_expand[pixels & 0x3]; + + if (dx & 1) { + Y0[2] = (pixels & 0x4) ? JPEG_BINARY_1 : JPEG_BINARY_0; + } + } else { + *Y0 = (pixels & 0x1) ? JPEG_BINARY_1 : JPEG_BINARY_0; + } + + Y0 += MCU_W; + } + break; + } + case PIXFORMAT_GRAYSCALE: { + if ((dx != MCU_W) || (dy != MCU_H)) { // partial MCU, fill with 0's to start + memset(Y0, 0, JPEG_444_GS_MCU_SIZE); + } + + for (int y = y_offset, yy = y + dy; y < yy; y++) { + uint8_t *rp = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src, y) + x_offset; + + #if (OMV_HARDWARE_JPEG == 0) + if (dx == MCU_W) { + *((uint32_t *) Y0) = *((uint32_t *) rp) ^ 0x80808080; + *(((uint32_t *) Y0) + 1) = *(((uint32_t *) rp) + 1) ^ 0x80808080; + } else if (dx >= 4) { + *((uint32_t *) Y0) = *((uint32_t *) rp) ^ 0x80808080; + + if (dx >= 6) { + *(((uint16_t *) Y0) + 2) = *(((uint16_t *) rp) + 2) ^ 0x8080; + + if (dx & 1) { + Y0[6] = rp[6] ^ 0x80; + } + } else if (dx & 1) { + Y0[4] = rp[4] ^ 0x80; + } + } else if (dx >= 2) { + *((uint16_t *) Y0) = *((uint16_t *) rp) ^ 0x8080; + + if (dx & 1) { + Y0[2] = rp[2] ^ 0x80; + } + } else { + *Y0 = *rp ^ 0x80; + } + #else + if (dx == MCU_W) { + *((uint32_t *) Y0) = *((uint32_t *) rp); + *(((uint32_t *) Y0) + 1) = *(((uint32_t *) rp) + 1); + } else if (dx >= 4) { + *((uint32_t *) Y0) = *((uint32_t *) rp); + + if (dx >= 6) { + *(((uint16_t *) Y0) + 2) = *(((uint16_t *) rp) + 2); + + if (dx & 1) { + Y0[6] = rp[6]; + } + } else if (dx & 1) { + Y0[4] = rp[4]; + } + } else if (dx >= 2) { + *((uint16_t *) Y0) = *((uint16_t *) rp); + + if (dx & 1) { + Y0[2] = rp[2]; + } + } else { + *Y0 = *rp; + } + #endif + + Y0 += MCU_W; + } + break; + } + case PIXFORMAT_RGB565: {//printf("input rgb565\n"); + if ((dx != MCU_W) || (dy != MCU_H)) { // partial MCU, fill with 0's to start + memset(Y0, 0, JPEG_444_GS_MCU_SIZE); + memset(CB, 0, JPEG_444_GS_MCU_SIZE); + memset(CR, 0, JPEG_444_GS_MCU_SIZE); + } + + for (int y = y_offset, yy = y + dy, index = 0; y < yy; y++) { + uint32_t *rp = (uint32_t *) (IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src, y) + x_offset); + for (int x = 0, xx = dx - 1; x < xx; x += 2, index += 2) { + int pixels = *rp++; + // if(pixels!=0xffffffff) + int r_pixels = ((pixels >> 8) & 0xf800f8) | ((pixels >> 13) & 0x70007); + int g_pixels = ((pixels >> 3) & 0xfc00fc) | ((pixels >> 9) & 0x30003); + int b_pixels = ((pixels << 3) & 0xf800f8) | ((pixels >> 2) & 0x70007); + + int y = ((r_pixels * 38) + (g_pixels * 75) + (b_pixels * 15)) >> 7; + + #if (OMV_HARDWARE_JPEG == 0) + y ^= 0x800080; + #endif + + Y0[index] = y, Y0[index + 1] = y >> 16; + + int u = __SSUB16(b_pixels * 64, (r_pixels * 21) + (g_pixels * 43)) >> 7; + + #if (OMV_HARDWARE_JPEG == 1) + u ^= 0x800080; + #endif + + CB[index] = u, CB[index + 1] = u >> 16; + + int v = __SSUB16(r_pixels * 64, (g_pixels * 54) + (b_pixels * 10)) >> 7; + + #if (OMV_HARDWARE_JPEG == 1) + v ^= 0x800080; + #endif + + CR[index] = v, CR[index + 1] = v >> 16; + } + + if (dx & 1) { + int pixel = *((uint16_t *) rp); + int r = COLOR_RGB565_TO_R8(pixel); + int g = COLOR_RGB565_TO_G8(pixel); + int b = COLOR_RGB565_TO_B8(pixel); + + int y0 = COLOR_RGB888_TO_Y_(r, g, b); + + #if (OMV_HARDWARE_JPEG == 0) + y0 ^= 0x80; + #endif + + Y0[index] = y0; + + int cb = COLOR_RGB888_TO_U_(r, g, b); + + #if (OMV_HARDWARE_JPEG == 1) + cb ^= 0x80; + #endif + + CB[index] = cb; + + int cr = COLOR_RGB888_TO_V_(r, g, b); + + #if (OMV_HARDWARE_JPEG == 1) + cr ^= 0x80; + #endif + + CR[index++] = cr; + } + + index += MCU_W - dx; + } + break; + } + case PIXFORMAT_RGB888: {//printf("input rgb888\n"); + if ((dx != MCU_W) || (dy != MCU_H)) { // partial MCU, fill with 0's to start + memset(Y0, 0, JPEG_444_GS_MCU_SIZE); + memset(CB, 0, JPEG_444_GS_MCU_SIZE); + memset(CR, 0, JPEG_444_GS_MCU_SIZE); + } + + for (int y = y_offset, yy = y + dy, index = 0; y < yy; y++) { + pixel24_t *rp = (pixel24_t *) (IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src, y) + x_offset); + for (int x = 0, xx = dx - 1; x < xx; x += 2, index += 2) { + uint16_t rgb565_1 = COLOR_R8_G8_B8_TO_RGB565((rp)->red, (rp)->green, (rp)->blue); + rp++; + uint16_t rgb565_2 = COLOR_R8_G8_B8_TO_RGB565((rp)->red, (rp)->green, (rp)->blue); + rp++; + int pixels = rgb565_2 << 16 | rgb565_1; + int r_pixels = ((pixels >> 8) & 0xf800f8) | ((pixels >> 13) & 0x70007); + int g_pixels = ((pixels >> 3) & 0xfc00fc) | ((pixels >> 9) & 0x30003); + int b_pixels = ((pixels << 3) & 0xf800f8) | ((pixels >> 2) & 0x70007); + int y = ((r_pixels * 38) + (g_pixels * 75) + (b_pixels * 15)) >> 7; + #if (OMV_HARDWARE_JPEG == 0) + y ^= 0x800080; + #endif + + Y0[index] = y, Y0[index + 1] = y >> 16; + + int u = __SSUB16(b_pixels * 64, (r_pixels * 21) + (g_pixels * 43)) >> 7; + + #if (OMV_HARDWARE_JPEG == 1) + u ^= 0x800080; + #endif + + CB[index] = u, CB[index + 1] = u >> 16; + + int v = __SSUB16(r_pixels * 64, (g_pixels * 54) + (b_pixels * 10)) >> 7; + + #if (OMV_HARDWARE_JPEG == 1) + v ^= 0x800080; + #endif + + CR[index] = v, CR[index + 1] = v >> 16; + } + + if (dx & 1) { + uint16_t rgb565_1 = COLOR_R8_G8_B8_TO_RGB565((rp)->red, (rp)->green, (rp)->blue); + int pixel = rgb565_1; + int r = COLOR_RGB565_TO_R8(pixel); + int g = COLOR_RGB565_TO_G8(pixel); + int b = COLOR_RGB565_TO_B8(pixel); + + int y0 = COLOR_RGB888_TO_Y_(r, g, b); + + #if (OMV_HARDWARE_JPEG == 0) + y0 ^= 0x80; + #endif + + Y0[index] = y0; + + int cb = COLOR_RGB888_TO_U_(r, g, b); + + #if (OMV_HARDWARE_JPEG == 1) + cb ^= 0x80; + #endif + + CB[index] = cb; + + int cr = COLOR_RGB888_TO_V_(r, g, b); + + #if (OMV_HARDWARE_JPEG == 1) + cr ^= 0x80; + #endif + + CR[index++] = cr; + } + + index += MCU_W - dx; + } + break; + } + case PIXFORMAT_YUV_ANY: { + if ((dx != MCU_W) || (dy != MCU_H)) { // partial MCU, fill with 0's to start + memset(Y0, 0, JPEG_444_GS_MCU_SIZE); + memset(CB, 0, JPEG_444_GS_MCU_SIZE); + memset(CR, 0, JPEG_444_GS_MCU_SIZE); + } + + int shift = (src->pixfmt == PIXFORMAT_YUV422) ? 24 : 8; + + for (int y = y_offset, yy = y + dy, index = 0; y < yy; y++) { + uint32_t *rp = (uint32_t *) (IMAGE_COMPUTE_YUV_PIXEL_ROW_PTR(src, y) + x_offset); + + for (int x = 0, xx = dx - 1; x < xx; x += 2, index += 2) { + int pixels = *rp++; + + #if (OMV_HARDWARE_JPEG == 0) + pixels ^= 0x80808080; + #endif + + Y0[index] = pixels, Y0[index + 1] = pixels >> 16; + + int cb = pixels >> shift; + CB[index] = cb, CB[index + 1] = cb; + + int cr = pixels >> (32 - shift); + CR[index] = cr, CR[index + 1] = cr; + } + + if (dx & 1) { + int pixel = *((uint16_t *) rp); + + #if (OMV_HARDWARE_JPEG == 0) + pixel ^= 0x8080; + #endif + + Y0[index] = pixel; + + if (index % MCU_W) { + if (shift == 8) { + CR[index] = CR[index - 1]; + CB[index++] = pixel >> 8; + } else { + CB[index] = CB[index - 1]; + CR[index++] = pixel >> 8; + } + } else { + if (shift == 8) { + CB[index] = pixel >> 8; + #if (OMV_HARDWARE_JPEG == 0) + CR[index++] = 0; + #else + CR[index++] = 0x80; + #endif + } else { + #if (OMV_HARDWARE_JPEG == 0) + CB[index] = 0; + #else + CB[index] = 0x80; + #endif + CR[index++] = pixel >> 8; + } + } + } + + index += MCU_W - dx; + } + break; + } + case PIXFORMAT_BAYER_ANY: { + if ((dx != MCU_W) || (dy != MCU_H)) { // partial MCU, fill with 0's to start + memset(Y0, 0, JPEG_444_GS_MCU_SIZE); + memset(CB, 0, JPEG_444_GS_MCU_SIZE); + memset(CR, 0, JPEG_444_GS_MCU_SIZE); + } + + int src_w = src->w, w_limit = src_w - 1, w_limit_m_1 = w_limit - 1; + int src_h = src->h, h_limit = src_h - 1, h_limit_m_1 = h_limit - 1; + + if (x_offset && y_offset && (x_offset < (src_w - MCU_W)) && (y_offset < (src_h - MCU_H))) { + for (int y = y_offset - 1, yy = y + MCU_H - 1, index_e = 0, index_o = MCU_W; y < yy; y += 2, + index_e += MCU_W, + index_o += MCU_W) { + uint8_t *rowptr_grgr_0 = src->data + (y * src_w); + uint8_t *rowptr_bgbg_1 = rowptr_grgr_0 + src_w; + uint8_t *rowptr_grgr_2 = rowptr_bgbg_1 + src_w; + uint8_t *rowptr_bgbg_3 = rowptr_grgr_2 + src_w; + + for (int x = x_offset - 1, xx = x + MCU_W - 1; x < xx; x += 2, index_e += 2, index_o += 2) { + uint32_t row_grgr_0 = *((uint32_t *) (rowptr_grgr_0 + x)); + uint32_t row_bgbg_1 = *((uint32_t *) (rowptr_bgbg_1 + x)); + uint32_t row_grgr_2 = *((uint32_t *) (rowptr_grgr_2 + x)); + uint32_t row_bgbg_3 = *((uint32_t *) (rowptr_bgbg_3 + x)); + + int r_pixels_0, g_pixels_0, b_pixels_0; + + switch (src->pixfmt) { + case PIXFORMAT_BAYER_BGGR: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16(__UHADD8(row_02, __PKHTB(row_02, row_02, 16))); + g_pixels_0 = __UXTB16(__UHADD8(row_1g, __PKHTB(row_1g, row_02, 8))); + b_pixels_0 = __UXTB16_RORn(__UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)), 8); + #else + + int r0 = ((row_grgr_0 & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int r2 = (((row_grgr_0 >> 16) & 0xFF) + ((row_grgr_2 >> 16) & 0xFF)) >> 1; + r_pixels_0 = (r2 << 16) | ((r0 + r2) >> 1); + + int g0 = (row_grgr_0 >> 8) & 0xFF; + int g1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 8) & 0xFF; + g_pixels_0 = (row_bgbg_1 & 0xFF0000) | ((((g0 + g2) >> 1) + g1) >> 1); + + int b1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + b_pixels_0 = (b1 << 16) | ((row_bgbg_1 >> 8) & 0xFF); + + #endif + break; + } + case PIXFORMAT_BAYER_GBRG: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16_RORn(__UHADD8(row_02, __PKHBT(row_02, row_02, 16)), 8); + g_pixels_0 = __UXTB16_RORn(__UHADD8(row_1g, __PKHBT(row_1g, row_02, 8)), 8); + b_pixels_0 = __UXTB16(__UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16))); + #else + + int r0 = (((row_grgr_0 >> 8) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int r2 = (((row_grgr_0 >> 24) & 0xFF) + ((row_grgr_2 >> 24) & 0xFF)) >> 1; + r_pixels_0 = r0 | (((r0 + r2) >> 1) << 16); + + int g0 = (row_grgr_0 >> 16) & 0xFF; + int g1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 16) & 0xFF; + g_pixels_0 = ((row_bgbg_1 >> 8) & 0xFF) | (((((g0 + g2) >> 1) + g1) >> 1) << 16); + + int b1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + b_pixels_0 = b1 | (row_bgbg_1 & 0xFF0000); + + #endif + break; + } + case PIXFORMAT_BAYER_GRBG: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16(__UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16))); + g_pixels_0 = __UXTB16_RORn(__UHADD8(row_1g, __PKHBT(row_1g, row_02, 8)), 8); + b_pixels_0 = __UXTB16_RORn(__UHADD8(row_02, __PKHBT(row_02, row_02, 16)), 8); + #else + + int r1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + r_pixels_0 = r1 | (row_bgbg_1 & 0xFF0000); + + int g0 = (row_grgr_0 >> 16) & 0xFF; + int g1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 16) & 0xFF; + g_pixels_0 = ((row_bgbg_1 >> 8) & 0xFF) | (((((g0 + g2) >> 1) + g1) >> 1) << 16); + + int b0 = (((row_grgr_0 >> 8) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int b2 = (((row_grgr_0 >> 24) & 0xFF) + ((row_grgr_2 >> 24) & 0xFF)) >> 1; + b_pixels_0 = b0 | (((b0 + b2) >> 1) << 16); + + #endif + break; + } + case PIXFORMAT_BAYER_RGGB: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16_RORn(__UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)), 8); + g_pixels_0 = __UXTB16(__UHADD8(row_1g, __PKHTB(row_1g, row_02, 8))); + b_pixels_0 = __UXTB16(__UHADD8(row_02, __PKHTB(row_02, row_02, 16))); + #else + + int r1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + r_pixels_0 = (r1 << 16) | ((row_bgbg_1 >> 8) & 0xFF); + + int g0 = (row_grgr_0 >> 8) & 0xFF; + int g1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 8) & 0xFF; + g_pixels_0 = (row_bgbg_1 & 0xFF0000) | ((((g0 + g2) >> 1) + g1) >> 1); + + int b0 = ((row_grgr_0 & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int b2 = (((row_grgr_0 >> 16) & 0xFF) + ((row_grgr_2 >> 16) & 0xFF)) >> 1; + b_pixels_0 = (b2 << 16) | ((b0 + b2) >> 1); + + #endif + break; + } + default: { + r_pixels_0 = 0; + g_pixels_0 = 0; + b_pixels_0 = 0; + break; + } + } + + int y0 = ((r_pixels_0 * 38) + (g_pixels_0 * 75) + (b_pixels_0 * 15)) >> 7; + + #if (OMV_HARDWARE_JPEG == 0) + y0 ^= 0x800080; + #endif + + Y0[index_e] = y0, Y0[index_e + 1] = y0 >> 16; + + int u0 = __SSUB16(b_pixels_0 * 64, (r_pixels_0 * 21) + (g_pixels_0 * 43)) >> 7; + + #if (OMV_HARDWARE_JPEG == 1) + u0 ^= 0x800080; + #endif + + CB[index_e] = u0, CB[index_e + 1] = u0 >> 16; + + int v0 = __SSUB16(r_pixels_0 * 64, (g_pixels_0 * 54) + (b_pixels_0 * 10)) >> 7; + + #if (OMV_HARDWARE_JPEG == 1) + v0 ^= 0x800080; + #endif + + CR[index_e] = v0, CR[index_e + 1] = v0 >> 16; + + int r_pixels_1, g_pixels_1, b_pixels_1; + + switch (src->pixfmt) { + case PIXFORMAT_BAYER_BGGR: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16(__UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16))); + g_pixels_1 = __UXTB16_RORn(__UHADD8(row_2g, __PKHBT(row_2g, row_13, 8)), 8); + b_pixels_1 = __UXTB16_RORn(__UHADD8(row_13, __PKHBT(row_13, row_13, 16)), 8); + #else + + int r2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + r_pixels_1 = (row_grgr_2 & 0xFF0000) | r2; + + int g1 = (row_bgbg_1 >> 16) & 0xFF; + int g2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 16) & 0xFF; + g_pixels_1 = (((((g1 + g3) >> 1) + g2) >> 1) << 16) | ((row_grgr_2 >> 8) & 0xFF); + + int b1 = (((row_bgbg_1 >> 8) & 0xFF) + ((row_bgbg_3 >> 8) & 0xFF)) >> 1; + int b3 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_3 >> 24) & 0xFF)) >> 1; + b_pixels_1 = (((b1 + b3) >> 1) << 16) | b1; + + #endif + break; + } + case PIXFORMAT_BAYER_GBRG: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16_RORn(__UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)), 8); + g_pixels_1 = __UXTB16(__UHADD8(row_2g, __PKHTB(row_2g, row_13, 8))); + b_pixels_1 = __UXTB16(__UHADD8(row_13, __PKHTB(row_13, row_13, 16))); + #else + + int r2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + r_pixels_1 = ((row_grgr_2 >> 8) & 0xFF) | (r2 << 16); + + int g1 = (row_bgbg_1 >> 8) & 0xFF; + int g2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 8) & 0xFF; + g_pixels_1 = ((((g1 + g3) >> 1) + g2) >> 1) | (row_grgr_2 & 0xFF0000); + + int b1 = ((row_bgbg_1 & 0xFF) + (row_bgbg_3 & 0xFF)) >> 1; + int b3 = (((row_bgbg_1 >> 16) & 0xFF) + ((row_bgbg_3 >> 16) & 0xFF)) >> 1; + b_pixels_1 = ((b1 + b3) >> 1) | (b3 << 16); + + #endif + break; + } + case PIXFORMAT_BAYER_GRBG: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16(__UHADD8(row_13, __PKHTB(row_13, row_13, 16))); + g_pixels_1 = __UXTB16(__UHADD8(row_2g, __PKHTB(row_2g, row_13, 8))); + b_pixels_1 = __UXTB16_RORn(__UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)), 8); + #else + + int r1 = ((row_bgbg_1 & 0xFF) + (row_bgbg_3 & 0xFF)) >> 1; + int r3 = (((row_bgbg_1 >> 16) & 0xFF) + ((row_bgbg_3 >> 16) & 0xFF)) >> 1; + r_pixels_1 = ((r1 + r3) >> 1) | (r3 << 16); + + int g1 = (row_bgbg_1 >> 8) & 0xFF; + int g2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 8) & 0xFF; + g_pixels_1 = ((((g1 + g3) >> 1) + g2) >> 1) | (row_grgr_2 & 0xFF0000); + + int b2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + b_pixels_1 = ((row_grgr_2 >> 8) & 0xFF) | (b2 << 16); + + #endif + break; + } + case PIXFORMAT_BAYER_RGGB: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16_RORn(__UHADD8(row_13, __PKHBT(row_13, row_13, 16)), 8); + g_pixels_1 = __UXTB16_RORn(__UHADD8(row_2g, __PKHBT(row_2g, row_13, 8)), 8); + b_pixels_1 = __UXTB16(__UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16))); + #else + + int r1 = (((row_bgbg_1 >> 8) & 0xFF) + ((row_bgbg_3 >> 8) & 0xFF)) >> 1; + int r3 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_3 >> 24) & 0xFF)) >> 1; + r_pixels_1 = (((r1 + r3) >> 1) << 16) | r1; + + int g1 = (row_bgbg_1 >> 16) & 0xFF; + int g2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 16) & 0xFF; + g_pixels_1 = (((((g1 + g3) >> 1) + g2) >> 1) << 16) | ((row_grgr_2 >> 8) & 0xFF); + + int b2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + b_pixels_1 = (row_grgr_2 & 0xFF0000) | b2; + + #endif + break; + } + default: { + r_pixels_1 = 0; + g_pixels_1 = 0; + b_pixels_1 = 0; + break; + } + } + + int y1 = ((r_pixels_1 * 38) + (g_pixels_1 * 75) + (b_pixels_1 * 15)) >> 7; + + #if (OMV_HARDWARE_JPEG == 0) + y1 ^= 0x800080; + #endif + + Y0[index_o] = y1, Y0[index_o + 1] = y1 >> 16; + + int u1 = __SSUB16(b_pixels_1 * 64, (r_pixels_1 * 21) + (g_pixels_1 * 43)) >> 7; + + #if (OMV_HARDWARE_JPEG == 1) + u1 ^= 0x800080; + #endif + + CB[index_o] = u1, CB[index_o + 1] = u1 >> 16; + + int v1 = __SSUB16(r_pixels_1 * 64, (g_pixels_1 * 54) + (b_pixels_1 * 10)) >> 7; + + #if (OMV_HARDWARE_JPEG == 1) + v1 ^= 0x800080; + #endif + + CR[index_o] = v1, CR[index_o + 1] = v1 >> 16; + } + } + } else { + // If dy is odd this loop will produce 1 extra boundary row in the MCU. + // This is okay given the boundary checking code below. + for (int y = y_offset, yy = y + dy, index_e = 0, index_o = MCU_W; y < yy; y += 2) { + uint8_t *rowptr_grgr_0, *rowptr_bgbg_1, *rowptr_grgr_2, *rowptr_bgbg_3; + + // keep row pointers in bounds + if (y == 0) { + rowptr_bgbg_1 = src->data; + rowptr_grgr_2 = rowptr_bgbg_1 + ((src_h >= 2) ? src_w : 0); + rowptr_bgbg_3 = rowptr_bgbg_1 + ((src_h >= 3) ? (src_w * 2) : 0); + rowptr_grgr_0 = rowptr_grgr_2; + } else if (y == h_limit_m_1) { + rowptr_grgr_0 = src->data + ((y - 1) * src_w); + rowptr_bgbg_1 = rowptr_grgr_0 + src_w; + rowptr_grgr_2 = rowptr_bgbg_1 + src_w; + rowptr_bgbg_3 = rowptr_bgbg_1; + } else if (y >= h_limit) { + rowptr_grgr_0 = src->data + ((y - 1) * src_w); + rowptr_bgbg_1 = rowptr_grgr_0 + src_w; + rowptr_grgr_2 = rowptr_grgr_0; + rowptr_bgbg_3 = rowptr_bgbg_1; + } else { // get 4 neighboring rows + rowptr_grgr_0 = src->data + ((y - 1) * src_w); + rowptr_bgbg_1 = rowptr_grgr_0 + src_w; + rowptr_grgr_2 = rowptr_bgbg_1 + src_w; + rowptr_bgbg_3 = rowptr_grgr_2 + src_w; + } + + // If dx is odd this loop will produce 1 extra boundary column in the MCU. + // This is okay given the boundary checking code below. + for (int x = x_offset, xx = x + dx; x < xx; x += 2, index_e += 2, index_o += 2) { + uint32_t row_grgr_0, row_bgbg_1, row_grgr_2, row_bgbg_3; + + // keep pixels in bounds + if (x == 0) { + if (src_w >= 4) { + row_grgr_0 = *((uint32_t *) rowptr_grgr_0); + row_bgbg_1 = *((uint32_t *) rowptr_bgbg_1); + row_grgr_2 = *((uint32_t *) rowptr_grgr_2); + row_bgbg_3 = *((uint32_t *) rowptr_bgbg_3); + } else if (src_w >= 3) { + row_grgr_0 = *((uint16_t *) rowptr_grgr_0) | (*(rowptr_grgr_0 + 2) << 16); + row_bgbg_1 = *((uint16_t *) rowptr_bgbg_1) | (*(rowptr_bgbg_1 + 2) << 16); + row_grgr_2 = *((uint16_t *) rowptr_grgr_2) | (*(rowptr_grgr_2 + 2) << 16); + row_bgbg_3 = *((uint16_t *) rowptr_bgbg_3) | (*(rowptr_bgbg_3 + 2) << 16); + } else if (src_w >= 2) { + row_grgr_0 = *((uint16_t *) rowptr_grgr_0); + row_grgr_0 = (row_grgr_0 << 16) | row_grgr_0; + row_bgbg_1 = *((uint16_t *) rowptr_bgbg_1); + row_bgbg_1 = (row_bgbg_1 << 16) | row_bgbg_1; + row_grgr_2 = *((uint16_t *) rowptr_grgr_2); + row_grgr_2 = (row_grgr_2 << 16) | row_grgr_2; + row_bgbg_3 = *((uint16_t *) rowptr_bgbg_3); + row_bgbg_3 = (row_bgbg_3 << 16) | row_bgbg_3; + } else { + row_grgr_0 = *(rowptr_grgr_0) * 0x01010101; + row_bgbg_1 = *(rowptr_bgbg_1) * 0x01010101; + row_grgr_2 = *(rowptr_grgr_2) * 0x01010101; + row_bgbg_3 = *(rowptr_bgbg_3) * 0x01010101; + } + // The starting point needs to be offset by 1. The below patterns are actually + // rgrg, gbgb, rgrg, and gbgb. So, shift left and backfill the missing border pixel. + row_grgr_0 = (row_grgr_0 << 8) | __UXTB_RORn(row_grgr_0, 8); + row_bgbg_1 = (row_bgbg_1 << 8) | __UXTB_RORn(row_bgbg_1, 8); + row_grgr_2 = (row_grgr_2 << 8) | __UXTB_RORn(row_grgr_2, 8); + row_bgbg_3 = (row_bgbg_3 << 8) | __UXTB_RORn(row_bgbg_3, 8); + } else if (x == w_limit_m_1) { + row_grgr_0 = *((uint32_t *) (rowptr_grgr_0 + x - 2)); + row_grgr_0 = (row_grgr_0 >> 8) | ((row_grgr_0 << 8) & 0xff000000); + row_bgbg_1 = *((uint32_t *) (rowptr_bgbg_1 + x - 2)); + row_bgbg_1 = (row_bgbg_1 >> 8) | ((row_bgbg_1 << 8) & 0xff000000); + row_grgr_2 = *((uint32_t *) (rowptr_grgr_2 + x - 2)); + row_grgr_2 = (row_grgr_2 >> 8) | ((row_grgr_2 << 8) & 0xff000000); + row_bgbg_3 = *((uint32_t *) (rowptr_bgbg_3 + x - 2)); + row_bgbg_3 = (row_bgbg_3 >> 8) | ((row_bgbg_1 << 8) & 0xff000000); + } else if (x >= w_limit) { + row_grgr_0 = *((uint16_t *) (rowptr_grgr_0 + x - 1)); + row_grgr_0 = (row_grgr_0 << 16) | row_grgr_0; + row_bgbg_1 = *((uint16_t *) (rowptr_bgbg_1 + x - 1)); + row_bgbg_1 = (row_bgbg_1 << 16) | row_bgbg_1; + row_grgr_2 = *((uint16_t *) (rowptr_grgr_2 + x - 1)); + row_grgr_2 = (row_grgr_2 << 16) | row_grgr_2; + row_bgbg_3 = *((uint16_t *) (rowptr_bgbg_3 + x - 1)); + row_bgbg_3 = (row_bgbg_3 << 16) | row_bgbg_3; + } else { // get 4 neighboring rows + row_grgr_0 = *((uint32_t *) (rowptr_grgr_0 + x - 1)); + row_bgbg_1 = *((uint32_t *) (rowptr_bgbg_1 + x - 1)); + row_grgr_2 = *((uint32_t *) (rowptr_grgr_2 + x - 1)); + row_bgbg_3 = *((uint32_t *) (rowptr_bgbg_3 + x - 1)); + } + + int r_pixels_0, g_pixels_0, b_pixels_0; + + switch (src->pixfmt) { + case PIXFORMAT_BAYER_BGGR: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16(__UHADD8(row_02, __PKHTB(row_02, row_02, 16))); + g_pixels_0 = __UXTB16(__UHADD8(row_1g, __PKHTB(row_1g, row_02, 8))); + b_pixels_0 = __UXTB16_RORn(__UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)), 8); + #else + + int r0 = ((row_grgr_0 & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int r2 = (((row_grgr_0 >> 16) & 0xFF) + ((row_grgr_2 >> 16) & 0xFF)) >> 1; + r_pixels_0 = (r2 << 16) | ((r0 + r2) >> 1); + + int g0 = (row_grgr_0 >> 8) & 0xFF; + int g1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 8) & 0xFF; + g_pixels_0 = (row_bgbg_1 & 0xFF0000) | ((((g0 + g2) >> 1) + g1) >> 1); + + int b1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + b_pixels_0 = (b1 << 16) | ((row_bgbg_1 >> 8) & 0xFF); + + #endif + break; + } + case PIXFORMAT_BAYER_GBRG: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16_RORn(__UHADD8(row_02, __PKHBT(row_02, row_02, 16)), 8); + g_pixels_0 = __UXTB16_RORn(__UHADD8(row_1g, __PKHBT(row_1g, row_02, 8)), 8); + b_pixels_0 = __UXTB16(__UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16))); + #else + + int r0 = (((row_grgr_0 >> 8) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int r2 = (((row_grgr_0 >> 24) & 0xFF) + ((row_grgr_2 >> 24) & 0xFF)) >> 1; + r_pixels_0 = r0 | (((r0 + r2) >> 1) << 16); + + int g0 = (row_grgr_0 >> 16) & 0xFF; + int g1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 16) & 0xFF; + g_pixels_0 = ((row_bgbg_1 >> 8) & 0xFF) | (((((g0 + g2) >> 1) + g1) >> 1) << 16); + + int b1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + b_pixels_0 = b1 | (row_bgbg_1 & 0xFF0000); + + #endif + break; + } + case PIXFORMAT_BAYER_GRBG: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16(__UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16))); + g_pixels_0 = __UXTB16_RORn(__UHADD8(row_1g, __PKHBT(row_1g, row_02, 8)), 8); + b_pixels_0 = __UXTB16_RORn(__UHADD8(row_02, __PKHBT(row_02, row_02, 16)), 8); + #else + + int r1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + r_pixels_0 = r1 | (row_bgbg_1 & 0xFF0000); + + int g0 = (row_grgr_0 >> 16) & 0xFF; + int g1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 16) & 0xFF; + g_pixels_0 = ((row_bgbg_1 >> 8) & 0xFF) | (((((g0 + g2) >> 1) + g1) >> 1) << 16); + + int b0 = (((row_grgr_0 >> 8) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int b2 = (((row_grgr_0 >> 24) & 0xFF) + ((row_grgr_2 >> 24) & 0xFF)) >> 1; + b_pixels_0 = b0 | (((b0 + b2) >> 1) << 16); + + #endif + break; + } + case PIXFORMAT_BAYER_RGGB: { + #if defined(ARM_MATH_DSP) + int row_02 = __UHADD8(row_grgr_0, row_grgr_2); + int row_1g = __UHADD8(row_bgbg_1, __PKHTB(row_bgbg_1, row_bgbg_1, 16)); + + r_pixels_0 = __UXTB16_RORn(__UHADD8(row_bgbg_1, __PKHBT(row_bgbg_1, row_bgbg_1, 16)), 8); + g_pixels_0 = __UXTB16(__UHADD8(row_1g, __PKHTB(row_1g, row_02, 8))); + b_pixels_0 = __UXTB16(__UHADD8(row_02, __PKHTB(row_02, row_02, 16))); + #else + + int r1 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_1 >> 8) & 0xFF)) >> 1; + r_pixels_0 = (r1 << 16) | ((row_bgbg_1 >> 8) & 0xFF); + + int g0 = (row_grgr_0 >> 8) & 0xFF; + int g1 = (((row_bgbg_1 >> 16) & 0xFF) + (row_bgbg_1 & 0xFF)) >> 1; + int g2 = (row_grgr_2 >> 8) & 0xFF; + g_pixels_0 = (row_bgbg_1 & 0xFF0000) | ((((g0 + g2) >> 1) + g1) >> 1); + + int b0 = ((row_grgr_0 & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int b2 = (((row_grgr_0 >> 16) & 0xFF) + ((row_grgr_2 >> 16) & 0xFF)) >> 1; + b_pixels_0 = (b2 << 16) | ((b0 + b2) >> 1); + + #endif + break; + } + default: { + r_pixels_0 = 0; + g_pixels_0 = 0; + b_pixels_0 = 0; + break; + } + } + + int y0 = ((r_pixels_0 * 38) + (g_pixels_0 * 75) + (b_pixels_0 * 15)) >> 7; + + #if (OMV_HARDWARE_JPEG == 0) + y0 ^= 0x800080; + #endif + + Y0[index_e] = y0, Y0[index_e + 1] = y0 >> 16; + + int u0 = __SSUB16(b_pixels_0 * 64, (r_pixels_0 * 21) + (g_pixels_0 * 43)) >> 7; + + #if (OMV_HARDWARE_JPEG == 1) + u0 ^= 0x800080; + #endif + + CB[index_e] = u0, CB[index_e + 1] = u0 >> 16; + + int v0 = __SSUB16(r_pixels_0 * 64, (g_pixels_0 * 54) + (b_pixels_0 * 10)) >> 7; + + #if (OMV_HARDWARE_JPEG == 1) + v0 ^= 0x800080; + #endif + + CR[index_e] = v0, CR[index_e + 1] = v0 >> 16; + + int r_pixels_1, g_pixels_1, b_pixels_1; + + switch (src->pixfmt) { + case PIXFORMAT_BAYER_BGGR: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16(__UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16))); + g_pixels_1 = __UXTB16_RORn(__UHADD8(row_2g, __PKHBT(row_2g, row_13, 8)), 8); + b_pixels_1 = __UXTB16_RORn(__UHADD8(row_13, __PKHBT(row_13, row_13, 16)), 8); + #else + + int r2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + r_pixels_1 = (row_grgr_2 & 0xFF0000) | r2; + + int g1 = (row_bgbg_1 >> 16) & 0xFF; + int g2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 16) & 0xFF; + g_pixels_1 = (((((g1 + g3) >> 1) + g2) >> 1) << 16) | ((row_grgr_2 >> 8) & 0xFF); + + int b1 = (((row_bgbg_1 >> 8) & 0xFF) + ((row_bgbg_3 >> 8) & 0xFF)) >> 1; + int b3 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_3 >> 24) & 0xFF)) >> 1; + b_pixels_1 = (((b1 + b3) >> 1) << 16) | b1; + + #endif + break; + } + case PIXFORMAT_BAYER_GBRG: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16_RORn(__UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)), 8); + g_pixels_1 = __UXTB16(__UHADD8(row_2g, __PKHTB(row_2g, row_13, 8))); + b_pixels_1 = __UXTB16(__UHADD8(row_13, __PKHTB(row_13, row_13, 16))); + #else + + int r2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + r_pixels_1 = ((row_grgr_2 >> 8) & 0xFF) | (r2 << 16); + + int g1 = (row_bgbg_1 >> 8) & 0xFF; + int g2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 8) & 0xFF; + g_pixels_1 = ((((g1 + g3) >> 1) + g2) >> 1) | (row_grgr_2 & 0xFF0000); + + int b1 = ((row_bgbg_1 & 0xFF) + (row_bgbg_3 & 0xFF)) >> 1; + int b3 = (((row_bgbg_1 >> 16) & 0xFF) + ((row_bgbg_3 >> 16) & 0xFF)) >> 1; + b_pixels_1 = ((b1 + b3) >> 1) | (b3 << 16); + + #endif + break; + } + case PIXFORMAT_BAYER_GRBG: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16(__UHADD8(row_13, __PKHTB(row_13, row_13, 16))); + g_pixels_1 = __UXTB16(__UHADD8(row_2g, __PKHTB(row_2g, row_13, 8))); + b_pixels_1 = __UXTB16_RORn(__UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)), 8); + #else + + int r1 = ((row_bgbg_1 & 0xFF) + (row_bgbg_3 & 0xFF)) >> 1; + int r3 = (((row_bgbg_1 >> 16) & 0xFF) + ((row_bgbg_3 >> 16) & 0xFF)) >> 1; + r_pixels_1 = ((r1 + r3) >> 1) | (r3 << 16); + + int g1 = (row_bgbg_1 >> 8) & 0xFF; + int g2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 8) & 0xFF; + g_pixels_1 = ((((g1 + g3) >> 1) + g2) >> 1) | (row_grgr_2 & 0xFF0000); + + int b2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + b_pixels_1 = ((row_grgr_2 >> 8) & 0xFF) | (b2 << 16); + + #endif + break; + } + case PIXFORMAT_BAYER_RGGB: { + #if defined(ARM_MATH_DSP) + int row_13 = __UHADD8(row_bgbg_1, row_bgbg_3); + int row_2g = __UHADD8(row_grgr_2, __PKHBT(row_grgr_2, row_grgr_2, 16)); + + r_pixels_1 = __UXTB16_RORn(__UHADD8(row_13, __PKHBT(row_13, row_13, 16)), 8); + g_pixels_1 = __UXTB16_RORn(__UHADD8(row_2g, __PKHBT(row_2g, row_13, 8)), 8); + b_pixels_1 = __UXTB16(__UHADD8(row_grgr_2, __PKHTB(row_grgr_2, row_grgr_2, 16))); + #else + + int r1 = (((row_bgbg_1 >> 8) & 0xFF) + ((row_bgbg_3 >> 8) & 0xFF)) >> 1; + int r3 = (((row_bgbg_1 >> 24) & 0xFF) + ((row_bgbg_3 >> 24) & 0xFF)) >> 1; + r_pixels_1 = (((r1 + r3) >> 1) << 16) | r1; + + int g1 = (row_bgbg_1 >> 16) & 0xFF; + int g2 = (((row_grgr_2 >> 24) & 0xFF) + ((row_grgr_2 >> 8) & 0xFF)) >> 1; + int g3 = (row_bgbg_3 >> 16) & 0xFF; + g_pixels_1 = (((((g1 + g3) >> 1) + g2) >> 1) << 16) | ((row_grgr_2 >> 8) & 0xFF); + + int b2 = (((row_grgr_2 >> 16) & 0xFF) + (row_grgr_2 & 0xFF)) >> 1; + b_pixels_1 = (row_grgr_2 & 0xFF0000) | b2; + + #endif + break; + } + default: { + r_pixels_1 = 0; + g_pixels_1 = 0; + b_pixels_1 = 0; + break; + } + } + + int y1 = ((r_pixels_1 * 38) + (g_pixels_1 * 75) + (b_pixels_1 * 15)) >> 7; + + #if (OMV_HARDWARE_JPEG == 0) + y1 ^= 0x800080; + #endif + + Y0[index_o] = y1, Y0[index_o + 1] = y1 >> 16; + + int u1 = __SSUB16(b_pixels_1 * 64, (r_pixels_1 * 21) + (g_pixels_1 * 43)) >> 7; + + #if (OMV_HARDWARE_JPEG == 1) + u1 ^= 0x800080; + #endif + + CB[index_o] = u1, CB[index_o + 1] = u1 >> 16; + + int v1 = __SSUB16(r_pixels_1 * 64, (g_pixels_1 * 54) + (b_pixels_1 * 10)) >> 7; + + #if (OMV_HARDWARE_JPEG == 1) + v1 ^= 0x800080; + #endif + + CR[index_o] = v1, CR[index_o + 1] = v1 >> 16; + } + + int inc = (MCU_W * 2) - (((dx + 1) / 2) * 2); // Handle boundary column. + index_e += inc; + index_o += inc; + } + } + break; + } + } +} + +#if (OMV_HARDWARE_JPEG == 1) + +#else + +// Software JPEG implementation. +#define FIX_0_382683433 ((int32_t) 98) +#define FIX_0_541196100 ((int32_t) 139) +#define FIX_0_707106781 ((int32_t) 181) +#define FIX_1_306562965 ((int32_t) 334) + +#define DESCALE(x, y) (x>>y) +#define MULTIPLY(x, y) DESCALE((x) * (y), 8) + +typedef struct { + int idx; + int length; + uint8_t *buf; + int bitc, bitb; + bool realloc; + bool overflow; +} jpeg_buf_t; + +// Quantization tables +static float fdtbl_Y[64], fdtbl_UV[64]; +static uint8_t YTable[64], UVTable[64]; + +static const uint8_t s_jpeg_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 const uint8_t 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 uint8_t 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, 1.387039845f, 1.306562965f, 1.175875602f, + 1.0f, 0.785694958f, 0.541196100f, 0.275899379f +}; + + +static const uint8_t std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; +static const uint8_t std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; +static const uint8_t std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; +static const uint8_t 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 uint8_t std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; +static const uint8_t std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; +static const uint8_t std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; +static const uint8_t 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 uint16_t YDC_HT[12][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 uint16_t UVDC_HT[12][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 uint16_t YAC_HT[256][2] = { + {0x000A, 0x0004},{0x0000, 0x0002},{0x0001, 0x0002},{0x0004, 0x0003},{0x000B, 0x0004},{0x001A, 0x0005},{0x0078, 0x0007},{0x00F8, 0x0008}, + {0x03F6, 0x000A},{0xFF82, 0x0010},{0xFF83, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x000C, 0x0004},{0x001B, 0x0005},{0x0079, 0x0007},{0x01F6, 0x0009},{0x07F6, 0x000B},{0xFF84, 0x0010},{0xFF85, 0x0010}, + {0xFF86, 0x0010},{0xFF87, 0x0010},{0xFF88, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x001C, 0x0005},{0x00F9, 0x0008},{0x03F7, 0x000A},{0x0FF4, 0x000C},{0xFF89, 0x0010},{0xFF8A, 0x0010},{0xFF8B, 0x0010}, + {0xFF8C, 0x0010},{0xFF8D, 0x0010},{0xFF8E, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x003A, 0x0006},{0x01F7, 0x0009},{0x0FF5, 0x000C},{0xFF8F, 0x0010},{0xFF90, 0x0010},{0xFF91, 0x0010},{0xFF92, 0x0010}, + {0xFF93, 0x0010},{0xFF94, 0x0010},{0xFF95, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x003B, 0x0006},{0x03F8, 0x000A},{0xFF96, 0x0010},{0xFF97, 0x0010},{0xFF98, 0x0010},{0xFF99, 0x0010},{0xFF9A, 0x0010}, + {0xFF9B, 0x0010},{0xFF9C, 0x0010},{0xFF9D, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x007A, 0x0007},{0x07F7, 0x000B},{0xFF9E, 0x0010},{0xFF9F, 0x0010},{0xFFA0, 0x0010},{0xFFA1, 0x0010},{0xFFA2, 0x0010}, + {0xFFA3, 0x0010},{0xFFA4, 0x0010},{0xFFA5, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x007B, 0x0007},{0x0FF6, 0x000C},{0xFFA6, 0x0010},{0xFFA7, 0x0010},{0xFFA8, 0x0010},{0xFFA9, 0x0010},{0xFFAA, 0x0010}, + {0xFFAB, 0x0010},{0xFFAC, 0x0010},{0xFFAD, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x00FA, 0x0008},{0x0FF7, 0x000C},{0xFFAE, 0x0010},{0xFFAF, 0x0010},{0xFFB0, 0x0010},{0xFFB1, 0x0010},{0xFFB2, 0x0010}, + {0xFFB3, 0x0010},{0xFFB4, 0x0010},{0xFFB5, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x01F8, 0x0009},{0x7FC0, 0x000F},{0xFFB6, 0x0010},{0xFFB7, 0x0010},{0xFFB8, 0x0010},{0xFFB9, 0x0010},{0xFFBA, 0x0010}, + {0xFFBB, 0x0010},{0xFFBC, 0x0010},{0xFFBD, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x01F9, 0x0009},{0xFFBE, 0x0010},{0xFFBF, 0x0010},{0xFFC0, 0x0010},{0xFFC1, 0x0010},{0xFFC2, 0x0010},{0xFFC3, 0x0010}, + {0xFFC4, 0x0010},{0xFFC5, 0x0010},{0xFFC6, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x01FA, 0x0009},{0xFFC7, 0x0010},{0xFFC8, 0x0010},{0xFFC9, 0x0010},{0xFFCA, 0x0010},{0xFFCB, 0x0010},{0xFFCC, 0x0010}, + {0xFFCD, 0x0010},{0xFFCE, 0x0010},{0xFFCF, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x03F9, 0x000A},{0xFFD0, 0x0010},{0xFFD1, 0x0010},{0xFFD2, 0x0010},{0xFFD3, 0x0010},{0xFFD4, 0x0010},{0xFFD5, 0x0010}, + {0xFFD6, 0x0010},{0xFFD7, 0x0010},{0xFFD8, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x03FA, 0x000A},{0xFFD9, 0x0010},{0xFFDA, 0x0010},{0xFFDB, 0x0010},{0xFFDC, 0x0010},{0xFFDD, 0x0010},{0xFFDE, 0x0010}, + {0xFFDF, 0x0010},{0xFFE0, 0x0010},{0xFFE1, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x07F8, 0x000B},{0xFFE2, 0x0010},{0xFFE3, 0x0010},{0xFFE4, 0x0010},{0xFFE5, 0x0010},{0xFFE6, 0x0010},{0xFFE7, 0x0010}, + {0xFFE8, 0x0010},{0xFFE9, 0x0010},{0xFFEA, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0xFFEB, 0x0010},{0xFFEC, 0x0010},{0xFFED, 0x0010},{0xFFEE, 0x0010},{0xFFEF, 0x0010},{0xFFF0, 0x0010},{0xFFF1, 0x0010}, + {0xFFF2, 0x0010},{0xFFF3, 0x0010},{0xFFF4, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x07F9, 0x000B},{0xFFF5, 0x0010},{0xFFF6, 0x0010},{0xFFF7, 0x0010},{0xFFF8, 0x0010},{0xFFF9, 0x0010},{0xFFFA, 0x0010},{0xFFFB, 0x0010}, + {0xFFFC, 0x0010},{0xFFFD, 0x0010},{0xFFFE, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, +}; + +static const uint16_t UVAC_HT[256][2] = { + {0x0000, 0x0002},{0x0001, 0x0002},{0x0004, 0x0003},{0x000A, 0x0004},{0x0018, 0x0005},{0x0019, 0x0005},{0x0038, 0x0006},{0x0078, 0x0007}, + {0x01F4, 0x0009},{0x03F6, 0x000A},{0x0FF4, 0x000C},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x000B, 0x0004},{0x0039, 0x0006},{0x00F6, 0x0008},{0x01F5, 0x0009},{0x07F6, 0x000B},{0x0FF5, 0x000C},{0xFF88, 0x0010}, + {0xFF89, 0x0010},{0xFF8A, 0x0010},{0xFF8B, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x001A, 0x0005},{0x00F7, 0x0008},{0x03F7, 0x000A},{0x0FF6, 0x000C},{0x7FC2, 0x000F},{0xFF8C, 0x0010},{0xFF8D, 0x0010}, + {0xFF8E, 0x0010},{0xFF8F, 0x0010},{0xFF90, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x001B, 0x0005},{0x00F8, 0x0008},{0x03F8, 0x000A},{0x0FF7, 0x000C},{0xFF91, 0x0010},{0xFF92, 0x0010},{0xFF93, 0x0010}, + {0xFF94, 0x0010},{0xFF95, 0x0010},{0xFF96, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x003A, 0x0006},{0x01F6, 0x0009},{0xFF97, 0x0010},{0xFF98, 0x0010},{0xFF99, 0x0010},{0xFF9A, 0x0010},{0xFF9B, 0x0010}, + {0xFF9C, 0x0010},{0xFF9D, 0x0010},{0xFF9E, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x003B, 0x0006},{0x03F9, 0x000A},{0xFF9F, 0x0010},{0xFFA0, 0x0010},{0xFFA1, 0x0010},{0xFFA2, 0x0010},{0xFFA3, 0x0010}, + {0xFFA4, 0x0010},{0xFFA5, 0x0010},{0xFFA6, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x0079, 0x0007},{0x07F7, 0x000B},{0xFFA7, 0x0010},{0xFFA8, 0x0010},{0xFFA9, 0x0010},{0xFFAA, 0x0010},{0xFFAB, 0x0010}, + {0xFFAC, 0x0010},{0xFFAD, 0x0010},{0xFFAE, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x007A, 0x0007},{0x07F8, 0x000B},{0xFFAF, 0x0010},{0xFFB0, 0x0010},{0xFFB1, 0x0010},{0xFFB2, 0x0010},{0xFFB3, 0x0010}, + {0xFFB4, 0x0010},{0xFFB5, 0x0010},{0xFFB6, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x00F9, 0x0008},{0xFFB7, 0x0010},{0xFFB8, 0x0010},{0xFFB9, 0x0010},{0xFFBA, 0x0010},{0xFFBB, 0x0010},{0xFFBC, 0x0010}, + {0xFFBD, 0x0010},{0xFFBE, 0x0010},{0xFFBF, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x01F7, 0x0009},{0xFFC0, 0x0010},{0xFFC1, 0x0010},{0xFFC2, 0x0010},{0xFFC3, 0x0010},{0xFFC4, 0x0010},{0xFFC5, 0x0010}, + {0xFFC6, 0x0010},{0xFFC7, 0x0010},{0xFFC8, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x01F8, 0x0009},{0xFFC9, 0x0010},{0xFFCA, 0x0010},{0xFFCB, 0x0010},{0xFFCC, 0x0010},{0xFFCD, 0x0010},{0xFFCE, 0x0010}, + {0xFFCF, 0x0010},{0xFFD0, 0x0010},{0xFFD1, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x01F9, 0x0009},{0xFFD2, 0x0010},{0xFFD3, 0x0010},{0xFFD4, 0x0010},{0xFFD5, 0x0010},{0xFFD6, 0x0010},{0xFFD7, 0x0010}, + {0xFFD8, 0x0010},{0xFFD9, 0x0010},{0xFFDA, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x01FA, 0x0009},{0xFFDB, 0x0010},{0xFFDC, 0x0010},{0xFFDD, 0x0010},{0xFFDE, 0x0010},{0xFFDF, 0x0010},{0xFFE0, 0x0010}, + {0xFFE1, 0x0010},{0xFFE2, 0x0010},{0xFFE3, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x07F9, 0x000B},{0xFFE4, 0x0010},{0xFFE5, 0x0010},{0xFFE6, 0x0010},{0xFFE7, 0x0010},{0xFFE8, 0x0010},{0xFFE9, 0x0010}, + {0xFFEA, 0x0010},{0xFFEB, 0x0010},{0xFFEC, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x0000, 0x0000},{0x3FE0, 0x000E},{0xFFED, 0x0010},{0xFFEE, 0x0010},{0xFFEF, 0x0010},{0xFFF0, 0x0010},{0xFFF1, 0x0010},{0xFFF2, 0x0010}, + {0xFFF3, 0x0010},{0xFFF4, 0x0010},{0xFFF5, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, + {0x03FA, 0x000A},{0x7FC3, 0x000F},{0xFFF6, 0x0010},{0xFFF7, 0x0010},{0xFFF8, 0x0010},{0xFFF9, 0x0010},{0xFFFA, 0x0010},{0xFFFB, 0x0010}, + {0xFFFC, 0x0010},{0xFFFD, 0x0010},{0xFFFE, 0x0010},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000},{0x0000, 0x0000}, +}; + +// Macro to write variable length codes to the output stream more efficiently +#define STORECODE(pOut, iLen, ulCode, ulAcc, iNewLen) \ + if (iLen+iNewLen > 32) { while (iLen >= 8) \ + {unsigned char c = (unsigned char)(ulAcc >> 24); *pOut++ = c; \ + if (c == 0xff) { *pOut++ = 0;} ulAcc <<= 8; iLen -= 8; }} \ + iLen += iNewLen; ulAcc |= (ulCode << (32-iLen)); + +// +// See if we're close to filling up the output buffer +// If so, allocate more space now so that we don't have +// to check on every byte written +// +// If we're out of space and the realloc option is not available +// return true to indicate that encoding has to halt +// +static int jpeg_check_highwater(jpeg_buf_t *jpeg_buf) +{ + if ((jpeg_buf->idx+1) >= jpeg_buf->length - 256) { + if (jpeg_buf->realloc == false) { + // Can't realloc buffer + jpeg_buf->overflow = true; + return 1; // failure + } + jpeg_buf->length += 1024; + jpeg_buf->buf = xrealloc(jpeg_buf->buf, jpeg_buf->length); + } + return 0; // ok +} /* jpeg_check_highwater() */ + +// +// Restore buffer pointer variables from local copies +// +void jpeg_restore_buf(jpeg_buf_t *jpeg_buf, uint8_t *pOut, int iBitCount, uint32_t ulBits) +{ + uint8_t c; + while (iBitCount >= 8) { + c = (uint8_t)(ulBits >> 24); + *pOut++ = c; + if (c == 0xff) { + *pOut++ = 0; + } + ulBits <<= 8; iBitCount -= 8; + } + jpeg_buf->idx = (int)(pOut - jpeg_buf->buf); + jpeg_buf->bitb = ulBits >> 8; + jpeg_buf->bitc = iBitCount; + +} /* jpeg_restore_buf() */ + +static void jpeg_put_char(jpeg_buf_t *jpeg_buf, char c) +{ + if ((jpeg_buf->idx+1) >= jpeg_buf->length) { + if (jpeg_buf->realloc == false) { + // Can't realloc buffer + jpeg_buf->overflow = true; + return; + } + jpeg_buf->length += 1024; + jpeg_buf->buf = xrealloc(jpeg_buf->buf, jpeg_buf->length); + } + + jpeg_buf->buf[jpeg_buf->idx++]=c; +} + +static void jpeg_put_bytes(jpeg_buf_t *jpeg_buf, const void *data, int size) +{ + if ((jpeg_buf->idx+size) >= jpeg_buf->length) { + if (jpeg_buf->realloc == false) { + // Can't realloc buffer + jpeg_buf->overflow = true; + return; + } + jpeg_buf->length += 1024; + jpeg_buf->buf = xrealloc(jpeg_buf->buf, jpeg_buf->length); + } + + memcpy(jpeg_buf->buf+jpeg_buf->idx, data, size); + jpeg_buf->idx += size; +} + +static void jpeg_writeBits(jpeg_buf_t *jpeg_buf, const uint16_t *bs) +{ + jpeg_buf->bitc += bs[1]; + jpeg_buf->bitb |= bs[0] << (24 - jpeg_buf->bitc); + + while (jpeg_buf->bitc > 7) { + uint8_t c = (jpeg_buf->bitb >> 16) & 255; + jpeg_put_char(jpeg_buf, c); + if(c == 255) { + jpeg_put_char(jpeg_buf, 0); + } + jpeg_buf->bitb <<= 8; + jpeg_buf->bitc -= 8; + } +} + +//Huffman-encoded magnitude value +static void jpeg_calcBits(int val, uint16_t bits[2]) { + int t1=val; + if (val<0) { + t1 = -val; + val = val-1; + } + bits[1] = 32-__CLZ(t1); + bits[0] = val & ((1<0; i--, p+=8, CDU+=8) { + t0 = CDU[0] + CDU[7]; + t1 = CDU[1] + CDU[6]; + t2 = CDU[2] + CDU[5]; + t3 = CDU[3] + CDU[4]; + + t7 = CDU[0] - CDU[7]; + t6 = CDU[1] - CDU[6]; + t5 = CDU[2] - CDU[5]; + t4 = CDU[3] - CDU[4]; + + // Even part + t10 = t0 + t3; + t13 = t0 - t3; + t11 = t1 + t2; + t12 = t1 - t2; + z1 = MULTIPLY(t12 + t13, FIX_0_707106781); // c4 + + p[0] = t10 + t11; + p[4] = t10 - t11; + p[2] = t13 + z1; + p[6] = t13 - z1; + + // Odd part + t10 = t4 + t5;// phase 2 + t11 = t5 + t6; + t12 = t6 + t7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = MULTIPLY(t10 - t12, FIX_0_382683433); // c6 + z2 = MULTIPLY(t10, FIX_0_541196100) + z5; // 1.306562965f-c6 + z4 = MULTIPLY(t12, FIX_1_306562965) + z5; // 1.306562965f+c6 + z3 = MULTIPLY(t11, FIX_0_707106781); // c4 + z11 = t7 + z3; // phase 5 + z13 = t7 - z3; + + p[5] = z13 + z2;// phase 6 + p[3] = z13 - z2; + p[1] = z11 + z4; + p[7] = z11 - z4; + } + + // DCT columns + for (int i=8, *p=DU; i>0; i--, p++) { + t0 = p[0] + p[56]; + t1 = p[8] + p[48]; + t2 = p[16] + p[40]; + t3 = p[24] + p[32]; + + t7 = p[0] - p[56]; + t6 = p[8] - p[48]; + t5 = p[16] - p[40]; + t4 = p[24] - p[32]; + + // Even part + t10 = t0 + t3; // phase 2 + t13 = t0 - t3; + t11 = t1 + t2; + t12 = t1 - t2; + z1 = MULTIPLY(t12 + t13, FIX_0_707106781); // c4 + + p[0] = t10 + t11; // phase 3 + p[32] = t10 - t11; + p[16] = t13 + z1; // phase 5 + p[48] = t13 - z1; + + // Odd part + t10 = t4 + t5; // phase 2 + t11 = t5 + t6; + t12 = t6 + t7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = MULTIPLY(t10 - t12, FIX_0_382683433); // c6 + z2 = MULTIPLY(t10, FIX_0_541196100) + z5; // 1.306562965f-c6 + z4 = MULTIPLY(t12, FIX_1_306562965) + z5; // 1.306562965f+c6 + z3 = MULTIPLY(t11, FIX_0_707106781); // c4 + z11 = t7 + z3; // phase 5 + z13 = t7 - z3; + + p[40] = z13 + z2;// phase 6 + p[24] = z13 - z2; + p[8] = z11 + z4; + p[56] = z11 - z4; + } + + // first non-zero element in reverse order + int end0pos = 0; + // Quantize/descale/zigzag the coefficients + for(int i=0; i<64; ++i) { + DUQ[s_jpeg_ZigZag[i]] = fast_roundf(DU[i]*fdtbl[i]); + if (s_jpeg_ZigZag[i] > end0pos && DUQ[s_jpeg_ZigZag[i]]) { + end0pos = s_jpeg_ZigZag[i]; + } + } + + if (jpeg_check_highwater(jpeg_buf)) // check if we're getting close to the end of the buffer + return 0; // stop encoding, we've run out of space + // Use local vars to speed up buffer access + // and a macro (STORECODE) to manipulate the local vars + uint8_t *pOut, iBitCount; // output pointer and bit count + uint32_t ulBits; // accumulated bits + pOut = &jpeg_buf->buf[jpeg_buf->idx]; + iBitCount = jpeg_buf->bitc; // current stored bits + ulBits = (jpeg_buf->bitb << 8); // bit pattern shifted up to bit 31 + + // Encode DC + int diff = DUQ[0] - DC; + if (diff == 0) { + STORECODE(pOut, iBitCount, HTDC[0][0], ulBits, HTDC[0][1]) + } else { + uint16_t bits[2]; + jpeg_calcBits(diff, bits); + STORECODE(pOut, iBitCount, HTDC[bits[1]][0], ulBits, HTDC[bits[1]][1]) + STORECODE(pOut, iBitCount, bits[0], ulBits, bits[1]) + } + + // Encode ACs + if(end0pos == 0) { + STORECODE(pOut, iBitCount, EOB[0], ulBits, EOB[1]) + jpeg_restore_buf(jpeg_buf, pOut, iBitCount, ulBits); + return DUQ[0]; + } + + for(int i = 1; i <= end0pos; ++i) { + int startpos = i; + for (; DUQ[i]==0 && i<=end0pos ; ++i) { + } + int nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + for (int nrmarker=1; nrmarker <= lng; ++nrmarker) { + STORECODE(pOut, iBitCount, M16zeroes[0], ulBits, M16zeroes[1]) + } // for + nrzeroes &= 15; + } + uint16_t bits[2]; + jpeg_calcBits(DUQ[i], bits); + STORECODE(pOut, iBitCount, HTAC[(nrzeroes<<4)+bits[1]][0], ulBits, HTAC[(nrzeroes<<4)+bits[1]][1]) + STORECODE(pOut, iBitCount, bits[0], ulBits, bits[1]) + } + if(end0pos != 63) { + STORECODE(pOut, iBitCount, EOB[0], ulBits, EOB[1]) + } + jpeg_restore_buf(jpeg_buf, pOut, iBitCount, ulBits); + return DUQ[0]; +} + +static void jpeg_init(int quality) +{ + static int q =0; + + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + // If quality changed, update quantization matrix + if (q != quality) { + q = quality; + for(int i = 0; i < 64; ++i) { + int yti = (YQT[i]*quality+50)/100; + YTable[s_jpeg_ZigZag[i]] = yti < 1 ? 1 : yti > 255 ? 255 : yti; + int uvti = (UVQT[i]*quality+50)/100; + UVTable[s_jpeg_ZigZag[i]] = uvti < 1 ? 1 : uvti > 255 ? 255 : uvti; + } + + for(int r = 0, k = 0; r < 8; ++r) { + for(int c = 0; c < 8; ++c, ++k) { + fdtbl_Y[k] = 1.0f / (aasf[r] * aasf[c] * YTable [s_jpeg_ZigZag[k]] * 8.0f); + fdtbl_UV[k] = 1.0f / (aasf[r] * aasf[c] * UVTable[s_jpeg_ZigZag[k]] * 8.0f); + } + } + } +} + +static void jpeg_write_headers(jpeg_buf_t *jpeg_buf, int w, int h, int bpp, jpeg_subsample_t jpeg_subsample) +{ + // Number of components (1 or 3) + uint8_t nr_comp = (bpp == 1)? 1 : 3; + + // JPEG headers + uint8_t m_soi[] = { + 0xFF, 0xD8 // SOI + }; + + uint8_t m_app0[] = { + 0xFF, 0xE0, // APP0 + 0x00, 0x10, 'J', 'F', 'I', 'F', 0x00, 0x01, + 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00 + }; + + uint8_t m_dqt[] = { + 0xFF, 0xDB, // DQT + (bpp*65+2)>>8, // Header length MSB + (bpp*65+2)&0xFF, // Header length LSB + }; + + uint8_t m_sof0[] = { + 0xFF, 0xC0, // SOF0 + (nr_comp*3+8)>>8, // Header length MSB + (nr_comp*3+8)&0xFF, // Header length LSB + 0x08, // Bits per sample + h>>8, h&0xFF, // Height + w>>8, w&0xFF, // Width + nr_comp, // Number of components + }; + + uint8_t m_dht[] = { + 0xFF, 0xC4, // DHT + (bpp*208+2)>>8, // Header length MSB + (bpp*208+2)&0xFF, // Header length LSB + }; + + uint8_t m_sos[] = { + 0xFF, 0xDA, // SOS + (nr_comp*2+6)>>8, // Header length MSB + (nr_comp*2+6)&0xFF, // Header length LSB + nr_comp, // Number of components + }; + + // Write SOI marker + jpeg_put_bytes(jpeg_buf, m_soi, sizeof(m_soi)); + // Write APP0 marker + jpeg_put_bytes(jpeg_buf, m_app0, sizeof(m_app0)); + + // Write DQT marker + jpeg_put_bytes(jpeg_buf, m_dqt, sizeof(m_dqt)); + // Write Y quantization table (index, table) + jpeg_put_char (jpeg_buf, 0); + jpeg_put_bytes(jpeg_buf, YTable, sizeof(YTable)); + + if (bpp > 1) { + // Write UV quantization table (index, table) + jpeg_put_char (jpeg_buf, 1); + jpeg_put_bytes(jpeg_buf, UVTable, sizeof(UVTable)); + } + + // Write SOF0 marker + jpeg_put_bytes(jpeg_buf, m_sof0, sizeof(m_sof0)); + for (int i=0; i0)}, 3); + + } + + // Write DHT marker + jpeg_put_bytes(jpeg_buf, m_dht, sizeof(m_dht)); + + // Write DHT-YDC + jpeg_put_char (jpeg_buf, 0x00); + jpeg_put_bytes(jpeg_buf, std_dc_luminance_nrcodes+1, sizeof(std_dc_luminance_nrcodes)-1); + jpeg_put_bytes(jpeg_buf, std_dc_luminance_values, sizeof(std_dc_luminance_values)); + + // Write DHT-YAC + jpeg_put_char (jpeg_buf, 0x10); + jpeg_put_bytes(jpeg_buf, std_ac_luminance_nrcodes+1, sizeof(std_ac_luminance_nrcodes)-1); + jpeg_put_bytes(jpeg_buf, std_ac_luminance_values, sizeof(std_ac_luminance_values)); + + if (bpp > 1) { + // Write DHT-UDC + jpeg_put_char (jpeg_buf, 0x01); + jpeg_put_bytes(jpeg_buf, std_dc_chrominance_nrcodes+1, sizeof(std_dc_chrominance_nrcodes)-1); + jpeg_put_bytes(jpeg_buf, std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + + // Write DHT-UAC + jpeg_put_char (jpeg_buf, 0x11); + jpeg_put_bytes(jpeg_buf, std_ac_chrominance_nrcodes+1, sizeof(std_ac_chrominance_nrcodes)-1); + jpeg_put_bytes(jpeg_buf, std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + } + + // Write SOS marker + jpeg_put_bytes(jpeg_buf, m_sos, sizeof(m_sos)); + for (int i=0; idata) { + uint32_t size=0; + // dst->data = fb_alloc_all(&size, FB_ALLOC_PREFER_SIZE | FB_ALLOC_CACHE_ALIGN); + printf("w:%d h:%d will alloc :%d\n", src->w, src->h , src->h * src->w * 4); + dst->data = xalloc(src->h * src->w * 4); + dst->size = IMLIB_IMAGE_MAX_SIZE(src->h * src->w * 4); + dst->is_data_alloc = 1; + } + + if (src->is_compressed) { + return true; + } + + // JPEG buffer + jpeg_buf_t jpeg_buf = { + .idx = 0, + .buf = dst->pixels, + .length = dst->size, + .bitc = 0, + .bitb = 0, + .realloc = realloc, + .overflow = false, + }; + + // Initialize quantization tables + jpeg_init(quality); + + jpeg_subsample_t jpeg_subsample = JPEG_SUBSAMPLE_1x1; + + if (src->is_color) { + if (quality <= 35) { + jpeg_subsample = JPEG_SUBSAMPLE_2x2; + } else if (quality < 60) { + jpeg_subsample = JPEG_SUBSAMPLE_2x1; + } + } + + jpeg_write_headers(&jpeg_buf, src->w, src->h, src->is_color ? 2 : 1, jpeg_subsample); + + int DCY = 0, DCU = 0, DCV = 0; + + switch (jpeg_subsample) { + case JPEG_SUBSAMPLE_1x1: { + int8_t YDU[JPEG_444_GS_MCU_SIZE]; + int8_t UDU[JPEG_444_GS_MCU_SIZE]; + int8_t VDU[JPEG_444_GS_MCU_SIZE]; + + for (int y_offset = 0; y_offset < src->h; y_offset += MCU_H) { + int dy = src->h - y_offset; + if (dy > MCU_H) { + dy = MCU_H; + } + + for (int x_offset = 0; x_offset < src->w; x_offset += MCU_W) { + int dx = src->w - x_offset; + if (dx > MCU_W) { + dx = MCU_W; + } + + jpeg_get_mcu(src, x_offset, y_offset, dx, dy, YDU, UDU, VDU); + DCY = jpeg_processDU(&jpeg_buf, YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + if (src->is_color) { + DCU = jpeg_processDU(&jpeg_buf, UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = jpeg_processDU(&jpeg_buf, VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + + if (jpeg_buf.overflow) { + return true; + } + } + break; + } + case JPEG_SUBSAMPLE_2x1: { // color only + int8_t YDU[JPEG_444_GS_MCU_SIZE * 2]; + int8_t UDU[JPEG_444_GS_MCU_SIZE * 2]; + int8_t VDU[JPEG_444_GS_MCU_SIZE * 2]; + int8_t UDU_avg[JPEG_444_GS_MCU_SIZE]; + int8_t VDU_avg[JPEG_444_GS_MCU_SIZE]; + + for (int y_offset = 0; y_offset < src->h; y_offset += MCU_H) { + int dy = src->h - y_offset; + if (dy > MCU_H) { + dy = MCU_H; + } + + for (int x_offset = 0; x_offset < src->w; ) { + for (int i = 0; i < (JPEG_444_GS_MCU_SIZE * 2); i += JPEG_444_GS_MCU_SIZE, x_offset += MCU_W) { + int dx = src->w - x_offset; + if (dx > MCU_W) { + dx = MCU_W; + } + + if (dx > 0) { + jpeg_get_mcu(src, x_offset, y_offset, dx, dy, YDU + i, UDU + i, VDU + i); + } else { + memset(YDU + i, 0, JPEG_444_GS_MCU_SIZE); + memset(UDU + i, 0, JPEG_444_GS_MCU_SIZE); + memset(VDU + i, 0, JPEG_444_GS_MCU_SIZE); + } + + DCY = jpeg_processDU(&jpeg_buf, YDU + i, fdtbl_Y, DCY, YDC_HT, YAC_HT); + } + + // horizontal subsampling of U & V + int8_t *UDUp0 = UDU; + int8_t *VDUp0 = VDU; + int8_t *UDUp1 = UDUp0 + JPEG_444_GS_MCU_SIZE; + int8_t *VDUp1 = VDUp0 + JPEG_444_GS_MCU_SIZE; + for (int j = 0; j < JPEG_444_GS_MCU_SIZE; j += MCU_W) { + for (int i = 0; i < MCU_W; i += 2) { + UDU_avg[j+(i/2)] = (UDUp0[i]+UDUp0[i+1]) / 2; + VDU_avg[j+(i/2)] = (VDUp0[i]+VDUp0[i+1]) / 2; + UDU_avg[j+(i/2)+(MCU_W/2)] = (UDUp1[i]+UDUp1[i+1]) / 2; + VDU_avg[j+(i/2)+(MCU_W/2)] = (VDUp1[i]+VDUp1[i+1]) / 2; + } + UDUp0 += MCU_W; + VDUp0 += MCU_W; + UDUp1 += MCU_W; + VDUp1 += MCU_W; + } + + DCU = jpeg_processDU(&jpeg_buf, UDU_avg, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = jpeg_processDU(&jpeg_buf, VDU_avg, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + + if (jpeg_buf.overflow) { + return true; + } + } + break; + } + case JPEG_SUBSAMPLE_2x2: { // color only + int8_t YDU[JPEG_444_GS_MCU_SIZE * 4]; + int8_t UDU[JPEG_444_GS_MCU_SIZE * 4]; + int8_t VDU[JPEG_444_GS_MCU_SIZE * 4]; + int8_t UDU_avg[JPEG_444_GS_MCU_SIZE]; + int8_t VDU_avg[JPEG_444_GS_MCU_SIZE]; + + for (int y_offset = 0; y_offset < src->h; ) { + for (int x_offset = 0; x_offset < src->w; ) { + for (int j = 0; j < (JPEG_444_GS_MCU_SIZE * 4); j += (JPEG_444_GS_MCU_SIZE * 2), y_offset += MCU_H) { + int dy = src->h - y_offset; + if (dy > MCU_H) { + dy = MCU_H; + } + + for (int i = 0; i < (JPEG_444_GS_MCU_SIZE * 2); i += JPEG_444_GS_MCU_SIZE, x_offset += MCU_W) { + int dx = src->w - x_offset; + if (dx > MCU_W) { + dx = MCU_W; + } + + if ((dx > 0) && (dy > 0)) { + jpeg_get_mcu(src, x_offset, y_offset, dx, dy, YDU + i + j, UDU + i + j, VDU + i + j); + } else { + memset(YDU + i + j, 0, JPEG_444_GS_MCU_SIZE); + memset(UDU + i + j, 0, JPEG_444_GS_MCU_SIZE); + memset(VDU + i + j, 0, JPEG_444_GS_MCU_SIZE); + } + + DCY = jpeg_processDU(&jpeg_buf, YDU + i + j, fdtbl_Y, DCY, YDC_HT, YAC_HT); + } + + // Reset back two columns. + x_offset -= (MCU_W * 2); + } + + // Advance to the next columns. + x_offset += (MCU_W * 2); + + // Reset back two rows. + y_offset -= (MCU_H * 2); + + // horizontal and vertical subsampling of U & V + int8_t *UDUp0 = UDU; + int8_t *VDUp0 = VDU; + int8_t *UDUp1 = UDUp0 + JPEG_444_GS_MCU_SIZE; + int8_t *VDUp1 = VDUp0 + JPEG_444_GS_MCU_SIZE; + int8_t *UDUp2 = UDUp1 + JPEG_444_GS_MCU_SIZE; + int8_t *VDUp2 = VDUp1 + JPEG_444_GS_MCU_SIZE; + int8_t *UDUp3 = UDUp2 + JPEG_444_GS_MCU_SIZE; + int8_t *VDUp3 = VDUp2 + JPEG_444_GS_MCU_SIZE; + for (int j = 0, k = JPEG_444_GS_MCU_SIZE / 2; k < JPEG_444_GS_MCU_SIZE; j += MCU_W, k += MCU_W) { + for (int i = 0; i < MCU_W; i += 2) { + UDU_avg[j+(i/2)] = (UDUp0[i]+UDUp0[i+1]+UDUp0[i+MCU_W]+UDUp0[i+1+MCU_W]) / 4; + VDU_avg[j+(i/2)] = (VDUp0[i]+VDUp0[i+1]+VDUp0[i+MCU_W]+VDUp0[i+1+MCU_W]) / 4; + UDU_avg[j+(i/2)+(MCU_W/2)] = (UDUp1[i]+UDUp1[i+1]+UDUp1[i+MCU_W]+UDUp1[i+1+MCU_W]) / 4; + VDU_avg[j+(i/2)+(MCU_W/2)] = (VDUp1[i]+VDUp1[i+1]+VDUp1[i+MCU_W]+VDUp1[i+1+MCU_W]) / 4; + UDU_avg[k+(i/2)] = (UDUp2[i]+UDUp2[i+1]+UDUp2[i+MCU_W]+UDUp2[i+1+MCU_W]) / 4; + VDU_avg[k+(i/2)] = (VDUp2[i]+VDUp2[i+1]+VDUp2[i+MCU_W]+VDUp2[i+1+MCU_W]) / 4; + UDU_avg[k+(i/2)+(MCU_W/2)] = (UDUp3[i]+UDUp3[i+1]+UDUp3[i+MCU_W]+UDUp3[i+1+MCU_W]) / 4; + VDU_avg[k+(i/2)+(MCU_W/2)] = (VDUp3[i]+VDUp3[i+1]+VDUp3[i+MCU_W]+VDUp3[i+1+MCU_W]) / 4; + } + UDUp0 += MCU_W * 2; + VDUp0 += MCU_W * 2; + UDUp1 += MCU_W * 2; + VDUp1 += MCU_W * 2; + UDUp2 += MCU_W * 2; + VDUp2 += MCU_W * 2; + UDUp3 += MCU_W * 2; + VDUp3 += MCU_W * 2; + } + + DCU = jpeg_processDU(&jpeg_buf, UDU_avg, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = jpeg_processDU(&jpeg_buf, VDU_avg, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + + if (jpeg_buf.overflow) { + return true; + } + + // Advance to the next rows. + y_offset += (MCU_H * 2); + } + break; + } + } + + // Do the bit alignment of the EOI marker + static const uint16_t fillBits[] = {0x7F, 7}; + jpeg_writeBits(&jpeg_buf, fillBits); + + // EOI + jpeg_put_char(&jpeg_buf, 0xFF); + jpeg_put_char(&jpeg_buf, 0xD9); + + dst->size = jpeg_buf.idx; + dst->data = jpeg_buf.buf; + + #if (TIME_JPEG==1) + printf("time: %lums\n", mp_hal_ticks_ms() - start); + #endif + + return false; +} + +#endif // (OMV_HARDWARE_JPEG == 1) + +int jpeg_clean_trailing_bytes(int size, uint8_t *data) +{ + while ((size > 1) && ((data[size-2] != 0xFF) || (data[size-1] != 0xD9))) { + size -= 1; + } + + return size; +} + +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) +// This function inits the geometry values of an image. +void jpeg_read_geometry(FIL *fp, image_t *img, const char *path, jpg_read_settings_t *rs) +{ + for (;;) { + uint16_t header; + read_word(fp, &header); + header = __REV16(header); + if ((0xFFD0 <= header) && (header <= 0xFFD9)) { + continue; + } else if (((0xFFC0 <= header) && (header <= 0xFFCF)) + || ((0xFFDA <= header) && (header <= 0xFFDF)) + || ((0xFFE0 <= header) && (header <= 0xFFEF)) + || ((0xFFF0 <= header) && (header <= 0xFFFE))) + { + uint16_t size; + read_word(fp, &size); + size = __REV16(size); + if (((0xFFC0 <= header) && (header <= 0xFFC3)) + || ((0xFFC5 <= header) && (header <= 0xFFC7)) + || ((0xFFC9 <= header) && (header <= 0xFFCB)) + || ((0xFFCD <= header) && (header <= 0xFFCF))) + { + read_byte_ignore(fp); + uint16_t height; + read_word(fp, &height); + height = __REV16(height); + + uint16_t width; + read_word(fp, &width); + width = __REV16(width); + + rs->jpg_w = width; + rs->jpg_h = height; + rs->jpg_size = IMLIB_IMAGE_MAX_SIZE(file_fsize(fp)); + + img->w = rs->jpg_w; + img->h = rs->jpg_h; + img->size = rs->jpg_size; + img->pixfmt = PIXFORMAT_JPEG; + return; + } else { + file_seek(fp, ftell(*fp) + size - 2); + } + } else { + ff_file_corrupted(fp); + } + } +} + +// This function reads the pixel values of an image. +void jpeg_read_pixels(FIL *fp, image_t *img) +{ + file_seek(fp, 0); + read_data(fp, img->pixels, img->size); +} + +void jpeg_read(image_t *img, const char *path) +{ + FIL fp; + jpg_read_settings_t rs; + + file_read_open(&fp, path); + + // Do not use file_buffer_on() here. + jpeg_read_geometry(&fp, img, path, &rs); + + if (!img->pixels) { + img->pixels = xalloc(img->size); + img->is_data_alloc = 1; + } + + jpeg_read_pixels(&fp, img); + file_close(&fp); +} + +void jpeg_write(image_t *img, const char *path, int quality) +{ + FIL fp; + file_write_open(&fp, path); + if (IM_IS_JPEG(img)) { + write_data(&fp, img->pixels, img->size); + } else { + image_t out = { .w=img->w, .h=img->h, .pixfmt=PIXFORMAT_JPEG, .size=0, .pixels=NULL }; // alloc in jpeg compress + // When jpeg_compress needs more memory than in currently allocated it + // will try to realloc. MP will detect that the pointer is outside of + // the heap and return NULL which will cause an out of memory error. + jpeg_compress(img, &out, quality, false); + write_data(&fp, out.pixels, out.size); + // fb_free(NULL); // frees alloc in jpeg_compress() + xfree(out.data); + } + file_close(&fp); +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO) diff --git a/github_source/minicv2/src/jpegd.c b/github_source/minicv2/src/jpegd.c new file mode 100644 index 0000000..33177b7 --- /dev/null +++ b/github_source/minicv2/src/jpegd.c @@ -0,0 +1,2910 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Baseline JPEG decoder. + */ +#include "ff_wrapper.h" +#include "imlib.h" +#include "omv_boardconfig.h" +#include "arm_compat.h" +#define TIME_JPEG (0) +#if (TIME_JPEG == 1) +// #include "py/mphal.h" +// #include +#endif +#include "imlib.h" + +static bool jpeg_is_progressive(image_t *img) +{ + uint8_t *data = img->data; + for (uint32_t i=0; isize;) { + uint8_t marker = data[i++]; + if (marker == 0xFF) { + continue; + } else if (marker == 0xD8) { //SOI + continue; + } else if (marker == 0xC0) { //SOF0/baseline + return false; + } else if (marker == 0xC2) { //SOF2/progressive + return true; + } else if (marker == 0xC2) { //EOI + return false; + } else if (marker == 0xDD) { //DRI 4 bytes payload. + i += 4; + } else if (marker >= 0xd0 && marker <= 0xd7) { //RSTn + continue; + } else if (i+2 < img->size) { // Other markers, variable size payload. + i += __REV16(*((uint16_t*)(&data[i]))); + } else { + return false; + } + } + return false; +} + +#if 0 //(OMV_HARDWARE_JPEG == 1) + +#else +/* Software JPEG decoder */ +#define FILE_HIGHWATER 1536 +#define JPEG_FILE_BUF_SIZE 2048 +#define HUFF_TABLEN 273 +#define HUFF11SIZE (1 << 11) +#define DC_TABLE_SIZE 1024 +#define DCTSIZE 64 +#define MAX_MCU_COUNT 6 +#define MAX_COMPS_IN_SCAN 4 +#define MAX_BUFFERED_PIXELS 2048 + +// Decoder options +#define JPEG_AUTO_ROTATE 1 +#define JPEG_SCALE_HALF 2 +#define JPEG_SCALE_QUARTER 4 +#define JPEG_SCALE_EIGHTH 8 +#define JPEG_LE_PIXELS 16 +#define JPEG_EXIF_THUMBNAIL 32 +#define JPEG_LUMA_ONLY 64 + +#define MCU0 (DCTSIZE * 0) +#define MCU1 (DCTSIZE * 1) +#define MCU2 (DCTSIZE * 2) +#define MCU3 (DCTSIZE * 3) +#define MCU4 (DCTSIZE * 4) +#define MCU5 (DCTSIZE * 5) + +// Pixel types (defaults to little endian RGB565) +enum { + RGB565_LITTLE_ENDIAN = 0, + RGB565_BIG_ENDIAN, + EIGHT_BIT_GRAYSCALE, + ONE_BIT_GRAYSCALE, + FOUR_BIT_DITHERED, + TWO_BIT_DITHERED, + ONE_BIT_DITHERED, + INVALID_PIXEL_TYPE, + RGB888_LITTLE_ENDIAN, + RGB888_BIG_ENDIAN +}; + +enum { + JPEG_MEM_RAM=0, + JPEG_MEM_FLASH +}; + +// Error codes returned by getLastError() +enum { + JPEG_SUCCESS = 0, + JPEG_INVALID_PARAMETER, + JPEG_DECODE_ERROR, + JPEG_UNSUPPORTED_FEATURE, + JPEG_INVALID_FILE +}; + +typedef struct buffered_bits { + unsigned char *pBuf; // buffer pointer + uint32_t ulBits; // buffered bits + uint32_t ulBitOff; // current bit offset +} BUFFERED_BITS; + +typedef struct jpeg_file_tag { + int32_t iPos; // current file position + int32_t iSize; // file size + uint8_t *pData; // memory file pointer + void *fHandle; // class pointer to File/SdFat or whatever you want +} JPEGFILE; + +typedef struct jpeg_draw_tag { + int x, y; // upper left corner of current MCU + int iWidth, iHeight; // size of this MCU + int iBpp; // bit depth of the pixels (8 or 16) + uint16_t *pPixels; // 16-bit pixels + void *pUser; +} JPEGDRAW; + +// Callback function prototypes +typedef int32_t (JPEG_READ_CALLBACK) (JPEGFILE *pFile, uint8_t *pBuf, int32_t iLen); +typedef int32_t (JPEG_SEEK_CALLBACK) (JPEGFILE *pFile, int32_t iPosition); +typedef int (JPEG_DRAW_CALLBACK) (JPEGDRAW *pDraw); +typedef void * (JPEG_OPEN_CALLBACK) (const char *szFilename, int32_t *pFileSize); +typedef void (JPEG_CLOSE_CALLBACK) (void *pHandle); + +/* JPEG color component info */ +typedef struct _jpegcompinfo { + // These values are fixed over the whole image + // For compression, they must be supplied by the user interface + // for decompression, they are read from the SOF marker. + unsigned char component_needed; /* do we need the value of this component? */ + unsigned char component_id; /* identifier for this component (0..255) */ + unsigned char component_index; /* its index in SOF or cinfo->comp_info[] */ + // unsigned char h_samp_factor; /* horizontal sampling factor (1..4) */ + // unsigned char v_samp_factor; /* vertical sampling factor (1..4) */ + unsigned char quant_tbl_no; /* quantization table selector (0..3) */ + // These values may vary between scans + // For compression, they must be supplied by the user interface + // for decompression, they are read from the SOS marker. + unsigned char dc_tbl_no; /* DC entropy table selector (0..3) */ + unsigned char ac_tbl_no; /* AC entropy table selector (0..3) */ + // These values are computed during compression or decompression startup + // int true_comp_width; /* component's image width in samples */ + // int true_comp_height; /* component's image height in samples */ + // the above are the logical dimensions of the downsampled image + // These values are computed before starting a scan of the component + // int MCU_width; /* number of blocks per MCU, horizontally */ + // int MCU_height; /* number of blocks per MCU, vertically */ + // int MCU_blocks; /* MCU_width * MCU_height */ + // int downsampled_width; /* image width in samples, after expansion */ + // int downsampled_height; /* image height in samples, after expansion */ + // the above are the true_comp_xxx values rounded up to multiples of + // the MCU dimensions; these are the working dimensions of the array + // as it is passed through the DCT or IDCT step. NOTE: these values + // differ depending on whether the component is interleaved or not!! + // This flag is used only for decompression. In cases where some of the + // components will be ignored (eg grayscale output from YCbCr image), + // we can skip IDCT etc. computations for the unused components. +} JPEGCOMPINFO; + +// +// our private structure to hold a JPEG image decode state +// +typedef struct jpeg_image_tag { + int iWidth, iHeight; // image size + int iThumbWidth, iThumbHeight; // thumbnail size (if present) + int iThumbData; // offset to image data + int iXOffset, iYOffset; // placement on the display + void *pUser; + uint8_t ucBpp, ucSubSample, ucHuffTableUsed; + uint8_t ucMode, ucOrientation, ucHasThumb, b11Bit; + uint8_t ucComponentsInScan, cApproxBitsLow, cApproxBitsHigh; + uint8_t iScanStart, iScanEnd, ucFF, ucNumComponents; + uint8_t ucACTable, ucDCTable, ucMaxACCol, ucMaxACRow; + uint8_t ucMemType, ucPixelType; + int iEXIF; // Offset to EXIF 'TIFF' file + int iError; + int iOptions; + int iVLCOff; // current VLC data offset + int iVLCSize; // current quantity of data in the VLC buffer + int iResInterval, iResCount; // restart interval + int iMaxMCUs; // max MCUs of pixels per JPEGDraw call + JPEG_READ_CALLBACK *pfnRead; + JPEG_SEEK_CALLBACK *pfnSeek; + JPEG_DRAW_CALLBACK *pfnDraw; + JPEG_OPEN_CALLBACK *pfnOpen; + JPEG_CLOSE_CALLBACK *pfnClose; + JPEGCOMPINFO JPCI[MAX_COMPS_IN_SCAN]; /* Max color components */ + JPEGFILE JPEGFile; + BUFFERED_BITS bb; + uint8_t *pImage; + uint8_t *pDitherBuffer; // provided externally to do Floyd-Steinberg dithering + uint16_t usPixels[MAX_BUFFERED_PIXELS]; + int16_t sMCUs[DCTSIZE * MAX_MCU_COUNT]; // 4:2:0 needs 6 DCT blocks per MCU + int16_t sQuantTable[DCTSIZE * 4]; // quantization tables + uint8_t ucFileBuf[JPEG_FILE_BUF_SIZE]; // holds temp data and pixel stack + uint8_t ucHuffDC[DC_TABLE_SIZE * 2]; // up to 2 'short' tables + uint16_t usHuffAC[HUFF11SIZE * 2]; +} JPEGIMAGE; + +int JPEG_openRAM(JPEGIMAGE *pJPEG, uint8_t *pData, int iDataSize, uint8_t *pImage); +int JPEG_openFile(JPEGIMAGE *pJPEG, const char *szFilename, JPEG_DRAW_CALLBACK *pfnDraw); +int JPEG_getWidth(JPEGIMAGE *pJPEG); +int JPEG_getHeight(JPEGIMAGE *pJPEG); +int JPEG_decode(JPEGIMAGE *pJPEG, int x, int y, int iOptions); +int JPEG_decodeDither(JPEGIMAGE *pJPEG, uint8_t *pDither, int iOptions); +void JPEG_close(JPEGIMAGE *pJPEG); +int JPEG_getLastError(JPEGIMAGE *pJPEG); +int JPEG_getOrientation(JPEGIMAGE *pJPEG); +int JPEG_getBpp(JPEGIMAGE *pJPEG); +int JPEG_getSubSample(JPEGIMAGE *pJPEG); +int JPEG_hasThumb(JPEGIMAGE *pJPEG); +int JPEG_getThumbWidth(JPEGIMAGE *pJPEG); +int JPEG_getThumbHeight(JPEGIMAGE *pJPEG); +int JPEG_getLastError(JPEGIMAGE *pJPEG); +void JPEG_setPixelType(JPEGIMAGE *pJPEG, int iType); // defaults to little endian +void JPEG_setMaxOutputSize(JPEGIMAGE *pJPEG, int iMaxMCUs); + +// Due to unaligned memory causing an exception, we have to do these macros the slow way +#define INTELSHORT(p) (*(uint16_t *) p) +#define INTELLONG(p) (*(uint32_t *) p) +#define MOTOSHORT(p) __builtin_bswap16(*(uint16_t *) p) +#define MOTOLONG(p) __builtin_bswap32(*(uint32_t *) p) + +// Must be a 32-bit target processor +#define REGISTER_WIDTH 32 + +#define TIME_JPEG (0) +#if (TIME_JPEG == 1) +// #include "py/mphal.h" +#endif + +// +// forward references +static int JPEGInit(JPEGIMAGE *pJPEG); +static int JPEGParseInfo(JPEGIMAGE *pPage, int bExtractThumb); +static void JPEGGetMoreData(JPEGIMAGE *pPage); +static int DecodeJPEG(JPEGIMAGE *pImage); +static int32_t readRAM(JPEGFILE *pFile, uint8_t *pBuf, int32_t iLen); +static int32_t seekMem(JPEGFILE *pFile, int32_t iPosition); + +/* JPEG tables */ +// zigzag ordering of DCT coefficients +static const unsigned char cZigZag[64] = { + 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 +}; + +// un-zigzag ordering +static const unsigned char cZigZag2[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 +}; + +// For AA&N IDCT method, multipliers are equal to quantization +// coefficients scaled by scalefactor[row]*scalefactor[col], where +// scalefactor[0] = 1 +// scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7 +// For integer operation, the multiplier table is to be scaled by +// IFAST_SCALE_BITS. +static const int iScaleBits[64] = { + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 22725, 31521, 29692, 26722, 22725, 17855, 12299, 6270, + 21407, 29692, 27969, 25172, 21407, 16819, 11585, 5906, + 19266, 26722, 25172, 22654, 19266, 15137, 10426, 5315, + 16384, 22725, 21407, 19266, 16384, 12873, 8867, 4520, + 12873, 17855, 16819, 15137, 12873, 10114, 6967, 3552, + 8867, 12299, 11585, 10426, 8867, 6967, 4799, 2446, + 4520, 6270, 5906, 5315, 4520, 3552, 2446, 1247 +}; + +// Range clip and shift for RGB565 output +// input value is 0 to 255, then another 256 for overflow to FF, then 512 more for negative values wrapping around +// Trims a few instructions off the final output stage +static const uint8_t ucRangeTable[] = { + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f +}; + +static const uint16_t usGrayTo565[] = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0020, 0x0020, 0x0020, + 0x0841, 0x0841, 0x0841, 0x0841, 0x0861, 0x0861, 0x0861, 0x0861, + 0x1082, 0x1082, 0x1082, 0x1082, 0x10a2, 0x10a2, 0x10a2, 0x10a2, + 0x18c3, 0x18c3, 0x18c3, 0x18c3, 0x18e3, 0x18e3, 0x18e3, 0x18e3, + 0x2104, 0x2104, 0x2104, 0x2104, 0x2124, 0x2124, 0x2124, 0x2124, + 0x2945, 0x2945, 0x2945, 0x2945, 0x2965, 0x2965, 0x2965, 0x2965, + 0x3186, 0x3186, 0x3186, 0x3186, 0x31a6, 0x31a6, 0x31a6, 0x31a6, + 0x39c7, 0x39c7, 0x39c7, 0x39c7, 0x39e7, 0x39e7, 0x39e7, 0x39e7, + 0x4208, 0x4208, 0x4208, 0x4208, 0x4228, 0x4228, 0x4228, 0x4228, + 0x4a49, 0x4a49, 0x4a49, 0x4a49, 0x4a69, 0x4a69, 0x4a69, 0x4a69, + 0x528a, 0x528a, 0x528a, 0x528a, 0x52aa, 0x52aa, 0x52aa, 0x52aa, + 0x5acb, 0x5acb, 0x5acb, 0x5acb, 0x5aeb, 0x5aeb, 0x5aeb, 0x5aeb, + 0x630c, 0x630c, 0x630c, 0x630c, 0x632c, 0x632c, 0x632c, 0x632c, + 0x6b4d, 0x6b4d, 0x6b4d, 0x6b4d, 0x6b6d, 0x6b6d, 0x6b6d, 0x6b6d, + 0x738e, 0x738e, 0x738e, 0x738e, 0x73ae, 0x73ae, 0x73ae, 0x73ae, + 0x7bcf, 0x7bcf, 0x7bcf, 0x7bcf, 0x7bef, 0x7bef, 0x7bef, 0x7bef, + 0x8410, 0x8410, 0x8410, 0x8410, 0x8430, 0x8430, 0x8430, 0x8430, + 0x8c51, 0x8c51, 0x8c51, 0x8c51, 0x8c71, 0x8c71, 0x8c71, 0x8c71, + 0x9492, 0x9492, 0x9492, 0x9492, 0x94b2, 0x94b2, 0x94b2, 0x94b2, + 0x9cd3, 0x9cd3, 0x9cd3, 0x9cd3, 0x9cf3, 0x9cf3, 0x9cf3, 0x9cf3, + 0xa514, 0xa514, 0xa514, 0xa514, 0xa534, 0xa534, 0xa534, 0xa534, + 0xad55, 0xad55, 0xad55, 0xad55, 0xad75, 0xad75, 0xad75, 0xad75, + 0xb596, 0xb596, 0xb596, 0xb596, 0xb5b6, 0xb5b6, 0xb5b6, 0xb5b6, + 0xbdd7, 0xbdd7, 0xbdd7, 0xbdd7, 0xbdf7, 0xbdf7, 0xbdf7, 0xbdf7, + 0xc618, 0xc618, 0xc618, 0xc618, 0xc638, 0xc638, 0xc638, 0xc638, + 0xce59, 0xce59, 0xce59, 0xce59, 0xce79, 0xce79, 0xce79, 0xce79, + 0xd69a, 0xd69a, 0xd69a, 0xd69a, 0xd6ba, 0xd6ba, 0xd6ba, 0xd6ba, + 0xdedb, 0xdedb, 0xdedb, 0xdedb, 0xdefb, 0xdefb, 0xdefb, 0xdefb, + 0xe71c, 0xe71c, 0xe71c, 0xe71c, 0xe73c, 0xe73c, 0xe73c, 0xe73c, + 0xef5d, 0xef5d, 0xef5d, 0xef5d, 0xef7d, 0xef7d, 0xef7d, 0xef7d, + 0xf79e, 0xf79e, 0xf79e, 0xf79e, 0xf7be, 0xf7be, 0xf7be, 0xf7be, + 0xffdf, 0xffdf, 0xffdf, 0xffdf, 0xffff, 0xffff, 0xffff, 0xffff}; + +// Memory initialization +int JPEG_openRAM(JPEGIMAGE *pJPEG, uint8_t *pData, int iDataSize, uint8_t *pImage) +{ + memset(pJPEG, 0, sizeof(JPEGIMAGE)); + pJPEG->ucMemType = JPEG_MEM_RAM; + pJPEG->pfnRead = readRAM; + pJPEG->pfnSeek = seekMem; + pJPEG->pImage = pImage; + pJPEG->pfnOpen = NULL; + pJPEG->pfnClose = NULL; + pJPEG->JPEGFile.iSize = iDataSize; + pJPEG->JPEGFile.pData = pData; + pJPEG->iMaxMCUs = 1000; // set to an unnaturally high value to start + return JPEGInit(pJPEG); +} + +int JPEG_getLastError(JPEGIMAGE *pJPEG) +{ + return pJPEG->iError; +} + +int JPEG_getWidth(JPEGIMAGE *pJPEG) +{ + return pJPEG->iWidth; +} + +int JPEG_getHeight(JPEGIMAGE *pJPEG) +{ + return pJPEG->iHeight; +} + +int JPEG_getOrientation(JPEGIMAGE *pJPEG) +{ + return (int) pJPEG->ucOrientation; +} + +int JPEG_getBpp(JPEGIMAGE *pJPEG) +{ + return (int) pJPEG->ucBpp; +} + +int JPEG_getSubSample(JPEGIMAGE *pJPEG) +{ + return (int) pJPEG->ucSubSample; +} + +int JPEG_hasThumb(JPEGIMAGE *pJPEG) +{ + return (int) pJPEG->ucHasThumb; +} + +int JPEG_getThumbWidth(JPEGIMAGE *pJPEG) +{ + return pJPEG->iThumbWidth; +} +int JPEG_getThumbHeight(JPEGIMAGE *pJPEG) +{ + return pJPEG->iThumbHeight; +} + +void JPEG_setPixelType(JPEGIMAGE *pJPEG, int iType) +{ + pJPEG->ucPixelType = (uint8_t) iType; +} + +void JPEG_setMaxOutputSize(JPEGIMAGE *pJPEG, int iMaxMCUs) +{ + if (iMaxMCUs < 1) { + iMaxMCUs = 1; // don't allow invalid value + } + pJPEG->iMaxMCUs = iMaxMCUs; +} + +int JPEG_decode(JPEGIMAGE *pJPEG, int x, int y, int iOptions) +{ + pJPEG->iXOffset = x; + pJPEG->iYOffset = y; + pJPEG->iOptions = iOptions; + return DecodeJPEG(pJPEG); +} + +int JPEG_decodeDither(JPEGIMAGE *pJPEG, uint8_t *pDither, int iOptions) +{ + pJPEG->iOptions = iOptions; + pJPEG->pDitherBuffer = pDither; + return DecodeJPEG(pJPEG); +} + +// Helper functions for memory based images +static int32_t readRAM(JPEGFILE *pFile, uint8_t *pBuf, int32_t iLen) +{ + int32_t iBytesRead; + + iBytesRead = iLen; + if ((pFile->iSize - pFile->iPos) < iLen) { + iBytesRead = pFile->iSize - pFile->iPos; + } + if (iBytesRead <= 0) { + return 0; + } + memcpy(pBuf, &pFile->pData[pFile->iPos], iBytesRead); + pFile->iPos += iBytesRead; + return iBytesRead; +} + +static int32_t seekMem(JPEGFILE *pFile, int32_t iPosition) +{ + if (iPosition < 0) { + iPosition = 0; + } else if (iPosition >= pFile->iSize) { + iPosition = pFile->iSize - 1; + } + pFile->iPos = iPosition; + return iPosition; +} + +// The following functions are written in plain C and have no +// 3rd party dependencies, not even the C runtime library +// +// Initialize a JPEG file and callback access from a file on SD or memory +// returns 1 for success, 0 for failure +// Fills in the basic image info fields of the JPEGIMAGE structure +static int JPEGInit(JPEGIMAGE *pJPEG) +{ + return JPEGParseInfo(pJPEG, 0); // gather info for image +} + +// Unpack the Huffman tables +static int JPEGGetHuffTables(uint8_t *pBuf, int iLen, JPEGIMAGE *pJPEG) +{ + int i, j, iOffset, iTableOffset; + uint8_t ucTable, *pHuffVals; + + iOffset = 0; + pHuffVals = (uint8_t *) pJPEG->usPixels; // temp holding area to save RAM + while (iLen > 17) { + // while there are tables to copy (we may have combined more than 1 table together) + ucTable = pBuf[iOffset++]; // get table index + if (ucTable & 0x10) { + // convert AC offset of 0x10 into offset of 4 + ucTable ^= 0x14; + } + pJPEG->ucHuffTableUsed |= (1 << ucTable); // mark this table as being defined + if (ucTable <= 7) { + // tables are 0-3, AC+DC + iTableOffset = ucTable * HUFF_TABLEN; + j = 0; // total bits + for (i = 0; i < 16; i++) { + j += pBuf[iOffset]; + pHuffVals[iTableOffset + i] = pBuf[iOffset++]; + } + iLen -= 17; // subtract length of bit lengths + if (j == 0 || j > 256 || j > iLen) { + // bogus bit lengths + return -1; + } + iTableOffset += 16; + for (i = 0; i < j; i++) { + // copy huffman table + pHuffVals[iTableOffset + i] = pBuf[iOffset++]; + } + iLen -= j; + } + } + return 0; +} + +// Expand the Huffman tables for fast decoding +// returns 1 for success, 0 for failure +static int JPEGMakeHuffTables(JPEGIMAGE *pJPEG, int bThumbnail) +{ + int code, repeat, count, codestart; + int j; + int iLen, iTable; + uint16_t *pTable, *pShort, *pLong; + uint8_t *pHuffVals, *pucTable, *pucShort, *pucLong; + uint32_t ul, *pLongTable; + int iBitNum; // current code bit length + int cc; // code + uint8_t *p, *pBits, ucCode; + int iMaxLength, iMaxMask; + int iTablesUsed; + + iTablesUsed = 0; + pHuffVals = (uint8_t *) pJPEG->usPixels; + for (j = 0; j < 4; j++) { + if (pJPEG->ucHuffTableUsed & (1 << j)) { + iTablesUsed++; + } + } + // first do DC components (up to 4 tables of 12-bit codes) + // we can save time and memory for the DC codes by knowing that there exist short codes (<= 6 bits) + // and long codes (>6 bits, but the first 5 bits are 1's). This allows us to create 2 tables: a 6-bit + // and 7 or 8-bit to handle any DC codes + iMaxLength = 12; // assume DC codes can be 12-bits + iMaxMask = 0x7f; // lower 7 bits after truncate 5 leading 1's + for (iTable = 0; iTable < 4; iTable++) { + if (pJPEG->ucHuffTableUsed & (1 << iTable)) { + // pJPEG->huffdcFast[iTable] = (int *)PILIOAlloc(0x180); // short table = 128 bytes, long table = + // 256 bytes + pucShort = &pJPEG->ucHuffDC[iTable * DC_TABLE_SIZE]; + // pJPEG->huffdc[iTable] = pJPEG->huffdcFast[iTable] + 0x20; // 0x20 longs = 128 bytes + pucLong = &pJPEG->ucHuffDC[iTable * DC_TABLE_SIZE + 128]; + pBits = &pHuffVals[iTable * HUFF_TABLEN]; + p = pBits; + p += 16; // point to bit data + cc = 0; // start with a code of 0 + for (iBitNum = 1; iBitNum <= 16; iBitNum++) { + iLen = *pBits++; // get number of codes for this bit length + if (iBitNum > iMaxLength && iLen > 0) { + // we can't handle codes longer a certain length + return 0; + } + while (iLen) { + // if (iBitNum > 6) // do long table + if ((cc >> (iBitNum - 5)) == 0x1f) { + // first 5 bits are 1 - use long table + count = iMaxLength - iBitNum; + codestart = cc << count; + pucTable = &pucLong[codestart & iMaxMask]; // use lower 7/8 bits of code + } else { + // do short table + count = 6 - iBitNum; + if (count < 0) { + return 0; // DEBUG - something went wrong + } + codestart = cc << count; + pucTable = &pucShort[codestart]; + } + ucCode = *p++; // get actual huffman code + // does precalculating the DC value save time on ARM? + if (ucCode != 0 && (ucCode + iBitNum) <= 6 && pJPEG->ucMode != 0xc2) { + // we can fit the magnitude value in the code lookup (not for progressive) + int k, iLoop; + unsigned char ucCoeff; + unsigned char *d = &pucTable[512]; + unsigned char ucMag = ucCode; + ucCode |= ((iBitNum + ucCode) << 4); // add magnitude bits to length + repeat = 1 << ucMag; + iLoop = 1 << (count - ucMag); + for (j = 0; j < repeat; j++) { + // calcuate the magnitude coeff already + if (j & 1 << (ucMag - 1)) { + // positive number + ucCoeff = (unsigned char) j; + } else { + // negative number + ucCoeff = (unsigned char) (j - ((1 << ucMag) - 1)); + } + for (k = 0; k < iLoop; k++) { + *d++ = ucCoeff; + } // for k + } // for j + } else { + ucCode |= (iBitNum << 4); + } + if (count) { + // do it as dwords to save time + repeat = (1 << count); + memset(pucTable, ucCode, repeat); + } else { + pucTable[0] = ucCode; + } + cc++; + iLen--; + } + cc <<= 1; + } + } // if table defined + } + // now do AC components (up to 4 tables of 16-bit codes) + // We split the codes into a short table (9 bits or less) and a long table (first 5 bits are 1) + for (iTable = 0; iTable < 4; iTable++) { + if (pJPEG->ucHuffTableUsed & (1 << (iTable + 4))) { + // if this table is defined + pBits = &pHuffVals[(iTable + 4) * HUFF_TABLEN]; + p = pBits; + p += 16; // point to bit data + pShort = &pJPEG->usHuffAC[iTable * HUFF11SIZE]; + pLong = &pJPEG->usHuffAC[iTable * HUFF11SIZE + 1024]; + cc = 0; // start with a code of 0 + // construct the decode table + for (iBitNum = 1; iBitNum <= 16; iBitNum++) { + iLen = *pBits++; // get number of codes for this bit length + while (iLen) { + if ((cc >> (iBitNum - 6)) == 0x3f) { + // first 6 bits are 1 - use long table + count = 16 - iBitNum; + codestart = cc << count; + pTable = &pLong[codestart & 0x3ff]; // use lower 10 bits of code + } else { + count = 10 - iBitNum; + if (count < 0) { + // an 11/12-bit? code - that doesn't fit our optimized + // scheme, see if we can do a bigger table version + if (count == -1 && iTablesUsed <= 4) { + return 0; + } else { + return 0; // DEBUG - fatal error, more than 2 big tables we currently don't support + } + } + codestart = cc << count; + pTable = &pShort[codestart]; // 10 bits or shorter + } + code = *p++; // get actual huffman code + if (bThumbnail && code != 0) { + // add "extra" bits to code length since we skip these codes + // get rid of extra bits in code and add increment (1) for AC index + code = ((iBitNum + (code & 0xf)) << 8) | ((code >> 4) + 1); + } else { + code |= (iBitNum << 8); + } + if (count) { + // do it as dwords to save time + repeat = 1 << (count - 1); // store as dwords (/2) + ul = code | (code << 16); + pLongTable = (uint32_t *) pTable; + for (j = 0; j < repeat; j++) { + *pLongTable++ = ul; + } + } else { + pTable[0] = (unsigned short) code; + } + cc++; + iLen--; + } + cc <<= 1; + } // for each bit length + } // if table defined + } + return 1; +} + +// TIFFSHORT +// read a 16-bit unsigned integer from the given pointer +// and interpret the data as big endian (Motorola) or little endian (Intel) +static uint16_t TIFFSHORT(unsigned char *p, int bMotorola) +{ + unsigned short s; + + if (bMotorola) { + s = *p * 0x100 + *(p + 1); // big endian (AKA Motorola byte order) + } else { + s = *p + *(p + 1) * 0x100; // little endian (AKA Intel byte order) + } + return s; +} + +// TIFFLONG +// read a 32-bit unsigned integer from the given pointer +// and interpret the data as big endian (Motorola) or little endian (Intel) +static uint32_t TIFFLONG(unsigned char *p, int bMotorola) +{ + uint32_t l; + + if (bMotorola) { + l = *p * 0x1000000 + *(p + 1) * 0x10000 + *(p + 2) * 0x100 + *(p + 3); // big endian + } else { + l = *p + *(p + 1) * 0x100 + *(p + 2) * 0x10000 + *(p + 3) * 0x1000000; // little endian + } + return l; +} + +// TIFFVALUE +// read an integer value encoded in a TIFF TAG (12-byte structure) +// and interpret the data as big endian (Motorola) or little endian (Intel) +static int TIFFVALUE(unsigned char *p, int bMotorola) +{ + int i, iType; + + iType = TIFFSHORT(p + 2, bMotorola); + /* If pointer to a list of items, must be a long */ + if (TIFFSHORT(p + 4, bMotorola) > 1) { + iType = 4; + } + switch (iType) { + case 3: /* Short */ + i = TIFFSHORT(p + 8, bMotorola); + break; + case 4: /* Long */ + case 7: // undefined (treat it as a long since it's usually a multibyte buffer) + i = TIFFLONG(p + 8, bMotorola); + break; + case 6: // signed byte + i = (signed char) p[8]; + break; + case 2: /* ASCII */ + case 5: /* Unsigned Rational */ + case 10: /* Signed Rational */ + i = TIFFLONG(p + 8, bMotorola); + break; + default: /* to suppress compiler warning */ + i = 0; + break; + } + return i; + +} + +static void GetTIFFInfo(JPEGIMAGE *pPage, int bMotorola, int iOffset) +{ + int iTag, iTagCount, i; + uint8_t *cBuf = pPage->ucFileBuf; + + iTagCount = TIFFSHORT(&cBuf[iOffset], bMotorola); /* Number of tags in this dir */ + if (iTagCount < 1 || iTagCount > 256) { + // invalid tag count + return; /* Bad header info */ + } + /*--- Search the TIFF tags ---*/ + for (i = 0; i < iTagCount; i++) { + unsigned char *p = &cBuf[iOffset + (i * 12) + 2]; + iTag = TIFFSHORT(p, bMotorola); /* current tag value */ + if (iTag == 274) { + // orientation tag + pPage->ucOrientation = TIFFVALUE(p, bMotorola); + } else if (iTag == 256) { + // width of thumbnail + pPage->iThumbWidth = TIFFVALUE(p, bMotorola); + } else if (iTag == 257) { + // height of thumbnail + pPage->iThumbHeight = TIFFVALUE(p, bMotorola); + } else if (iTag == 513) { + // offset to JPEG data + pPage->iThumbData = TIFFVALUE(p, bMotorola); + } + } +} + +static int JPEGGetSOS(JPEGIMAGE *pJPEG, int *iOff) +{ + int16_t sLen; + int iOffset = *iOff; + int i, j; + uint8_t uc,c,cc; + uint8_t *buf = pJPEG->ucFileBuf; + + sLen = MOTOSHORT(&buf[iOffset]); + iOffset += 2; + + // Assume no components in this scan + for (i = 0; i < 4; i++) { + pJPEG->JPCI[i].component_needed = 0; + } + + uc = buf[iOffset++]; // get number of components + pJPEG->ucComponentsInScan = uc; + sLen -= 3; + if (uc < 1 || uc > MAX_COMPS_IN_SCAN || sLen != (uc * 2 + 3)) { + // check length of data packet + return 1; // error + } + for (i = 0; i < uc; i++) { + cc = buf[iOffset++]; + c = buf[iOffset++]; + sLen -= 2; + for (j = 0; j < 4; j++) { + // search for component id + if (pJPEG->JPCI[j].component_id == cc) { + break; + } + } + if (j == 4) { + // error, not found + return 1; + } + if ((c & 0xf) > 3 || (c & 0xf0) > 0x30) { + return 1; // bogus table numbers + } + pJPEG->JPCI[j].dc_tbl_no = c >> 4; + pJPEG->JPCI[j].ac_tbl_no = c & 0xf; + pJPEG->JPCI[j].component_needed = 1; // mark this component as being included in the scan + } + pJPEG->iScanStart = buf[iOffset++]; // Get the scan start (or lossless predictor) for this scan + pJPEG->iScanEnd = buf[iOffset++]; // Get the scan end for this scan + c = buf[iOffset++]; // successive approximation bits + pJPEG->cApproxBitsLow = c & 0xf; // also point transform in lossless mode + pJPEG->cApproxBitsHigh = c >> 4; + + *iOff = iOffset; + return 0; + +} + +// Remove markers from the data stream to allow faster decode +// Stuffed zeros and restart interval markers aren't needed to properly decode +// the data, but they make reading VLC data slower, so I pull them out first +static int JPEGFilter(uint8_t *pBuf, uint8_t *d, int iLen, uint8_t *bFF) +{ + // since we have the entire jpeg buffer in memory already, we can just change it in place + unsigned char c, *s, *pEnd, *pStart; + + pStart = d; + s = pBuf; + pEnd = &s[iLen - 1]; // stop just shy of the end to not miss a final marker/stuffed 0 + if (*bFF) { + // last byte was a FF, check the next one + if (s[0] == 0) { + // stuffed 0, keep the FF + *d++ = 0xff; + } + s++; + *bFF = 0; + } + while (s < pEnd) { + c = *d++ = *s++; + if (c == 0xff) { + // marker or stuffed zeros? + if (s[0] != 0) { + // it's a marker, skip both + d--; + } + s++; // for stuffed 0's, store the FF, skip the 00 + } + } + if (s == pEnd) { + // need to test the last byte + c = s[0]; + if (c == 0xff) { + // last byte is FF, take care of it next time through + *bFF = 1; // take care of it next time through + } else { + *d++ = c; // nope, just store it + } + } + return (int) (d - pStart); // filtered output length +} + +// Read and filter more VLC data for decoding +static void JPEGGetMoreData(JPEGIMAGE *pPage) +{ + int iDelta = pPage->iVLCSize - pPage->iVLCOff; + // move any existing data down + if (iDelta >= (JPEG_FILE_BUF_SIZE - 64) || iDelta < 0) { + return; // buffer is already full; no need to read more data + } + if (pPage->iVLCOff != 0) { + memcpy(pPage->ucFileBuf, &pPage->ucFileBuf[pPage->iVLCOff], pPage->iVLCSize - pPage->iVLCOff); + pPage->iVLCSize -= pPage->iVLCOff; + pPage->iVLCOff = 0; + pPage->bb.pBuf = pPage->ucFileBuf; // reset VLC source pointer too + } + if (pPage->JPEGFile.iPos < pPage->JPEGFile.iSize && pPage->iVLCSize < JPEG_FILE_BUF_SIZE - 64) { + int i; + // Try to read enough to fill the buffer + // max length we can read + i = (*pPage->pfnRead)(&pPage->JPEGFile, &pPage->ucFileBuf[pPage->iVLCSize], JPEG_FILE_BUF_SIZE - pPage->iVLCSize); + // Filter out the markers + pPage->iVLCSize += JPEGFilter(&pPage->ucFileBuf[pPage->iVLCSize], &pPage->ucFileBuf[pPage->iVLCSize], i, &pPage->ucFF); + } +} + +// Parse the JPEG header, gather necessary info to decode the image +// Returns 1 for success, 0 for failure +static int JPEGParseInfo(JPEGIMAGE *pPage, int bExtractThumb) +{ + int iBytesRead; + int i, iOffset, iTableOffset; + uint8_t ucTable, *s = pPage->ucFileBuf; + uint16_t usMarker, usLen = 0; + int iFilePos = 0; + + if (bExtractThumb) { + // seek to the start of the thumbnail image + iFilePos = pPage->iThumbData; + (*pPage->pfnSeek)(&pPage->JPEGFile, iFilePos); + } + iBytesRead = (*pPage->pfnRead)(&pPage->JPEGFile, s, JPEG_FILE_BUF_SIZE); + if (iBytesRead < 256) { + // a JPEG file this tiny? probably bad + pPage->iError = JPEG_INVALID_FILE; + return 0; + } + iFilePos += iBytesRead; + if (MOTOSHORT(pPage->ucFileBuf) != 0xffd8) { + pPage->iError = JPEG_INVALID_FILE; + return 0; // not a JPEG file + } + iOffset = 2; /* Start at offset of first marker */ + usMarker = 0; /* Search for SOFx (start of frame) marker */ + while (usMarker != 0xffda && iOffset < pPage->JPEGFile.iSize) { + if (iOffset >= JPEG_FILE_BUF_SIZE / 2) { + // too close to the end, read more data + // Do we need to seek first? + if (iOffset >= JPEG_FILE_BUF_SIZE) { + iFilePos += (iOffset - iBytesRead); + iOffset = 0; + (*pPage->pfnSeek)(&pPage->JPEGFile, iFilePos); + iBytesRead = 0; // throw away any old data + } + // move existing bytes down + if (iOffset) { + memcpy(pPage->ucFileBuf, &pPage->ucFileBuf[iOffset], iBytesRead - iOffset); + iBytesRead -= iOffset; + iOffset = 0; + } + i = (*pPage->pfnRead)(&pPage->JPEGFile, &pPage->ucFileBuf[iBytesRead], JPEG_FILE_BUF_SIZE - iBytesRead); + iFilePos += i; + iBytesRead += i; + } + usMarker = MOTOSHORT(&s[iOffset]); + iOffset += 2; + usLen = MOTOSHORT(&s[iOffset]); // marker length + + if (usMarker < 0xffc0 || usMarker == 0xffff) { + // invalid marker, could be generated by "Arles Image Web Page Creator" or Accusoft + iOffset++; + continue; // skip 1 byte and try to resync + } + switch (usMarker) { + case 0xffc1: + case 0xffc2: + case 0xffc3: + pPage->iError = JPEG_UNSUPPORTED_FEATURE; + return 0; // currently unsupported modes + + case 0xffe1: // App1 (EXIF?) + if (s[iOffset + 2] == 'E' && s[iOffset + 3] == 'x' + && (s[iOffset + 8] == 'M' || s[iOffset + 8] == 'I')) { + // the EXIF data we want + int bMotorola, IFD, iTagCount; + pPage->iEXIF = iFilePos - iBytesRead + iOffset + 8; // start of TIFF file + // Get the orientation value (if present) + bMotorola = (s[iOffset + 8] == 'M'); + IFD = TIFFLONG(&s[iOffset + 12], bMotorola); + iTagCount = TIFFSHORT(&s[iOffset + 16], bMotorola); + GetTIFFInfo(pPage, bMotorola, IFD + iOffset + 8); + // The second IFD defines the thumbnail (if present) + if (iTagCount >= 1 && iTagCount < 32) { + // valid number of tags for EXIF data 'page' + // point to next IFD + IFD += (12 * iTagCount) + 2; + IFD = TIFFLONG(&s[IFD + iOffset + 8], bMotorola); + if (IFD != 0) { + // Thumbnail present? + pPage->ucHasThumb = 1; + GetTIFFInfo(pPage, bMotorola, IFD + iOffset + 8); // info for second 'page' of TIFF + pPage->iThumbData += iOffset + 8; // absolute offset in the file + } + } + } + break; + case 0xffc0: // SOFx - start of frame + pPage->ucMode = (uint8_t) usMarker; + pPage->ucBpp = s[iOffset + 2]; // bits per sample + pPage->iHeight = MOTOSHORT(&s[iOffset + 3]); + pPage->iWidth = MOTOSHORT(&s[iOffset + 5]); + pPage->ucNumComponents = s[iOffset + 7]; + pPage->ucBpp = pPage->ucBpp * pPage->ucNumComponents; // Bpp = number of components * bits per sample + if (pPage->ucNumComponents == 1) { + pPage->ucSubSample = 0; // use this to differentiate from color 1:1 + } else { + usLen -= 8; + iOffset += 8; + for (i = 0; i < pPage->ucNumComponents; i++) { + uint8_t ucSamp; + pPage->JPCI[i].component_id = s[iOffset++]; + pPage->JPCI[i].component_index = (unsigned char) i; + ucSamp = s[iOffset++]; // get the h+v sampling factor + if (i == 0) { + // Y component? + pPage->ucSubSample = ucSamp; + } + pPage->JPCI[i].quant_tbl_no = s[iOffset++]; // quantization table number + usLen -= 3; + } + } + break; + case 0xffdd: // Restart Interval + if (usLen == 4) { + pPage->iResInterval = MOTOSHORT(&s[iOffset + 2]); + } + break; + case 0xffc4: /* M_DHT */ // get Huffman tables + iOffset += 2; // skip length + usLen -= 2; // subtract length length + if (JPEGGetHuffTables(&s[iOffset], usLen, pPage) != 0) { + // bad tables? + pPage->iError = JPEG_DECODE_ERROR; + return 0; // error + } + break; + case 0xffdb: /* M_DQT */ + /* Get the quantization tables */ + /* first byte has PPPPNNNN where P = precision and N = table number 0-3 */ + iOffset += 2; // skip length + usLen -= 2; // subtract length length + while (usLen > 0) { + ucTable = s[iOffset++]; // table number + if ((ucTable & 0xf) > 3) { + // invalid table number + pPage->iError = JPEG_DECODE_ERROR; + return 0; + } + iTableOffset = (ucTable & 0xf) * DCTSIZE; + if (ucTable & 0xf0) { + // if word precision + for (i = 0; i < DCTSIZE; i++) { + pPage->sQuantTable[i + iTableOffset] = MOTOSHORT(&s[iOffset]); + iOffset += 2; + } + usLen -= (DCTSIZE * 2 + 1); + } else { + // byte precision + for (i = 0; i < DCTSIZE; i++) { + pPage->sQuantTable[i + iTableOffset] = (unsigned short) s[iOffset++]; + } + usLen -= (DCTSIZE + 1); + } + } + break; + } // switch on JPEG marker + iOffset += usLen; + } // while + if (usMarker == 0xffda) { + // start of image + if (pPage->ucBpp != 8) { + // need to match up table IDs + iOffset -= usLen; + JPEGGetSOS(pPage, &iOffset); // get Start-Of-Scan info for decoding + } + if (!JPEGMakeHuffTables(pPage, 0)) { + //int bThumbnail) DEBUG + pPage->iError = JPEG_UNSUPPORTED_FEATURE; + return 0; + } + // Now the offset points to the start of compressed data + i = JPEGFilter(&pPage->ucFileBuf[iOffset], pPage->ucFileBuf, iBytesRead - iOffset, &pPage->ucFF); + pPage->iVLCOff = 0; + pPage->iVLCSize = i; + JPEGGetMoreData(pPage); // read more VLC data + return 1; + } + pPage->iError = JPEG_DECODE_ERROR; + return 0; +} + +// Fix and reorder the quantization table for faster decoding.* +static void JPEGFixQuantD(JPEGIMAGE *pJPEG) +{ + int iTable, iTableOffset; + signed short sTemp[DCTSIZE]; + int i; + uint16_t *p; + + for (iTable = 0; iTable < pJPEG->ucNumComponents; iTable++) { + iTableOffset = iTable * DCTSIZE; + p = (uint16_t *) &pJPEG->sQuantTable[iTableOffset]; + for (i = 0; i < DCTSIZE; i++) { + sTemp[i] = p[cZigZag[i]]; + } + memcpy(&pJPEG->sQuantTable[iTableOffset], sTemp, DCTSIZE * sizeof(short)); // copy back to original spot + + // Prescale for DCT multiplication + p = (uint16_t *) &pJPEG->sQuantTable[iTableOffset]; + for (i = 0; i < DCTSIZE; i++) { + p[i] = (uint16_t) ((p[i] * iScaleBits[i]) >> 12); + } + } +} + +// Decode the 64 coefficients of the current DCT block +static int JPEGDecodeMCU(JPEGIMAGE *pJPEG, int iMCU, int *iDCPredictor) +{ + uint32_t ulCode, ulTemp; + uint8_t *pZig; + signed char cCoeff; + unsigned short *pFast; + unsigned char ucHuff, *pucFast; + uint32_t usHuff; // this prevents an unnecessary & 65535 for shorts + uint32_t ulBitOff, ulBits; // local copies to allow compiler to use register vars + uint8_t *pBuf, *pEnd, *pEnd2; + signed short *pMCU = &pJPEG->sMCUs[iMCU]; + uint8_t ucMaxACCol, ucMaxACRow; + + #define MIN_DCT_THRESHOLD 8 + + ulBitOff = pJPEG->bb.ulBitOff; + ulBits = pJPEG->bb.ulBits; + pBuf = pJPEG->bb.pBuf; + + pZig = (unsigned char *) &cZigZag2[1]; + pEnd = (unsigned char *) &cZigZag2[64]; + + if (ulBitOff > (REGISTER_WIDTH - 17)) { + // need to get more data + pBuf += (ulBitOff >> 3); + ulBitOff &= 7; + ulBits = MOTOLONG(pBuf); + } + if (pJPEG->iOptions & (JPEG_SCALE_QUARTER | JPEG_SCALE_EIGHTH)) { + // reduced size DCT + pMCU[1] = pMCU[8] = pMCU[9] = 0; + pEnd2 = (uint8_t *) &cZigZag2[5]; // we only need to store the 4 elements we care about + } else { + memset(pMCU, 0, 64 * sizeof(short)); // pre-fill with zero since we may skip coefficients + pEnd2 = (uint8_t *) &cZigZag2[64]; + } + ucMaxACCol = ucMaxACRow = 0; + pZig = (unsigned char *) &cZigZag2[1]; + pEnd = (unsigned char *) &cZigZag2[64]; + + // get the DC component + pucFast = &pJPEG->ucHuffDC[pJPEG->ucDCTable * DC_TABLE_SIZE]; + ulCode = (ulBits >> (REGISTER_WIDTH - 12 - ulBitOff)) & 0xfff; // get as lower 12 bits + if (ulCode >= 0xf80) { + // it's a long code + ulCode = (ulCode & 0xff); // point to long table and trim to 7-bits + 0x80 + // offset into long table + } else { + ulCode >>= 6; // it's a short code, use first 6 bits only + } + ucHuff = pucFast[ulCode]; + cCoeff = (signed char) pucFast[ulCode + 512]; // get pre-calculated extra bits for "small" values + if (ucHuff == 0) { + // invalid code + return -1; + } + ulBitOff += (ucHuff >> 4); // add the Huffman length + ucHuff &= 0xf; // get the actual code (SSSS) + if (ucHuff) { + // if there is a change to the DC value + // get the 'extra' bits + if (cCoeff) { + (*iDCPredictor) += cCoeff; + } else { + if (ulBitOff > (REGISTER_WIDTH - 17)) { + // need to get more data + pBuf += (ulBitOff >> 3); + ulBitOff &= 7; + ulBits = MOTOLONG(pBuf); + } + ulCode = ulBits << ulBitOff; + ulTemp = ~(uint32_t) (((int32_t) ulCode) >> 31); // slide sign bit across other 31 bits + ulCode >>= (REGISTER_WIDTH - ucHuff); + ulCode -= ulTemp >> (REGISTER_WIDTH - ucHuff); + ulBitOff += ucHuff; // add bit length + (*iDCPredictor) += (int) ulCode; + } + } + pMCU[0] = (short) *iDCPredictor; // store in MCU[0] + // Now get the other 63 AC coefficients + pFast = &pJPEG->usHuffAC[pJPEG->ucACTable * HUFF11SIZE]; + if (pJPEG->b11Bit) { + // 11-bit "slow" tables used + while (pZig < pEnd) { + if (ulBitOff > (REGISTER_WIDTH - 17)) { + // need to get more data + pBuf += (ulBitOff >> 3); + ulBitOff &= 7; + ulBits = MOTOLONG(pBuf); + } + ulCode = (ulBits >> (REGISTER_WIDTH - 16 - ulBitOff)) & 0xffff; // get as lower 16 bits + if (ulCode >= 0xf000) { + // first 4 bits = 1, use long table + ulCode = (ulCode & 0x1fff); + } else { + ulCode >>= 4; // use lower 12 bits (short table) + } + usHuff = pFast[ulCode]; + if (usHuff == 0) { + // invalid code + return -1; + } + ulBitOff += (usHuff >> 8); // add length + usHuff &= 0xff; // get code (RRRR/SSSS) + if (usHuff == 0) { + // no more AC components + goto mcu_done; + } + if (ulBitOff > (REGISTER_WIDTH - 17)) { + // need to get more data + pBuf += (ulBitOff >> 3); + ulBitOff &= 7; + ulBits = MOTOLONG(pBuf); + } + pZig += (usHuff >> 4); // get the skip amount (RRRR) + usHuff &= 0xf; // get (SSSS) - extra length + if (pZig < pEnd && usHuff) { + // && piHisto) + ulCode = ulBits << ulBitOff; + // slide sign bit across other 63 bits + ulTemp = ~(uint32_t) (((int32_t) ulCode) >> (REGISTER_WIDTH - 1)); + ulCode >>= (REGISTER_WIDTH - usHuff); + ulCode -= ulTemp >> (REGISTER_WIDTH - usHuff); + ucMaxACCol |= 1 << (*pZig & 7); // keep track of occupied columns + if (*pZig >= 0x20) { + // if more than 4 rows used in a col, mark it + ucMaxACRow |= 1 << (*pZig & 7); // keep track of the max AC term + // row + } + pMCU[*pZig] = (signed short) ulCode; // store AC coefficient (already + // reordered) + } + ulBitOff += usHuff; // add (SSSS) extra length + pZig++; + } // while + } else { + // 10-bit "fast" tables used + while (pZig < pEnd) { + if (ulBitOff > (REGISTER_WIDTH - 17)) { + // need to get more data + pBuf += (ulBitOff >> 3); + ulBitOff &= 7; + ulBits = MOTOLONG(pBuf); + } + ulCode = (ulBits >> (REGISTER_WIDTH - 16 - ulBitOff)) & 0xffff; // get as lower 16 bits + if (ulCode >= 0xfc00) { + // first 6 bits = 1, use long table + ulCode = (ulCode & 0x7ff); // (ulCode & 0x3ff) + 0x400; + } else { + ulCode >>= 6; // use lower 10 bits (short table) + } + usHuff = pFast[ulCode]; + if (usHuff == 0) { + // invalid code + return -1; + } + ulBitOff += (usHuff >> 8); // add length + usHuff &= 0xff; // get code (RRRR/SSSS) + if (usHuff == 0) { + // no more AC components + goto mcu_done; + } + if (ulBitOff > (REGISTER_WIDTH - 17)) { + // need to get more data + pBuf += (ulBitOff >> 3); + ulBitOff &= 7; + ulBits = MOTOLONG(pBuf); + } + pZig += (usHuff >> 4); // get the skip amount (RRRR) + usHuff &= 0xf; // get (SSSS) - extra length + if (pZig < pEnd2 && usHuff) { + ulCode = ulBits << ulBitOff; + ulTemp = ~(uint32_t) (((int32_t) ulCode) >> (REGISTER_WIDTH - 1)); // slide sign bit across other + // 63 bits + ulCode >>= (REGISTER_WIDTH - usHuff); + ulCode -= ulTemp >> (REGISTER_WIDTH - usHuff); + ucMaxACCol |= 1 << (*pZig & 7); // keep track of occupied + // columns + if (*pZig >= 0x20) { + // if more than 4 rows used in a col, mark it + ucMaxACRow |= 1 << (*pZig & 7); // keep track of the max AC term + // row + } + pMCU[*pZig] = (signed short) ulCode; // store AC coefficient (already + // reordered) + } + ulBitOff += usHuff; // add (SSSS) extra length + pZig++; + } // while + } // 10-bit tables +mcu_done: + pJPEG->bb.pBuf = pBuf; + pJPEG->iVLCOff = (int) (pBuf - pJPEG->ucFileBuf); + pJPEG->bb.ulBitOff = ulBitOff; + pJPEG->bb.ulBits = ulBits; + pJPEG->ucMaxACCol = ucMaxACCol; + pJPEG->ucMaxACRow = ucMaxACRow; // DEBUG + return 0; +} + +// Inverse DCT +static void JPEGIDCT(JPEGIMAGE *pJPEG, int iMCUOffset, int iQuantTable, int iACFlags) +{ + int iRow; + unsigned char ucColMask; + int iCol; + signed int tmp6,tmp7,tmp10,tmp11,tmp12,tmp13; + signed int z5,z10,z11,z12,z13; + signed int tmp0,tmp1,tmp2,tmp3,tmp4,tmp5; + signed short *pQuant; + unsigned char *pOutput; + unsigned char ucMaxACRow, ucMaxACCol; + int16_t *pMCUSrc = &pJPEG->sMCUs[iMCUOffset]; + + ucMaxACRow = (unsigned char) (iACFlags >> 8); + ucMaxACCol = iACFlags & 0xff; + + // my shortcut method appears to violate patent 20020080052 + // but the patent is invalidated by prior art: + // http://netilium.org/~mad/dtj/DTJ/DTJK04/ + pQuant = &pJPEG->sQuantTable[iQuantTable * DCTSIZE]; + if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + // special case + /* Column 0 */ + tmp4 = pMCUSrc[0] * pQuant[0]; + tmp5 = pMCUSrc[8] * pQuant[8]; + tmp0 = tmp4 + tmp5; + tmp2 = tmp4 - tmp5; + /* Column 1 */ + tmp4 = pMCUSrc[1] * pQuant[1]; + tmp5 = pMCUSrc[9] * pQuant[9]; + tmp1 = tmp4 + tmp5; + tmp3 = tmp4 - tmp5; + /* Pass 2: process 2 rows, store into output array. */ + /* Row 0 */ + pOutput = (unsigned char *) pMCUSrc; // store output pixels back into MCU + pOutput[0] = ucRangeTable[(((tmp0 + tmp1) >> 5) & 0x3ff)]; + pOutput[1] = ucRangeTable[(((tmp0 - tmp1) >> 5) & 0x3ff)]; + /* Row 1 */ + pOutput[2] = ucRangeTable[(((tmp2 + tmp3) >> 5) & 0x3ff)]; + pOutput[3] = ucRangeTable[(((tmp2 - tmp3) >> 5) & 0x3ff)]; + return; + } + // do columns first + ucColMask = ucMaxACCol | 1; // column 0 must always be calculated + for (iCol = 0; iCol < 8 && ucColMask; iCol++) { + if (ucColMask & (1 << iCol)) { + // column has data in it + ucColMask &= ~(1 << iCol); // unmark this col after use + if (!(ucMaxACRow & (1 << iCol))) { + // simpler calculations if only half populated + // even part + tmp10 = pMCUSrc[iCol] * pQuant[iCol]; + tmp1 = pMCUSrc[iCol + 16] * pQuant[iCol + 16]; // get 2nd row + tmp12 = ((tmp1 * 106) >> 8); // used to be 362 - 1 (256) + tmp0 = tmp10 + tmp1; + tmp3 = tmp10 - tmp1; + tmp1 = tmp10 + tmp12; + tmp2 = tmp10 - tmp12; + // odd part + tmp4 = pMCUSrc[iCol + 8] * pQuant[iCol + 8]; // get 1st row + tmp5 = pMCUSrc[iCol + 24]; + if (tmp5) { + // this value is usually 0 + tmp5 *= pQuant[iCol + 24]; // get 3rd row + tmp7 = tmp4 + tmp5; + tmp11 = (((tmp4 - tmp5) * 362) >> 8); // 362>>8 = 1.414213562 + z5 = (((tmp4 - tmp5) * 473) >> 8); // 473>>8 = 1.8477 + tmp12 = ((-tmp5 * -669) >> 8) + z5; // -669>>8 = -2.6131259 + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp10 = ((tmp4 * 277) >> 8) - z5; // 277>>8 = 1.08239 + tmp4 = tmp10 + tmp5; + } else { + // simpler case when we only have 1 odd row to calculate + tmp7 = tmp4; + tmp5 = (145 * tmp4) >> 8; + tmp6 = (217 * tmp4) >> 8; + tmp4 = (-51 * tmp4) >> 8; + } + pMCUSrc[iCol] = (short) (tmp0 + tmp7); // row0 + pMCUSrc[iCol + 8] = (short) (tmp1 + tmp6); // row 1 + pMCUSrc[iCol + 16] = (short) (tmp2 + tmp5); // row 2 + pMCUSrc[iCol + 24] = (short) (tmp3 - tmp4); // row 3 + pMCUSrc[iCol + 32] = (short) (tmp3 + tmp4); // row 4 + pMCUSrc[iCol + 40] = (short) (tmp2 - tmp5); // row 5 + pMCUSrc[iCol + 48] = (short) (tmp1 - tmp6); // row 6 + pMCUSrc[iCol + 56] = (short) (tmp0 - tmp7); // row 7 + } else { + // need to do full column calculation + // even part + tmp0 = pMCUSrc[iCol] * pQuant[iCol]; + tmp2 = pMCUSrc[iCol + 32]; // get 4th row + if (tmp2) { + // 4th row is most likely 0 + tmp2 = tmp2 * pQuant[iCol + 32]; + tmp10 = tmp0 + tmp2; + tmp11 = tmp0 - tmp2; + } else { + tmp10 = tmp11 = tmp0; + } + tmp1 = pMCUSrc[iCol + 16] * pQuant[iCol + 16]; // get 2nd row + tmp3 = pMCUSrc[iCol + 48]; // get 6th row + if (tmp3) { + // 6th row is most likely 0 + tmp3 = tmp3 * pQuant[iCol + 48]; + tmp13 = tmp1 + tmp3; + tmp12 = (((tmp1 - tmp3) * 362) >> 8) - tmp13; // 362>>8 = 1.414213562 + } else { + tmp13 = tmp1; + tmp12 = ((tmp1 * 362) >> 8) - tmp1; + } + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + // odd part + tmp5 = pMCUSrc[iCol + 24] * pQuant[iCol + 24]; // get 3rd row + tmp6 = pMCUSrc[iCol + 40]; // get 5th row + if (tmp6) { + // very likely that row 5 = 0 + tmp6 = tmp6 * pQuant[iCol + 40]; + z13 = tmp6 + tmp5; + z10 = tmp6 - tmp5; + } else { + z13 = tmp5; + z10 = -tmp5; + } + tmp4 = pMCUSrc[iCol + 8] * pQuant[iCol + 8]; // get 1st row + tmp7 = pMCUSrc[iCol + 56]; // get 7th row + if (tmp7) { + // very likely that row 7 = 0 + tmp7 = tmp7 * pQuant[iCol + 56]; + z11 = tmp4 + tmp7; + z12 = tmp4 - tmp7; + } else { + z11 = z12 = tmp4; + } + tmp7 = z11 + z13; + tmp11 = (((z11 - z13) * 362) >> 8); // 362>>8 = 1.414213562 + z5 = (((z10 + z12) * 473) >> 8); // 473>>8 = 1.8477 + tmp12 = ((z10 * -669) >> 8) + z5; // -669>>8 = -2.6131259 + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp10 = ((z12 * 277) >> 8) - z5; // 277>>8 = 1.08239 + tmp4 = tmp10 + tmp5; + pMCUSrc[iCol] = (short) (tmp0 + tmp7); // row0 + pMCUSrc[iCol + 8] = (short) (tmp1 + tmp6); // row 1 + pMCUSrc[iCol + 16] = (short) (tmp2 + tmp5); // row 2 + pMCUSrc[iCol + 24] = (short) (tmp3 - tmp4); // row 3 + pMCUSrc[iCol + 32] = (short) (tmp3 + tmp4); // row 4 + pMCUSrc[iCol + 40] = (short) (tmp2 - tmp5); // row 5 + pMCUSrc[iCol + 48] = (short) (tmp1 - tmp6); // row 6 + pMCUSrc[iCol + 56] = (short) (tmp0 - tmp7); // row 7 + } // full calculation needed + } // if column has data in it + } // for each column + // now do rows + pOutput = (unsigned char *) pMCUSrc; // store output pixels back into MCU + for (iRow = 0; iRow < 64; iRow += 8) { + // all rows must be calculated + // even part + if (ucMaxACCol < 0x10) { + // quick and dirty calculation (right 4 columns are all 0's) + if (ucMaxACCol < 0x04) { + // very likely case (1 or 2 columns occupied) + // even part + tmp0 = tmp1 = tmp2 = tmp3 = pMCUSrc[iRow + 0]; + // odd part + tmp7 = pMCUSrc[iRow + 1]; + tmp6 = (tmp7 * 217) >> 8; // * 0.8477 + tmp5 = (tmp7 * 145) >> 8; // * 0.5663 + tmp4 = -((tmp7 * 51) >> 8); // * -0.199 + } else { + tmp10 = pMCUSrc[iRow + 0]; + tmp13 = pMCUSrc[iRow + 2]; + tmp12 = ((tmp13 * 106) >> 8); // 2-6 * 1.414 + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp10 + tmp12; + tmp2 = tmp10 - tmp12; + // odd part + z13 = pMCUSrc[iRow + 3]; + z11 = pMCUSrc[iRow + 1]; + tmp7 = z11 + z13; + tmp11 = ((z11 - z13) * 362) >> 8; // * 1.414 + z5 = ((z11 - z13) * 473) >> 8; // * 1.8477 + tmp10 = ((z11 * 277) >> 8) - z5; // * 1.08239 + tmp12 = ((z13 * 669) >> 8) + z5; // * 2.61312 + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + } + } else { + // need to do the full calculation + tmp10 = pMCUSrc[iRow + 0] + pMCUSrc[iRow + 4]; + tmp11 = pMCUSrc[iRow + 0] - pMCUSrc[iRow + 4]; + tmp13 = pMCUSrc[iRow + 2] + pMCUSrc[iRow + 6]; + tmp12 = (((pMCUSrc[iRow + 2] - pMCUSrc[iRow + 6]) * 362) >> 8) - tmp13; // 2-6 * 1.414 + tmp0 = tmp10 + tmp13; + tmp3 = tmp10 - tmp13; + tmp1 = tmp11 + tmp12; + tmp2 = tmp11 - tmp12; + // odd part + z13 = pMCUSrc[iRow + 5] + pMCUSrc[iRow + 3]; + z10 = pMCUSrc[iRow + 5] - pMCUSrc[iRow + 3]; + z11 = pMCUSrc[iRow + 1] + pMCUSrc[iRow + 7]; + z12 = pMCUSrc[iRow + 1] - pMCUSrc[iRow + 7]; + tmp7 = z11 + z13; + tmp11 = ((z11 - z13) * 362) >> 8; // * 1.414 + z5 = ((z10 + z12) * 473) >> 8; // * 1.8477 + tmp10 = ((z12 * 277) >> 8) - z5; // * 1.08239 + tmp12 = ((z10 * -669) >> 8) + z5; // * 2.61312 + tmp6 = tmp12 - tmp7; + tmp5 = tmp11 - tmp6; + tmp4 = tmp10 + tmp5; + } + // final output stage - scale down and range limit + pOutput[0] = ucRangeTable[(((tmp0 + tmp7) >> 5) & 0x3ff)]; + pOutput[1] = ucRangeTable[(((tmp1 + tmp6) >> 5) & 0x3ff)]; + pOutput[2] = ucRangeTable[(((tmp2 + tmp5) >> 5) & 0x3ff)]; + pOutput[3] = ucRangeTable[(((tmp3 - tmp4) >> 5) & 0x3ff)]; + pOutput[4] = ucRangeTable[(((tmp3 + tmp4) >> 5) & 0x3ff)]; + pOutput[5] = ucRangeTable[(((tmp2 - tmp5) >> 5) & 0x3ff)]; + pOutput[6] = ucRangeTable[(((tmp1 - tmp6) >> 5) & 0x3ff)]; + pOutput[7] = ucRangeTable[(((tmp0 - tmp7) >> 5) & 0x3ff)]; + pOutput += 8; + } // for each row +} + +// render grayscale MCU as either 1-bit or RGB565 +static void JPEGPutMCUGray(JPEGIMAGE *pJPEG, int x, int y) +{ + int i, j, xcount, ycount; + uint8_t *pSrc = (uint8_t *)&pJPEG->sMCUs[0]; + + // For odd-sized JPEGs, don't draw past the edge of the image bounds + xcount = ycount = 8; + if (x + 8 > pJPEG->iWidth) xcount = pJPEG->iWidth & 7; + if (y + 8 > pJPEG->iHeight) ycount = pJPEG->iHeight & 7; + if (pJPEG->ucPixelType == ONE_BIT_GRAYSCALE) { + const int iPitch = ((pJPEG->iWidth + 31) >> 3) & 0xfffc; + uint8_t *pDest = (uint8_t *) &pJPEG->pImage[(y * iPitch) + (x >> 3)]; + + for (i = 0; i < ycount; i++) { + // do up to 8 rows + uint8_t ucPixels = 0; + for (j = 0; j < xcount; j++) { + if (pSrc[j] > 127) { + ucPixels |= (1 << j); + } + } + pDest[0] = ucPixels; // one byte holds the 8 pixels + pSrc += 8; + pDest += iPitch; // next line + } + } else { // must be RGB565 output + const int iPitch = pJPEG->iWidth; + uint16_t *usDest = (uint16_t *)&pJPEG->pImage[(y * iPitch * 2) + x*2]; + + for (i=0; iiWidth; + uint8_t *pDest, *pSrc = (uint8_t *) &pJPEG->sMCUs[0]; + pDest = (uint8_t *) &pJPEG->pImage[(y * iPitch) + x]; + if (pJPEG->ucSubSample <= 0x11) { + // single Y + if (pJPEG->iOptions & JPEG_SCALE_HALF) { + // special handling of 1/2 size (pixel averaging) + int pix; + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + pix = (pSrc[0] + pSrc[1] + pSrc[8] + pSrc[9] + 2) >> 2; // average 2x2 block + pDest[j] = (uint8_t) pix; + pSrc += 2; + } + pSrc += 8; // skip extra line + pDest += iPitch; + } + return; + } + xcount = ycount = 8; // debug + if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + xcount = ycount = 2; + } else if (pJPEG->iOptions & JPEG_SCALE_EIGHTH) { + xcount = ycount = 1; + } + if ((x + 8) > pJPEG->iWidth) xcount = pJPEG->iWidth & 7; + if ((y + 8) > pJPEG->iHeight) ycount = pJPEG->iHeight & 7; + for (i = 0; i < ycount; i++) { + // do up to 8 rows + for (j = 0; j < xcount; j++) { + *pDest++ = *pSrc++; + } + pSrc += (8 - xcount); + pDest -= xcount; + pDest += iPitch; // next line + } + return; + } // single Y source + if (pJPEG->ucSubSample == 0x21) { + // stacked horizontally + if (pJPEG->iOptions & JPEG_SCALE_EIGHTH) { + // only 2 pixels emitted + pDest[0] = pSrc[0]; + pDest[1] = pSrc[128]; + return; + } /* 1/8 */ + if (pJPEG->iOptions & JPEG_SCALE_HALF) { + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + int pix; + pix = (pSrc[j * 2] + pSrc[j * 2 + 1] + pSrc[j * 2 + 8] + pSrc[j * 2 + 9] + 2) >> 2; + pDest[j] = (uint8_t) pix; + pix = (pSrc[j * 2 + 128] + pSrc[j * 2 + 129] + pSrc[j * 2 + 136] + pSrc[j * 2 + 137] + 2) >> 2; + pDest[j + 4] = (uint8_t) pix; + } + pSrc += 16; + pDest += iPitch; + } + return; + } + if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + // each MCU contributes a 2x2 block + pDest[0] = pSrc[0]; // Y0 + pDest[1] = pSrc[1]; + pDest[iPitch] = pSrc[2]; + pDest[iPitch + 1] = pSrc[3]; + + pDest[2] = pSrc[128]; // Y` + pDest[3] = pSrc[129]; + pDest[iPitch + 2] = pSrc[130]; + pDest[iPitch + 3] = pSrc[131]; + return; + } + for (i = 0; i < 8; i++) { + for (j = 0; j < 8; j++) { + pDest[j] = pSrc[j]; + pDest[j + 8] = pSrc[128 + j]; + } + pSrc += 8; + pDest += iPitch; + } + } // 0x21 + if (pJPEG->ucSubSample == 0x12) { + // stacked vertically + if (pJPEG->iOptions & JPEG_SCALE_EIGHTH) { + // only 2 pixels emitted + pDest[0] = pSrc[0]; + pDest[iPitch] = pSrc[128]; + return; + } /* 1/8 */ + if (pJPEG->iOptions & JPEG_SCALE_HALF) { + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + int pix; + pix = (pSrc[j * 2] + pSrc[j * 2 + 1] + pSrc[j * 2 + 8] + pSrc[j * 2 + 9] + 2) >> 2; + pDest[j] = (uint8_t) pix; + pix = (pSrc[j * 2 + 128] + pSrc[j * 2 + 129] + pSrc[j * 2 + 136] + pSrc[j * 2 + 137] + 2) >> 2; + pDest[4 * iPitch + j] = (uint8_t) pix; + } + pSrc += 16; + pDest += iPitch; + } + return; + } + if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + // each MCU contributes a 2x2 block + pDest[0] = pSrc[0]; // Y0 + pDest[1] = pSrc[1]; + pDest[iPitch] = pSrc[2]; + pDest[iPitch + 1] = pSrc[3]; + + pDest[iPitch * 2] = pSrc[128]; // Y` + pDest[iPitch * 2 + 1] = pSrc[129]; + pDest[iPitch * 3] = pSrc[130]; + pDest[iPitch * 3 + 1] = pSrc[131]; + return; + } + for (i = 0; i < 8; i++) { + for (j = 0; j < 8; j++) { + pDest[j] = pSrc[j]; + pDest[8 * iPitch + j] = pSrc[128 + j]; + } + pSrc += 8; + pDest += iPitch; + } + } // 0x12 + if (pJPEG->ucSubSample == 0x22) { + if (pJPEG->iOptions & JPEG_SCALE_EIGHTH) { + // each MCU contributes 1 pixel + pDest[0] = pSrc[0]; // Y0 + pDest[1] = pSrc[128]; // Y1 + pDest[iPitch] = pSrc[256]; // Y2 + pDest[iPitch + 1] = pSrc[384]; // Y3 + return; + } + if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + // each MCU contributes 2x2 pixels + pDest[0] = pSrc[0]; // Y0 + pDest[1] = pSrc[1]; + pDest[iPitch] = pSrc[2]; + pDest[iPitch + 1] = pSrc[3]; + + pDest[2] = pSrc[128]; // Y1 + pDest[3] = pSrc[129]; + pDest[iPitch + 2] = pSrc[130]; + pDest[iPitch + 3] = pSrc[131]; + + pDest[iPitch * 2] = pSrc[256]; // Y2 + pDest[iPitch * 2 + 1] = pSrc[257]; + pDest[iPitch * 3] = pSrc[258]; + pDest[iPitch * 3 + 1] = pSrc[259]; + + pDest[iPitch * 2 + 2] = pSrc[384]; // Y3 + pDest[iPitch * 2 + 3] = pSrc[385]; + pDest[iPitch * 3 + 2] = pSrc[386]; + pDest[iPitch * 3 + 3] = pSrc[387]; + return; + } + if (pJPEG->iOptions & JPEG_SCALE_HALF) { + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + int pix; + pix = (pSrc[j * 2] + pSrc[j * 2 + 1] + pSrc[j * 2 + 8] + pSrc[j * 2 + 9] + 2) >> 2; + pDest[j] = (uint8_t) pix; // Y0 + pix = (pSrc[j * 2 + 128] + pSrc[j * 2 + 129] + pSrc[j * 2 + 136] + pSrc[j * 2 + 137] + 2) >> 2; + pDest[j + 4] = (uint8_t) pix; // Y1 + pix = (pSrc[j * 2 + 256] + pSrc[j * 2 + 257] + pSrc[j * 2 + 264] + pSrc[j * 2 + 265] + 2) >> 2; + pDest[iPitch * 4 + j] = (uint8_t) pix; // Y2 + pix = (pSrc[j * 2 + 384] + pSrc[j * 2 + 385] + pSrc[j * 2 + 392] + pSrc[j * 2 + 393] + 2) >> 2; + pDest[iPitch * 4 + j + 4] = (uint8_t) pix; // Y3 + } + pSrc += 16; + pDest += iPitch; + } + return; + } + xcount = ycount = 16; + if ((x + 16) > pJPEG->iWidth) xcount = pJPEG->iWidth & 15; + if ((y + 16) > pJPEG->iHeight) ycount = pJPEG->iHeight & 15; + // The source MCUs are 64 bytes of data at offsets of 0, 128, 256, 384 + // The 4 8x8 MCUs are looping through using a single pass of x/y by + // using the 0/8 bit of the coordinate to adjust the source data offset + for (i = 0; i < ycount; i++) { + for (j = 0; j < xcount; j++) { + pDest[j] = pSrc[j+((i&8)*24)+((j&8)*15)]; + } + pSrc += 8; + pDest += iPitch; + } + } // 0x22 +} + +static void JPEGPutMCU1BitGray(JPEGIMAGE *pJPEG, int x, int y) +{ + int i, j, xcount, ycount; + const int iPitch = ((pJPEG->iWidth + 31) >> 3) & 0xfffc; + uint8_t *pDest, *pSrc = (uint8_t *) &pJPEG->sMCUs[0]; + pDest = (uint8_t *) &pJPEG->pImage[(y * iPitch) + (x >> 3)]; + if (pJPEG->ucSubSample <= 0x11) { + // single Y + if (pJPEG->iOptions & JPEG_SCALE_HALF) { + // special handling of 1/2 size (pixel averaging) + int pix; + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + pix = (pSrc[0] + pSrc[1] + pSrc[8] + pSrc[9] + 2) >> 2; // average 2x2 block + pDest[j] = (uint8_t) pix; + pSrc += 2; + } + pSrc += 8; // skip extra line + pDest += iPitch; + } + return; + } + xcount = ycount = 8; // debug + if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + xcount = ycount = 2; + } else if (pJPEG->iOptions & JPEG_SCALE_EIGHTH) { + xcount = ycount = 1; + } + for (i = 0; i < ycount; i++) { + // do up to 8 rows + uint8_t ucPixels = 0; + for (j = 0; j < xcount; j++) { + if (pSrc[j] > 127) { + ucPixels |= (1 << j); + } + } + pDest[0] = ucPixels; + pSrc += xcount; + pDest += iPitch; // next line + } + return; + } // single Y source + if (pJPEG->ucSubSample == 0x21) { + // stacked horizontally + if (pJPEG->iOptions & JPEG_SCALE_EIGHTH) { + // only 2 pixels emitted + pDest[0] = pSrc[0]; + pDest[1] = pSrc[128]; + return; + } /* 1/8 */ + if (pJPEG->iOptions & JPEG_SCALE_HALF) { + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + int pix; + pix = (pSrc[j * 2] + pSrc[j * 2 + 1] + pSrc[j * 2 + 8] + pSrc[j * 2 + 9] + 2) >> 2; + pDest[j] = (uint8_t) pix; + pix = (pSrc[j * 2 + 128] + pSrc[j * 2 + 129] + pSrc[j * 2 + 136] + pSrc[j * 2 + 137] + 2) >> 2; + pDest[j + 4] = (uint8_t) pix; + } + pSrc += 16; + pDest += iPitch; + } + return; + } + if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + // each MCU contributes a 2x2 block + pDest[0] = pSrc[0]; // Y0 + pDest[1] = pSrc[1]; + pDest[iPitch] = pSrc[2]; + pDest[iPitch + 1] = pSrc[3]; + + pDest[2] = pSrc[128]; // Y` + pDest[3] = pSrc[129]; + pDest[iPitch + 2] = pSrc[130]; + pDest[iPitch + 3] = pSrc[131]; + return; + } + for (i = 0; i < 8; i++) { + uint8_t uc0 = 0, uc1 = 0; + for (j = 0; j < 8; j++) { + if (pSrc[j] > 127) { + uc0 |= (1 << j); + } + if (pSrc[128 + j] > 127) { + uc1 |= (1 << j); + } + } + pDest[0] = uc0; + pDest[1] = uc1; + pSrc += 8; + pDest += iPitch; + } + } // 0x21 + if (pJPEG->ucSubSample == 0x12) { + // stacked vertically + if (pJPEG->iOptions & JPEG_SCALE_EIGHTH) { + // only 2 pixels emitted + pDest[0] = pSrc[0]; + pDest[iPitch] = pSrc[128]; + return; + } /* 1/8 */ + if (pJPEG->iOptions & JPEG_SCALE_HALF) { + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + int pix; + pix = (pSrc[j * 2] + pSrc[j * 2 + 1] + pSrc[j * 2 + 8] + pSrc[j * 2 + 9] + 2) >> 2; + pDest[j] = (uint8_t) pix; + pix = (pSrc[j * 2 + 128] + pSrc[j * 2 + 129] + pSrc[j * 2 + 136] + pSrc[j * 2 + 137] + 2) >> 2; + pDest[4 * iPitch + j] = (uint8_t) pix; + } + pSrc += 16; + pDest += iPitch; + } + return; + } + if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + // each MCU contributes a 2x2 block + pDest[0] = pSrc[0]; // Y0 + pDest[1] = pSrc[1]; + pDest[iPitch] = pSrc[2]; + pDest[iPitch + 1] = pSrc[3]; + + pDest[iPitch * 2] = pSrc[128]; // Y` + pDest[iPitch * 2 + 1] = pSrc[129]; + pDest[iPitch * 3] = pSrc[130]; + pDest[iPitch * 3 + 1] = pSrc[131]; + return; + } + for (i = 0; i < 8; i++) { + uint8_t uc0 = 0, uc1 = 0; + for (j = 0; j < 8; j++) { + if (pSrc[j] > 127) { + uc0 |= (1 << j); + } + if (pSrc[128 + j] > 127) { + uc1 |= (1 << j); + } + } + pDest[0] = uc0; + pDest[8 * iPitch] = uc1; + pSrc += 8; + pDest += iPitch; + } + } // 0x12 + if (pJPEG->ucSubSample == 0x22) { + if (pJPEG->iOptions & JPEG_SCALE_EIGHTH) { + // each MCU contributes 1 pixel + pDest[0] = pSrc[0]; // Y0 + pDest[1] = pSrc[128]; // Y1 + pDest[iPitch] = pSrc[256]; // Y2 + pDest[iPitch + 1] = pSrc[384]; // Y3 + return; + } + if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + // each MCU contributes 2x2 pixels + pDest[0] = pSrc[0]; // Y0 + pDest[1] = pSrc[1]; + pDest[iPitch] = pSrc[2]; + pDest[iPitch + 1] = pSrc[3]; + + pDest[2] = pSrc[128]; // Y1 + pDest[3] = pSrc[129]; + pDest[iPitch + 2] = pSrc[130]; + pDest[iPitch + 3] = pSrc[131]; + + pDest[iPitch * 2] = pSrc[256]; // Y2 + pDest[iPitch * 2 + 1] = pSrc[257]; + pDest[iPitch * 3] = pSrc[258]; + pDest[iPitch * 3 + 1] = pSrc[259]; + + pDest[iPitch * 2 + 2] = pSrc[384]; // Y3 + pDest[iPitch * 2 + 3] = pSrc[385]; + pDest[iPitch * 3 + 2] = pSrc[386]; + pDest[iPitch * 3 + 3] = pSrc[387]; + return; + } + if (pJPEG->iOptions & JPEG_SCALE_HALF) { + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + int pix; + pix = (pSrc[j * 2] + pSrc[j * 2 + 1] + pSrc[j * 2 + 8] + pSrc[j * 2 + 9] + 2) >> 2; + pDest[j] = (uint8_t) pix; // Y0 + pix = (pSrc[j * 2 + 128] + pSrc[j * 2 + 129] + pSrc[j * 2 + 136] + pSrc[j * 2 + 137] + 2) >> 2; + pDest[j + 4] = (uint8_t) pix; // Y1 + pix = (pSrc[j * 2 + 256] + pSrc[j * 2 + 257] + pSrc[j * 2 + 264] + pSrc[j * 2 + 265] + 2) >> 2; + pDest[iPitch * 4 + j] = (uint8_t) pix; // Y2 + pix = (pSrc[j * 2 + 384] + pSrc[j * 2 + 385] + pSrc[j * 2 + 392] + pSrc[j * 2 + 393] + 2) >> 2; + pDest[iPitch * 4 + j + 4] = (uint8_t) pix; // Y3 + } + pSrc += 16; + pDest += iPitch; + } + return; + } + for (i = 0; i < 8; i++) { + uint8_t uc00 = 0, uc10 = 0, uc01 = 0, uc11 = 0; + for (j = 0; j < 8; j++) { + if (pSrc[j] > 127) { + uc00 |= (1 << j); // Y0 + } + if (pSrc[j + 128] > 127) { + uc10 |= (1 << j); // Y1 + } + if (pSrc[j + 256] > 127) { + uc01 |= (1 << j); // Y2 + } + if (pSrc[j + 384] > 127) { + uc11 |= (1 << j); // Y3 + } + } + pDest[0] = uc00; // Y0 + pDest[1] = uc10; // Y1 + pDest[iPitch * 8] = uc01; // Y2 + pDest[iPitch * 8 + 1] = uc11; // Y3 + pSrc += 8; + pDest += iPitch; + } + } // 0x22 +} + +static void JPEGPixelLE(uint16_t *pDest, int iY, int iCb, int iCr) +{ + uint32_t ulPixel; + uint32_t ulCbCr = (iCb | (iCr << 16)); + uint32_t ulTmp; // for green calc + ulTmp = -1409; + ulTmp = (ulTmp & 0xffff) | (-2925 << 16); + ulCbCr = __SSUB16(ulCbCr, 0x00800080); // dual 16-bit subtraction + ulPixel = __SMLAD(ulCbCr, ulTmp, iY) >> 14; // G + ulPixel = __USAT16(ulPixel, 6) << 5; // range limit to 6 bits + ulTmp = __SMLAD(7258, ulCbCr, iY) >> 15; // Blue + ulTmp = __USAT16(ulTmp, 5); // range limit to 5 bits + ulPixel |= ulTmp; // now we have G + B + ulTmp = __SMLAD(5742, ulCbCr >> 16, iY) >> 15; // Red + ulTmp = __USAT16(ulTmp, 5); // range limit to 5 bits + ulPixel |= (ulTmp << 11); // now we have R + G + B + pDest[0] = (uint16_t) ulPixel; +} + +static void JPEGPixel2LE(uint16_t *pDest, int iY1, int iY2, int iCb, int iCr) +{ + uint32_t ulPixel1, ulPixel2; + uint32_t ulCbCr = (iCb | (iCr << 16)); + uint32_t ulTmp2, ulTmp; // for green calc + ulTmp = -1409; + ulTmp = (ulTmp & 0xffff) | (-2925 << 16); + ulCbCr = __SSUB16(ulCbCr, 0x00800080); // dual 16-bit subtraction + ulPixel1 = __SMLAD(ulCbCr, ulTmp, iY1) >> 14; // G for pixel 1 + ulPixel2 = __SMLAD(ulCbCr, ulTmp, iY2) >> 14; // G for pixel 2 + ulPixel1 |= (ulPixel2 << 16); + ulPixel1 = __USAT16(ulPixel1, 6) << 5; // range limit both to 6 bits + ulTmp = __SMLAD(7258, ulCbCr, iY1) >> 15; // Blue 1 + ulTmp2 = __SMLAD(7258, ulCbCr, iY2) >> 15; // Blue 2 + ulTmp = __USAT16(ulTmp | (ulTmp2 << 16), 5); // range limit both to 5 bits + ulPixel1 |= ulTmp; // now we have G + B + ulTmp = __SMLAD(5742, ulCbCr >> 16, iY1) >> 15; // Red 1 + ulTmp2 = __SMLAD(5742, ulCbCr >> 16, iY2) >> 15; // Red 2 + ulTmp = __USAT16(ulTmp | (ulTmp2 << 16), 5); // range limit both to 5 bits + ulPixel1 |= (ulTmp << 11); // now we have R + G + B + *(uint32_t *) &pDest[0] = ulPixel1; +} + +static void JPEGPutMCU11(JPEGIMAGE *pJPEG, int x, int y) +{ + int iCr, iCb; + signed int Y; + int iCol; + int iRow; + const int iPitch = pJPEG->iWidth; + uint8_t *pY, *pCr, *pCb; + uint16_t *pOutput = (uint16_t *) &pJPEG->pImage[(y * iPitch * 2) + x * 2]; + + pY = (unsigned char *) &pJPEG->sMCUs[0 * DCTSIZE]; + pCb = (unsigned char *) &pJPEG->sMCUs[1 * DCTSIZE]; + pCr = (unsigned char *) &pJPEG->sMCUs[2 * DCTSIZE]; + + if (pJPEG->iOptions & JPEG_SCALE_HALF) { + for (iRow = 0; iRow < 4; iRow++) { + // up to 8 rows to do + for (iCol = 0; iCol < 4; iCol++) { + // up to 4x2 cols to do + iCr = (pCr[0] + pCr[1] + pCr[8] + pCr[9] + 2) >> 2; + iCb = (pCb[0] + pCb[1] + pCb[8] + pCb[9] + 2) >> 2; + Y = (pY[0] + pY[1] + pY[8] + pY[9]) << 10; + JPEGPixelLE(pOutput + iCol, Y, iCb, iCr); + pCr += 2; + pCb += 2; + pY += 2; + } // for col + pCr += 8; + pCb += 8; + pY += 8; + pOutput += iPitch; + } // for row + return; + } + if (pJPEG->iOptions & JPEG_SCALE_EIGHTH) { + // special case for 1/8 scaling + // only 4 pixels to draw, so no looping needed + iCr = pCr[0]; + iCb = pCb[0]; + Y = (int) (pY[0]) << 12; + JPEGPixelLE(pOutput, Y, iCb, iCr); + return; + } + if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + // special case for 1/4 scaling + iCr = *pCr++; + iCb = *pCb++; + Y = (int) (*pY++) << 12; + JPEGPixelLE(pOutput, Y, iCb, iCr); + iCr = *pCr++; + iCb = *pCb++; + Y = (int) (*pY++) << 12; + JPEGPixelLE(pOutput + 1, Y, iCb, iCr); + iCr = *pCr++; + iCb = *pCb++; + Y = (int) (*pY++) << 12; + JPEGPixelLE(pOutput + iPitch, Y, iCb, iCr); + iCr = *pCr++; + iCb = *pCb++; + Y = (int) (*pY++) << 12; + JPEGPixelLE(pOutput + 1 + iPitch, Y, iCb, iCr); + return; + } + for (iRow = 0; iRow < 8; iRow++) { + // up to 8 rows to do + for (iCol = 0; iCol < 8; iCol++) { + // up to 4x2 cols to do + iCr = *pCr++; + iCb = *pCb++; + Y = (int) (*pY++) << 12; + JPEGPixelLE(pOutput + iCol, Y, iCb, iCr); + } // for col + pOutput += iPitch; + } // for row +} /* JPEGPutMCU11() */ + +static void JPEGPutMCU22(JPEGIMAGE *pJPEG, int x, int y) +{ + uint32_t Cr,Cb; + signed int Y1, Y2, Y3, Y4; + int iRow, iRowLimit, iCol, iXCount1, iXCount2, iYCount; + unsigned char *pY, *pCr, *pCb; + const int iPitch = pJPEG->iWidth; + int bUseOdd1, bUseOdd2; // special case where 24bpp odd sized image can clobber first column + uint16_t *pOutput = (uint16_t *) &pJPEG->pImage[(y * iPitch * 2) + x * 2]; + + pY = (unsigned char *) &pJPEG->sMCUs[0 * DCTSIZE]; + pCb = (unsigned char *) &pJPEG->sMCUs[4 * DCTSIZE]; + pCr = (unsigned char *) &pJPEG->sMCUs[5 * DCTSIZE]; + + if (pJPEG->iOptions & JPEG_SCALE_HALF) { + // special handling of 1/2 size (pixel averaging) + for (iRow = 0; iRow < 4; iRow++) { + // 16x16 becomes 8x8 of 2x2 pixels + for (iCol = 0; iCol < 4; iCol++) { + Y1 = (pY[iCol * 2] + pY[iCol * 2 + 1] + pY[iCol * 2 + 8] + pY[iCol * 2 + 9]) << 10; + Cb = pCb[iCol]; + Cr = pCr[iCol]; + JPEGPixelLE(pOutput + iCol, Y1, Cb, Cr); // top left + Y1 = (pY[iCol * 2 + (DCTSIZE * 2)] + pY[iCol * 2 + 1 + (DCTSIZE * 2)] + + pY[iCol * 2 + 8 + (DCTSIZE * 2)] + pY[iCol * 2 + 9 + (DCTSIZE * 2)]) << 10; + Cb = pCb[iCol + 4]; + Cr = pCr[iCol + 4]; + JPEGPixelLE(pOutput + iCol + 4, Y1, Cb, Cr); // top right + Y1 = (pY[iCol * 2 + (DCTSIZE * 4)] + pY[iCol * 2 + 1 + (DCTSIZE * 4)] + + pY[iCol * 2 + 8 + (DCTSIZE * 4)] + pY[iCol * 2 + 9 + (DCTSIZE * 4)]) << 10; + Cb = pCb[iCol + 32]; + Cr = pCr[iCol + 32]; + JPEGPixelLE(pOutput + iCol + iPitch * 4, Y1, Cb, Cr); // bottom left + Y1 = (pY[iCol * 2 + (DCTSIZE * 6)] + pY[iCol * 2 + 1 + (DCTSIZE * 6)] + + pY[iCol * 2 + 8 + (DCTSIZE * 6)] + pY[iCol * 2 + 9 + (DCTSIZE * 6)]) << 10; + Cb = pCb[iCol + 32 + 4]; + Cr = pCr[iCol + 32 + 4]; + JPEGPixelLE(pOutput + iCol + 4 + iPitch * 4, Y1, Cb, Cr); // bottom right + } + pY += 8; + pCb += 8; + pCr += 8; + pOutput += iPitch; + } + return; + } + if (pJPEG->iOptions & JPEG_SCALE_EIGHTH) { + Y1 = pY[0] << 12; // scale to level of conversion table + Cb = pCb[0]; + Cr = pCr[0]; + JPEGPixelLE(pOutput, Y1, Cb, Cr); + // top right block + Y1 = pY[DCTSIZE * 2] << 12; // scale to level of conversion table + JPEGPixelLE(pOutput + 1, Y1, Cb, Cr); + // bottom left block + Y1 = pY[DCTSIZE * 4] << 12; // scale to level of conversion table + JPEGPixelLE(pOutput + iPitch, Y1, Cb, Cr); + // bottom right block + Y1 = pY[DCTSIZE * 6] << 12; // scale to level of conversion table + JPEGPixelLE(pOutput + 1 + iPitch, Y1, Cb, Cr); + return; + } + if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + // special case of 1/4 + for (iRow = 0; iRow < 2; iRow++) { + for (iCol = 0; iCol < 2; iCol++) { + // top left block + Y1 = pY[iCol] << 12; // scale to level of conversion table + Cb = pCb[0]; + Cr = pCr[0]; + JPEGPixelLE(pOutput + iCol, Y1, Cb, Cr); + // top right block + Y1 = pY[iCol + (DCTSIZE * 2)] << 12; // scale to level of conversion table + Cb = pCb[1]; + Cr = pCr[1]; + JPEGPixelLE(pOutput + 2 + iCol, Y1, Cb, Cr); + // bottom left block + Y1 = pY[iCol + DCTSIZE * 4] << 12; // scale to level of conversion table + Cb = pCb[2]; + Cr = pCr[2]; + JPEGPixelLE(pOutput + iPitch * 2 + iCol, Y1, Cb, Cr); + // bottom right block + Y1 = pY[iCol + DCTSIZE * 6] << 12; // scale to level of conversion table + Cb = pCb[3]; + Cr = pCr[3]; + JPEGPixelLE(pOutput + iPitch * 2 + 2 + iCol, Y1, Cb, Cr); + } // for each column + pY += 2; // skip 1 line of source pixels + pOutput += iPitch; + } + return; + } + /* Convert YCC pixels into RGB pixels and store in output image */ + iYCount = 4; + iRowLimit = 16; // assume all rows possible to draw + if ((y + 15) >= pJPEG->iHeight) { + iRowLimit = pJPEG->iHeight & 15; + if (iRowLimit < 8) + iYCount = iRowLimit >> 1; + } + bUseOdd1 = bUseOdd2 = 1; // assume odd column can be used + if ((x + 15) >= pJPEG->iWidth) { + iCol = (((pJPEG->iWidth & 15) + 1) >> 1); + if (iCol >= 4) { + iXCount1 = 4; + iXCount2 = iCol - 4; + if (pJPEG->iWidth & 1 && (iXCount2 * 2) + 8 + (x * 16) > pJPEG->iWidth) { + bUseOdd2 = 0; + } + } else { + iXCount1 = iCol; + iXCount2 = 0; + if (pJPEG->iWidth & 1 && (iXCount1 * 2) + (x * 16) > pJPEG->iWidth) { + bUseOdd1 = 0; + } + } + } else { + iXCount1 = iXCount2 = 4; + } + for (iRow = 0; iRow < iYCount; iRow++) { + // up to 4 rows to do + for (iCol = 0; iCol < iXCount1; iCol++) { + // up to 4 cols to do + // for top left block + Y1 = pY[iCol * 2]; + Y2 = pY[iCol * 2 + 1]; + Y3 = pY[iCol * 2 + 8]; + Y4 = pY[iCol * 2 + 9]; + Y1 <<= 12; // scale to level of conversion table + Y2 <<= 12; + Y3 <<= 12; + Y4 <<= 12; + Cb = pCb[iCol]; + Cr = pCr[iCol]; + if (bUseOdd1 || iCol != (iXCount1 - 1)) { + // only render if it won't go off the right edge + JPEGPixel2LE(pOutput + (iCol << 1), Y1, Y2, Cb, Cr); + JPEGPixel2LE(pOutput + iPitch + (iCol << 1), Y3, Y4, Cb, Cr); + } else { + JPEGPixelLE(pOutput + (iCol << 1), Y1, Cb, Cr); + JPEGPixelLE(pOutput + iPitch + (iCol << 1), Y3, Cb, Cr); + } + // for top right block + if (iCol < iXCount2) { + Y1 = pY[iCol * 2 + DCTSIZE * 2]; + Y2 = pY[iCol * 2 + 1 + DCTSIZE * 2]; + Y3 = pY[iCol * 2 + 8 + DCTSIZE * 2]; + Y4 = pY[iCol * 2 + 9 + DCTSIZE * 2]; + Y1 <<= 12; // scale to level of conversion table + Y2 <<= 12; + Y3 <<= 12; + Y4 <<= 12; + Cb = pCb[iCol + 4]; + Cr = pCr[iCol + 4]; + if (bUseOdd2 || iCol != (iXCount2 - 1)) { + // only render if it won't go off the right edge + JPEGPixel2LE(pOutput + 8 + (iCol << 1), Y1, Y2, Cb, Cr); + JPEGPixel2LE(pOutput + iPitch + 8 + (iCol << 1), Y3, Y4, Cb, Cr); + } else { + JPEGPixelLE(pOutput + 8 + (iCol << 1), Y1, Cb, Cr); + JPEGPixelLE(pOutput + iPitch + 8 + (iCol << 1), Y3, Cb, Cr); + } + } + if (iRowLimit > 8) { + // for bottom left block + Y1 = pY[iCol * 2 + DCTSIZE * 4]; + Y2 = pY[iCol * 2 + 1 + DCTSIZE * 4]; + Y3 = pY[iCol * 2 + 8 + DCTSIZE * 4]; + Y4 = pY[iCol * 2 + 9 + DCTSIZE * 4]; + Y1 <<= 12; // scale to level of conversion table + Y2 <<= 12; + Y3 <<= 12; + Y4 <<= 12; + Cb = pCb[iCol + 32]; + Cr = pCr[iCol + 32]; + if (bUseOdd1 || iCol != (iXCount1 - 1)) { + // only render if it won't go off the right edge + JPEGPixel2LE(pOutput + iPitch * 8 + (iCol << 1), Y1, Y2, Cb, Cr); + JPEGPixel2LE(pOutput + iPitch * 9 + (iCol << 1), Y3, Y4, Cb, Cr); + } else { + JPEGPixelLE(pOutput + iPitch * 8 + (iCol << 1), Y1, Cb, Cr); + JPEGPixelLE(pOutput + iPitch * 9 + (iCol << 1), Y3, Cb, Cr); + } + // for bottom right block + if (iCol < iXCount2) { + Y1 = pY[iCol * 2 + DCTSIZE * 6]; + Y2 = pY[iCol * 2 + 1 + DCTSIZE * 6]; + Y3 = pY[iCol * 2 + 8 + DCTSIZE * 6]; + Y4 = pY[iCol * 2 + 9 + DCTSIZE * 6]; + Y1 <<= 12; // scale to level of conversion table + Y2 <<= 12; + Y3 <<= 12; + Y4 <<= 12; + Cb = pCb[iCol + 36]; + Cr = pCr[iCol + 36]; + if (bUseOdd2 || iCol != (iXCount2 - 1)) { + // only render if it won't go off the right edge + JPEGPixel2LE(pOutput + iPitch * 8 + 8 + (iCol << 1), Y1, Y2, Cb, Cr); + JPEGPixel2LE(pOutput + iPitch * 9 + 8 + (iCol << 1), Y3, Y4, Cb, Cr); + } else { + JPEGPixelLE(pOutput + iPitch * 8 + 8 + (iCol << 1), Y1, Cb, Cr); + JPEGPixelLE(pOutput + iPitch * 9 + 8 + (iCol << 1), Y3, Cb, Cr); + } + } + } // row limit > 8 + } // for each column + pY += 16; // skip to next line of source pixels + pCb += 8; + pCr += 8; + pOutput += iPitch * 2; + } +} + +static void JPEGPutMCU12(JPEGIMAGE *pJPEG, int x, int y) +{ + uint32_t Cr,Cb; + signed int Y1, Y2; + int iRow, iCol, iXCount, iYCount; + uint8_t *pY, *pCr, *pCb; + const int iPitch = pJPEG->iWidth; + uint16_t *pOutput = (uint16_t *) &pJPEG->pImage[(y * iPitch * 2) + x * 2]; + + pY = (uint8_t *) &pJPEG->sMCUs[0 * DCTSIZE]; + pCb = (uint8_t *) &pJPEG->sMCUs[2 * DCTSIZE]; + pCr = (uint8_t *) &pJPEG->sMCUs[3 * DCTSIZE]; + + if (pJPEG->iOptions & JPEG_SCALE_HALF) { + for (iRow = 0; iRow < 4; iRow++) { + for (iCol = 0; iCol < 4; iCol++) { + Y1 = (pY[0] + pY[1] + pY[8] + pY[9]) << 10; + Cb = (pCb[0] + pCb[1] + 1) >> 1; + Cr = (pCr[0] + pCr[1] + 1) >> 1; + JPEGPixelLE(pOutput + iCol, Y1, Cb, Cr); + Y1 = (pY[DCTSIZE * 2] + pY[DCTSIZE * 2 + 1] + pY[DCTSIZE * 2 + 8] + pY[DCTSIZE * 2 + 9]) << 10; + Cb = (pCb[32] + pCb[33] + 1) >> 1; + Cr = (pCr[32] + pCr[33] + 1) >> 1; + JPEGPixelLE(pOutput + iCol + iPitch, Y1, Cb, Cr); + pCb += 2; + pCr += 2; + pY += 2; + } + pY += 8; + pOutput += iPitch * 2; + } + return; + } + if (pJPEG->iOptions & JPEG_SCALE_EIGHTH) { + Y1 = pY[0] << 12; + Y2 = pY[DCTSIZE * 2] << 12; + Cb = pCb[0]; + Cr = pCr[0]; + JPEGPixelLE(pOutput, Y1, Cb, Cr); + JPEGPixelLE(pOutput + iPitch, Y2, Cb, Cr); + return; + } + if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + // draw a 2x4 block + Y1 = pY[0] << 12; + Y2 = pY[2] << 12; + Cb = pCb[0]; + Cr = pCr[0]; + JPEGPixelLE(pOutput, Y1, Cb, Cr); + JPEGPixelLE(pOutput + iPitch, Y2, Cb, Cr); + Y1 = pY[1] << 12; + Y2 = pY[3] << 12; + Cb = pCb[1]; + Cr = pCr[1]; + JPEGPixelLE(pOutput + 1, Y1, Cb, Cr); + JPEGPixelLE(pOutput + 1 + iPitch, Y2, Cb, Cr); + pY += DCTSIZE * 2; // next Y block below + Y1 = pY[0] << 12; + Y2 = pY[2] << 12; + Cb = pCb[2]; + Cr = pCr[2]; + JPEGPixelLE(pOutput + iPitch * 2, Y1, Cb, Cr); + JPEGPixelLE(pOutput + iPitch * 3, Y2, Cb, Cr); + Y1 = pY[1] << 12; + Y2 = pY[3] << 12; + Cb = pCb[3]; + Cr = pCr[3]; + JPEGPixelLE(pOutput + 1 + iPitch * 2, Y1, Cb, Cr); + JPEGPixelLE(pOutput + 1 + iPitch * 3, Y2, Cb, Cr); + return; + } + /* Convert YCC pixels into RGB pixels and store in output image */ + iYCount = 16; + iXCount = 8; + for (iRow = 0; iRow < iYCount; iRow += 2) { + // up to 16 rows to do + for (iCol = 0; iCol < iXCount; iCol++) { + // up to 8 cols to do + Y1 = pY[iCol]; + Y2 = pY[iCol + 8]; + Y1 <<= 12; // scale to level of conversion table + Y2 <<= 12; + Cb = pCb[iCol]; + Cr = pCr[iCol]; + JPEGPixelLE(pOutput + iCol, Y1, Cb, Cr); + JPEGPixelLE(pOutput + iPitch + iCol, Y2, Cb, Cr); + } + pY += 16; // skip to next 2 lines of source pixels + if (iRow == 6) { + // next MCU block, skip ahead to correct spot + pY += (128 - 64); + } + pCb += 8; + pCr += 8; + pOutput += iPitch * 2; // next 2 lines of dest pixels + } +} + +static void JPEGPutMCU21(JPEGIMAGE *pJPEG, int x, int y) +{ + int iCr, iCb; + signed int Y1, Y2; + int iCol; + int iRow; + uint8_t *pY, *pCr, *pCb; + const int iPitch = pJPEG->iWidth; + uint16_t *pOutput = (uint16_t *) &pJPEG->pImage[(y * iPitch * 2) + x * 2]; + + pY = (uint8_t *) &pJPEG->sMCUs[0 * DCTSIZE]; + pCb = (uint8_t *) &pJPEG->sMCUs[2 * DCTSIZE]; + pCr = (uint8_t *) &pJPEG->sMCUs[3 * DCTSIZE]; + + if (pJPEG->iOptions & JPEG_SCALE_HALF) { + for (iRow = 0; iRow < 4; iRow++) { + for (iCol = 0; iCol < 4; iCol++) { + // left block + iCr = (pCr[0] + pCr[8] + 1) >> 1; + iCb = (pCb[0] + pCb[8] + 1) >> 1; + Y1 = (signed int) (pY[0] + pY[1] + pY[8] + pY[9]) << 10; + JPEGPixelLE(pOutput + iCol, Y1, iCb, iCr); + // right block + iCr = (pCr[4] + pCr[12] + 1) >> 1; + iCb = (pCb[4] + pCb[12] + 1) >> 1; + Y1 = (signed int) (pY[128] + pY[129] + pY[136] + pY[137]) << 10; + JPEGPixelLE(pOutput + iCol + 4, Y1, iCb, iCr); + pCb++; + pCr++; + pY += 2; + } + pCb += 12; + pCr += 12; + pY += 8; + pOutput += iPitch; + } + return; + } + if (pJPEG->iOptions & JPEG_SCALE_EIGHTH) { + // draw 2 pixels + iCr = pCr[0]; + iCb = pCb[0]; + Y1 = (signed int) (pY[0]) << 12; + Y2 = (signed int) (pY[DCTSIZE * 2]) << 12; + JPEGPixel2LE(pOutput, Y1, Y2, iCb, iCr); + return; + } + if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + // draw 4x2 pixels + // top left + iCr = pCr[0]; + iCb = pCb[0]; + Y1 = (signed int) (pY[0]) << 12; + Y2 = (signed int) (pY[1]) << 12; + JPEGPixel2LE(pOutput, Y1, Y2, iCb, iCr); + // top right + iCr = pCr[1]; + iCb = pCb[1]; + Y1 = (signed int) pY[DCTSIZE * 2] << 12; + Y2 = (signed int) pY[DCTSIZE * 2 + 1] << 12; + JPEGPixel2LE(pOutput + 2, Y1, Y2, iCb, iCr); + // bottom left + iCr = pCr[2]; + iCb = pCb[2]; + Y1 = (signed int) (pY[2]) << 12; + Y2 = (signed int) (pY[3]) << 12; + JPEGPixel2LE(pOutput + iPitch, Y1, Y2, iCb, iCr); + // bottom right + iCr = pCr[3]; + iCb = pCb[3]; + Y1 = (signed int) pY[DCTSIZE * 2 + 2] << 12; + Y2 = (signed int) pY[DCTSIZE * 2 + 3] << 12; + JPEGPixel2LE(pOutput + iPitch + 2, Y1, Y2, iCb, iCr); + return; + } + /* Convert YCC pixels into RGB pixels and store in output image */ + for (iRow = 0; iRow < 8; iRow++) { + // up to 8 rows to do + for (iCol = 0; iCol < 4; iCol++) { + // up to 4x2 cols to do + // left block + iCr = *pCr++; + iCb = *pCb++; + Y1 = (signed int) (*pY++) << 12; + Y2 = (signed int) (*pY++) << 12; + JPEGPixel2LE(pOutput + iCol * 2, Y1, Y2, iCb, iCr); + // right block + iCr = pCr[3]; + iCb = pCb[3]; + Y1 = (signed int) pY[126] << 12; + Y2 = (signed int) pY[127] << 12; + JPEGPixel2LE(pOutput + 8 + iCol * 2, Y1, Y2, iCb, iCr); + } // for col + pCb += 4; + pCr += 4; + pOutput += iPitch; + } // for row +} + +// Decode the image +// returns 0 for error, 1 for success +static int DecodeJPEG(JPEGIMAGE *pJPEG) +{ + int cx, cy, x, y, mcuCX, mcuCY; + int iLum0, iLum1, iLum2, iLum3, iCr, iCb; + signed int iDCPred0, iDCPred1, iDCPred2; + int i, iQuant1, iQuant2, iQuant3, iErr; + uint8_t c; + int iMCUCount, /*xoff, iPitch,*/ bThumbnail = 0; + int bContinue = 1; // early exit if the DRAW callback wants to stop + uint32_t l, *pl; + unsigned char cDCTable0, cACTable0, cDCTable1, cACTable1, cDCTable2, cACTable2; + int iMaxFill = 16, iScaleShift = 0; + + // Requested the Exif thumbnail + if (pJPEG->iOptions & JPEG_EXIF_THUMBNAIL) { + if (pJPEG->iThumbData == 0 || pJPEG->iThumbWidth == 0) { + // doesn't exist + pJPEG->iError = JPEG_INVALID_PARAMETER; + return 0; + } + if (!JPEGParseInfo(pJPEG, 1)) { + // parse the embedded thumbnail file header + return 0; // something went wrong + } + } + // Fast downscaling options + if (pJPEG->iOptions & JPEG_SCALE_HALF) { + iScaleShift = 1; + } else if (pJPEG->iOptions & JPEG_SCALE_QUARTER) { + iScaleShift = 2; + iMaxFill = 1; + } else if (pJPEG->iOptions & JPEG_SCALE_EIGHTH) { + iScaleShift = 3; + iMaxFill = 1; + bThumbnail = 1; + } + + // reorder and fix the quantization table for decoding + JPEGFixQuantD(pJPEG); + pJPEG->bb.ulBits = MOTOLONG(&pJPEG->ucFileBuf[0]); // preload first 4 bytes + pJPEG->bb.pBuf = pJPEG->ucFileBuf; + pJPEG->bb.ulBitOff = 0; + + cDCTable0 = pJPEG->JPCI[0].dc_tbl_no; + cACTable0 = pJPEG->JPCI[0].ac_tbl_no; + cDCTable1 = pJPEG->JPCI[1].dc_tbl_no; + cACTable1 = pJPEG->JPCI[1].ac_tbl_no; + cDCTable2 = pJPEG->JPCI[2].dc_tbl_no; + cACTable2 = pJPEG->JPCI[2].ac_tbl_no; + iDCPred0 = iDCPred1 = iDCPred2 = mcuCX = mcuCY = 0; + + switch (pJPEG->ucSubSample) { + // set up the parameters for the different subsampling options + case 0x00: // fake value to handle grayscale + case 0x01: // fake value to handle sRGB/CMYK + case 0x11: + cx = (pJPEG->iWidth + 7) >> 3; // number of MCU blocks + cy = (pJPEG->iHeight + 7) >> 3; + iCr = MCU1; + iCb = MCU2; + mcuCX = mcuCY = 8; + break; + case 0x12: + cx = (pJPEG->iWidth + 7) >> 3; // number of MCU blocks + cy = (pJPEG->iHeight + 15) >> 4; + iCr = MCU2; + iCb = MCU3; + mcuCX = 8; + mcuCY = 16; + break; + case 0x21: + cx = (pJPEG->iWidth + 15) >> 4; // number of MCU blocks + cy = (pJPEG->iHeight + 7) >> 3; + iCr = MCU2; + iCb = MCU3; + mcuCX = 16; + mcuCY = 8; + break; + case 0x22: + cx = (pJPEG->iWidth + 15) >> 4; // number of MCU blocks + cy = (pJPEG->iHeight + 15) >> 4; + iCr = MCU4; + iCb = MCU5; + mcuCX = mcuCY = 16; + break; + default: // to suppress compiler warning + cx = cy = 0; + iCr = iCb = 0; + break; + } + // Scale down the MCUs by the requested amount + mcuCX >>= iScaleShift; + mcuCY >>= iScaleShift; + + iQuant1 = pJPEG->sQuantTable[pJPEG->JPCI[0].quant_tbl_no * DCTSIZE]; // DC quant values + iQuant2 = pJPEG->sQuantTable[pJPEG->JPCI[1].quant_tbl_no * DCTSIZE]; + iQuant3 = pJPEG->sQuantTable[pJPEG->JPCI[2].quant_tbl_no * DCTSIZE]; + // luminance values are always in these positions + iLum0 = MCU0; + iLum1 = MCU1; + iLum2 = MCU2; + iLum3 = MCU3; + iErr = 0; + pJPEG->iResCount = pJPEG->iResInterval; + // Calculate how many MCUs we can fit in the pixel buffer to maximize LCD drawing speed + iMCUCount = MAX_BUFFERED_PIXELS / (mcuCX * mcuCY); + if (pJPEG->ucPixelType == EIGHT_BIT_GRAYSCALE) { + iMCUCount *= 2; // each pixel is only 1 byte + } + if (iMCUCount > cx) { + iMCUCount = cx; // don't go wider than the image + } + if (iMCUCount > pJPEG->iMaxMCUs) { + // did the user set an upper bound on how many pixels per JPEGDraw callback? + iMCUCount = pJPEG->iMaxMCUs; + } + if (pJPEG->ucPixelType > EIGHT_BIT_GRAYSCALE) { + // dithered, override the max MCU count + iMCUCount = cx; // do the whole row + } + for (y = 0; y < cy && bContinue; y++) { + for (x = 0; x < cx && bContinue && iErr == 0; x++) { + pJPEG->ucACTable = cACTable0; + pJPEG->ucDCTable = cDCTable0; + // do the first luminance component + iErr = JPEGDecodeMCU(pJPEG, iLum0, &iDCPred0); + if (pJPEG->ucMaxACCol == 0 || bThumbnail) { + // no AC components, save some time + pl = (uint32_t *) &pJPEG->sMCUs[iLum0]; + c = ucRangeTable[((iDCPred0 * iQuant1) >> 5) & 0x3ff]; + l = c | ((uint32_t) c << 8) | ((uint32_t) c << 16) | ((uint32_t) c << 24); + // dct stores byte values + for (i = 0; i < iMaxFill; i++) { + // 8x8 bytes = 16 longs + pl[i] = l; + } + } else { + // first quantization table + JPEGIDCT(pJPEG, iLum0, pJPEG->JPCI[0].quant_tbl_no, (pJPEG->ucMaxACCol | (pJPEG->ucMaxACRow << 8))); + } + // do the second luminance component + if (pJPEG->ucSubSample > 0x11) { + // subsampling + iErr |= JPEGDecodeMCU(pJPEG, iLum1, &iDCPred0); + if (pJPEG->ucMaxACCol == 0 || bThumbnail) { + // no AC components, save some time + c = ucRangeTable[((iDCPred0 * iQuant1) >> 5) & 0x3ff]; + l = c | ((uint32_t) c << 8) | ((uint32_t) c << 16) | ((uint32_t) c << 24); + // dct stores byte values + pl = (uint32_t *) &pJPEG->sMCUs[iLum1]; + for (i = 0; i < iMaxFill; i++) { + // 8x8 bytes = 16 longs + pl[i] = l; + } + } else { + // first quantization table + JPEGIDCT(pJPEG, iLum1, pJPEG->JPCI[0].quant_tbl_no, (pJPEG->ucMaxACCol | (pJPEG->ucMaxACRow << 8))); + } + if (pJPEG->ucSubSample == 0x22) { + iErr |= JPEGDecodeMCU(pJPEG, iLum2, &iDCPred0); + if (pJPEG->ucMaxACCol == 0 || bThumbnail) { + // no AC components, save some time + c = ucRangeTable[((iDCPred0 * iQuant1) >> 5) & 0x3ff]; + l = c | ((uint32_t) c << 8) | ((uint32_t) c << 16) | ((uint32_t) c << 24); + // dct stores byte values + pl = (uint32_t *) &pJPEG->sMCUs[iLum2]; + for (i = 0; i < iMaxFill; i++) { + // 8x8 bytes = 16 longs + pl[i] = l; + } + } else { + // first quantization table + JPEGIDCT(pJPEG, iLum2, pJPEG->JPCI[0].quant_tbl_no, (pJPEG->ucMaxACCol | (pJPEG->ucMaxACRow << 8))); + } + iErr |= JPEGDecodeMCU(pJPEG, iLum3, &iDCPred0); + if (pJPEG->ucMaxACCol == 0 || bThumbnail) { + // no AC components, save some time + c = ucRangeTable[((iDCPred0 * iQuant1) >> 5) & 0x3ff]; + l = c | ((uint32_t) c << 8) | ((uint32_t) c << 16) | ((uint32_t) c << 24); + // dct stores byte values + pl = (uint32_t *) &pJPEG->sMCUs[iLum3]; + for (i = 0; i < iMaxFill; i++) { + // 8x8 bytes = 16 longs + pl[i] = l; + } + } else { + // first quantization table + JPEGIDCT(pJPEG, iLum3, pJPEG->JPCI[0].quant_tbl_no, (pJPEG->ucMaxACCol | (pJPEG->ucMaxACRow << 8))); + } + } // if 2:2 subsampling + } // if subsampling used + if (pJPEG->ucSubSample && pJPEG->ucNumComponents == 3) { + // if color (not CMYK) + // first chroma + pJPEG->ucACTable = cACTable1; + pJPEG->ucDCTable = cDCTable1; + iErr |= JPEGDecodeMCU(pJPEG, iCr, &iDCPred1); + if (pJPEG->ucMaxACCol == 0 || bThumbnail) { + // no AC components, save some time + c = ucRangeTable[((iDCPred1 * iQuant2) >> 5) & 0x3ff]; + l = c | ((uint32_t) c << 8) | ((uint32_t) c << 16) | ((uint32_t) c << 24); + // dct stores byte values + pl = (uint32_t *) &pJPEG->sMCUs[iCr]; + for (i = 0; i < iMaxFill; i++) { + // 8x8 bytes = 16 longs + pl[i] = l; + } + } else { + // second quantization table + JPEGIDCT(pJPEG, iCr, pJPEG->JPCI[1].quant_tbl_no, (pJPEG->ucMaxACCol | (pJPEG->ucMaxACRow << 8))); + } + // second chroma + pJPEG->ucACTable = cACTable2; + pJPEG->ucDCTable = cDCTable2; + iErr |= JPEGDecodeMCU(pJPEG, iCb, &iDCPred2); + if (pJPEG->ucMaxACCol == 0 || bThumbnail) { + // no AC components, save some time + c = ucRangeTable[((iDCPred2 * iQuant3) >> 5) & 0x3ff]; + l = c | ((uint32_t) c << 8) | ((uint32_t) c << 16) | ((uint32_t) c << 24); + // dct stores byte values + pl = (uint32_t *) &pJPEG->sMCUs[iCb]; + for (i = 0; i < iMaxFill; i++) { + // 8x8 bytes = 16 longs + pl[i] = l; + } + } else { + JPEGIDCT(pJPEG, iCb, pJPEG->JPCI[2].quant_tbl_no, (pJPEG->ucMaxACCol | (pJPEG->ucMaxACRow << 8))); + } + } // if color components present + if (pJPEG->ucPixelType == EIGHT_BIT_GRAYSCALE) { + JPEGPutMCU8BitGray(pJPEG, x * mcuCX, y * mcuCY); + } else if (pJPEG->ucPixelType == ONE_BIT_GRAYSCALE) { + JPEGPutMCU1BitGray(pJPEG, x * mcuCX, y * mcuCY); + } else { + switch (pJPEG->ucSubSample) { + case 0x00: // grayscale + JPEGPutMCUGray(pJPEG, x * mcuCX, y * mcuCY); + break; // not used + case 0x11: + JPEGPutMCU11(pJPEG, x * mcuCX, y * mcuCY); + break; + case 0x12: + JPEGPutMCU12(pJPEG, x * mcuCX, y * mcuCY); + break; + case 0x21: + JPEGPutMCU21(pJPEG, x * mcuCX, y * mcuCY); + break; + case 0x22: + JPEGPutMCU22(pJPEG, x * mcuCX, y * mcuCY); + break; + } // switch on color option + } + if (pJPEG->iResInterval) { + if (--pJPEG->iResCount == 0) { + pJPEG->iResCount = pJPEG->iResInterval; + iDCPred0 = iDCPred1 = iDCPred2 = 0; // reset DC predictors + if (pJPEG->bb.ulBitOff & 7) { + // need to start at the next even byte + // new restart interval starts on byte boundary + pJPEG->bb.ulBitOff += (8 - (pJPEG->bb.ulBitOff & 7)); + } + } // if restart interval needs to reset + } // if there is a restart interval + // See if we need to feed it more data + if (pJPEG->iVLCOff >= FILE_HIGHWATER) { + JPEGGetMoreData(pJPEG); // need more 'filtered' VLC data + } + } // for x + } // for y + if (iErr != 0) { + pJPEG->iError = JPEG_DECODE_ERROR; + } + return (iErr == 0); +} + +void jpeg_decompress(image_t *dst, image_t *src) +{ + JPEGIMAGE jpg; + + #if (TIME_JPEG == 1) + mp_uint_t start = mp_hal_ticks_ms(); + #endif + + // Supports decoding baseline JPEGs only. + if (jpeg_is_progressive(src)) { + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Progressive JPEG is not supported.")); + ERR_PRINT("Progressive JPEG is not supported."); + } + + if (JPEG_openRAM(&jpg, src->data, src->size, dst->data) == 0) { + // failed to parse the header + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("JPEG decoder failed.")); + ERR_PRINT("JPEG decoder failed."); + } + + switch (dst->pixfmt) { + case PIXFORMAT_BINARY: + // Force 1-bit (binary) output in the draw function. + jpg.ucPixelType = ONE_BIT_GRAYSCALE; + break; + case PIXFORMAT_GRAYSCALE: + // Force 8-bit grayscale output. + jpg.ucPixelType = EIGHT_BIT_GRAYSCALE; + break; + case PIXFORMAT_RGB565: + // Force output to be RGB565 + jpg.ucPixelType = RGB565_LITTLE_ENDIAN; + break; + case PIXFORMAT_RGB888: + // Force output to be RGB565 + jpg.ucPixelType = RGB888_LITTLE_ENDIAN; + break; + default: + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Unsupported format.")); + ERR_PRINT("Unsupported format."); + } + + // Set up dest image params + jpg.pUser = (void *) dst; + + // Fill buffer with 0's so we only need to write "set" bits + memset(dst->data, 0, image_size(dst)); + + // Start decoding. + if (JPEG_decode(&jpg, 0, 0, 0) == 0) { + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("JPEG decoder failed.")); + ERR_PRINT("JPEG decoder failed."); + } + + #if (TIME_JPEG == 1) + printf("time: %u ms\n", mp_hal_ticks_ms() - start); + #endif +} +#endif diff --git a/github_source/minicv2/src/kmeans.c b/github_source/minicv2/src/kmeans.c new file mode 100644 index 0000000..76dc82e --- /dev/null +++ b/github_source/minicv2/src/kmeans.c @@ -0,0 +1,141 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Kmeans clustering. + */ +#include +#include +// #include +#include +#include "imlib.h" +#include "array.h" +#include "xalloc.h" + +extern uint32_t rng_randint(uint32_t min, uint32_t max); + +static cluster_t *cluster_alloc(int cx, int cy) +{ + cluster_t *c=NULL; + c = xalloc(sizeof(*c)); + if (c != NULL) { + c->x = cx; + c->y = cy; + c->w = 0; + c->h = 0; + array_alloc(&c->points, NULL); + } + return c; +} + +static void cluster_free(void *c) +{ + cluster_t *cl = c; + array_free(cl->points); + xfree(cl); +} + +static void cluster_reset(array_t *clusters, array_t *points) +{ + // Reset all clusters + for (int j=0; jpoints)) { + array_push_back(points, array_pop_back(cl->points)); + } + array_free(cl->points); + array_alloc(&cl->points, NULL); + } +} + +static int cluster_update(array_t *clusters) +{ + // Update clusters + for (int j=0; jx, cy = cl->y; + int cl_size = array_length(cl->points); + + // Sum all points in this cluster + for (int i=0; ipoints, i); + cl->x += p->x; + cl->y += p->y; + // Find out the max x and y while we're at it + if (p->x > cl->w) { + cl->w = p->x; + } + if (p->y > cl->h) { + cl->h = p->y; + } + } + + // Update centroid + cl->x = cl->x/cl_size; + cl->y = cl->y/cl_size; + // Update cluster size + cl->w = (cl->w - cl->x) * 2; + cl->h = (cl->h - cl->y) * 2; + if (cl->x == cx && cl->y == cy) { + // Cluster centroid did not change + return 0; + } + } + + return 1; +} + +static void cluster_points(array_t *clusters, array_t *points, cluster_dist_t dist_func) +{ + // Add objects to cluster + while (array_length(points)) { + float distance = FLT_MAX; + cluster_t *cl_nearest = NULL; + kp_t *p = array_pop_back(points); + + for (int j=0; jx, cl->y, p); + if (d < distance) { + distance = d; + cl_nearest = cl; + } + } + // Add pointer to point to cluster. + // Note: Objects in the cluster are not free'd + array_push_back(cl_nearest->points, p); + } +} + +array_t *cluster_kmeans(array_t *points, int k, cluster_dist_t dist_func) +{ + // Alloc clusters array + array_t *clusters=NULL; + array_alloc(&clusters, cluster_free); + + // Select K clusters randomly + for (int i=0; ix, p->y)); + } + + int cl_changed = 1; + do { + // Reset clusters + cluster_reset(clusters, points); + + // Add points to clusters + cluster_points(clusters, points, dist_func); + + // Update centroids + cl_changed = cluster_update(clusters); + + } while (cl_changed); + + return clusters; +} diff --git a/github_source/minicv2/src/lab_tab.c b/github_source/minicv2/src/lab_tab.c new file mode 100644 index 0000000..e57ed5e --- /dev/null +++ b/github_source/minicv2/src/lab_tab.c @@ -0,0 +1,8197 @@ +#include +#ifdef IMLIB_ENABLE_LAB_LUT +const int8_t lab_table[98304] = { + 0, 0, -1, 0, 3, -9, 1, 8, -21, 2, 16, -31, + 4, 27, -41, 6, 35, -48, 8, 41, -55, 11, 45, -61, + 14, 50, -67, 16, 54, -73, 19, 58, -79, 22, 62, -84, + 24, 66, -90, 27, 70, -95, 29, 74, -101, 31, 78, -106, + 1, -1, 0, 1, 2, -8, 2, 7, -19, 3, 15, -30, + 4, 25, -39, 6, 32, -47, 9, 38, -54, 12, 43, -60, + 14, 48, -66, 17, 52, -72, 19, 57, -78, 22, 61, -84, + 24, 65, -89, 27, 70, -95, 29, 74, -100, 31, 78, -106, + 2, -2, 0, 2, 0, -7, 3, 5, -18, 4, 13, -29, + 5, 23, -38, 7, 30, -46, 10, 36, -53, 12, 41, -59, + 15, 47, -66, 17, 51, -71, 20, 56, -78, 22, 60, -83, + 25, 64, -89, 27, 69, -95, 29, 73, -100, 32, 77, -106, + 2, -4, 1, 3, -1, -6, 3, 3, -17, 4, 11, -27, + 6, 21, -37, 8, 28, -44, 10, 34, -52, 13, 40, -58, + 15, 45, -65, 18, 50, -71, 20, 55, -77, 22, 59, -83, + 25, 64, -89, 27, 68, -94, 30, 72, -100, 32, 76, -105, + 3, -6, 3, 4, -3, -4, 4, 1, -15, 5, 9, -26, + 7, 19, -35, 9, 25, -43, 11, 32, -50, 13, 38, -57, + 16, 43, -64, 18, 48, -70, 21, 53, -76, 23, 58, -82, + 25, 63, -88, 27, 67, -94, 30, 71, -99, 32, 76, -105, + 5, -8, 5, 5, -6, -3, 6, 0, -14, 7, 7, -24, + 8, 15, -33, 10, 23, -41, 12, 29, -49, 14, 35, -56, + 16, 41, -63, 19, 47, -69, 21, 52, -75, 23, 57, -81, + 26, 61, -87, 28, 66, -93, 30, 70, -99, 32, 75, -104, + 6, -11, 7, 6, -9, 0, 7, -3, -11, 8, 4, -22, + 9, 12, -31, 11, 20, -40, 13, 27, -48, 15, 33, -55, + 17, 39, -62, 19, 45, -68, 22, 50, -75, 24, 55, -81, + 26, 60, -87, 28, 65, -92, 31, 69, -98, 33, 74, -104, + 8, -14, 9, 8, -12, 1, 8, -6, -9, 9, 1, -19, + 11, 9, -29, 12, 17, -38, 14, 24, -46, 16, 30, -53, + 18, 37, -60, 20, 43, -67, 22, 48, -74, 24, 53, -80, + 27, 58, -86, 29, 63, -92, 31, 68, -97, 33, 73, -103, + 9, -18, 12, 10, -15, 4, 10, -9, -6, 11, -2, -17, + 12, 6, -27, 13, 14, -36, 15, 21, -44, 17, 28, -52, + 19, 34, -59, 21, 40, -66, 23, 46, -72, 25, 52, -79, + 27, 57, -85, 29, 62, -91, 31, 66, -97, 34, 71, -102, + 11, -21, 14, 11, -18, 6, 12, -12, -4, 13, -5, -15, + 14, 3, -24, 15, 11, -33, 16, 18, -42, 18, 25, -50, + 20, 32, -57, 22, 38, -64, 24, 44, -71, 26, 50, -77, + 28, 55, -84, 30, 60, -90, 32, 65, -96, 34, 70, -102, + 13, -23, 16, 13, -20, 9, 13, -14, -2, 14, -8, -12, + 15, 0, -22, 16, 7, -31, 18, 15, -40, 19, 22, -48, + 21, 29, -56, 22, 35, -63, 24, 42, -70, 26, 47, -76, + 28, 53, -83, 30, 58, -89, 32, 63, -95, 34, 68, -101, + 15, -25, 19, 15, -22, 11, 15, -17, 1, 16, -11, -9, + 17, -3, -19, 18, 4, -29, 19, 12, -38, 20, 19, -46, + 22, 26, -54, 24, 32, -61, 26, 39, -68, 27, 44, -75, + 29, 50, -81, 31, 56, -87, 33, 61, -94, 35, 66, -100, + 16, -26, 21, 17, -24, 13, 17, -19, 3, 18, -13, -7, + 18, -6, -17, 19, 1, -26, 20, 9, -36, 22, 16, -44, + 23, 23, -52, 25, 29, -59, 26, 36, -67, 28, 42, -73, + 30, 48, -80, 32, 54, -86, 34, 59, -93, 36, 64, -99, + 18, -27, 23, 18, -25, 15, 19, -21, 5, 19, -15, -5, + 20, -9, -15, 21, -2, -24, 22, 6, -34, 23, 13, -42, + 24, 20, -50, 26, 27, -58, 27, 33, -65, 29, 40, -72, + 31, 46, -79, 33, 51, -85, 35, 57, -91, 36, 62, -97, + 20, -29, 25, 20, -27, 17, 20, -23, 7, 21, -17, -2, + 21, -11, -12, 22, -4, -22, 23, 3, -31, 24, 10, -40, + 26, 17, -48, 27, 24, -56, 28, 31, -63, 30, 37, -70, + 32, 43, -77, 34, 49, -84, 35, 55, -90, 37, 60, -96, + 21, -30, 27, 21, -28, 19, 22, -25, 9, 22, -19, 0, + 23, -13, -10, 23, -7, -20, 24, 0, -29, 25, 7, -38, + 27, 14, -46, 28, 21, -54, 30, 28, -62, 31, 34, -69, + 33, 41, -76, 34, 47, -82, 36, 52, -89, 38, 58, -95, + 23, -32, 29, 23, -30, 21, 23, -27, 11, 24, -22, 2, + 24, -16, -8, 25, -9, -17, 26, -2, -27, 27, 4, -36, + 28, 12, -45, 29, 18, -52, 31, 25, -60, 32, 32, -67, + 34, 38, -74, 35, 44, -81, 37, 50, -88, 39, 56, -94, + 25, -33, 30, 25, -31, 23, 25, -28, 14, 25, -24, 4, + 26, -18, -6, 26, -12, -15, 27, -5, -25, 28, 1, -34, + 29, 9, -43, 30, 16, -50, 32, 23, -58, 33, 29, -65, + 35, 36, -73, 36, 42, -79, 38, 48, -86, 40, 54, -92, + 26, -34, 32, 26, -33, 25, 26, -30, 16, 27, -26, 6, + 27, -20, -4, 28, -14, -13, 29, -7, -23, 29, -1, -32, + 30, 6, -41, 32, 13, -48, 33, 20, -56, 34, 26, -64, + 36, 33, -71, 37, 39, -78, 39, 45, -85, 40, 51, -91, + 28, -36, 33, 28, -34, 26, 28, -31, 18, 28, -27, 8, + 29, -22, -1, 29, -16, -11, 30, -10, -21, 31, -3, -29, + 32, 3, -38, 33, 10, -47, 34, 17, -55, 35, 24, -62, + 37, 31, -69, 38, 37, -76, 40, 43, -83, 41, 49, -90, + 29, -37, 34, 29, -35, 28, 29, -33, 19, 30, -29, 10, + 30, -24, 0, 31, -19, -9, 31, -12, -19, 32, -6, -27, + 33, 1, -36, 34, 7, -45, 35, 15, -53, 36, 21, -60, + 38, 28, -68, 39, 34, -75, 41, 40, -82, 42, 46, -88, + 31, -38, 36, 31, -37, 30, 31, -34, 21, 31, -31, 12, + 32, -26, 2, 32, -21, -7, 33, -14, -16, 33, -8, -25, + 34, -1, -34, 35, 5, -43, 36, 12, -51, 38, 18, -58, + 39, 25, -66, 40, 32, -73, 42, 38, -80, 43, 44, -87, + 32, -39, 37, 32, -38, 31, 32, -36, 23, 33, -33, 14, + 33, -28, 4, 33, -23, -5, 34, -17, -14, 35, -11, -23, + 36, -4, -32, 37, 2, -41, 38, 9, -49, 39, 16, -57, + 40, 23, -64, 41, 29, -71, 43, 35, -79, 44, 41, -85, + 34, -41, 38, 34, -39, 33, 34, -37, 25, 34, -34, 16, + 34, -30, 6, 35, -25, -2, 35, -19, -12, 36, -13, -21, + 37, -6, -30, 38, 0, -39, 39, 7, -47, 40, 13, -55, + 41, 20, -62, 42, 26, -70, 44, 33, -77, 45, 39, -84, + 35, -42, 40, 35, -41, 34, 35, -39, 27, 36, -36, 18, + 36, -32, 8, 36, -27, 0, 37, -21, -10, 37, -15, -19, + 38, -9, -28, 39, -2, -37, 40, 4, -45, 41, 10, -53, + 42, 17, -61, 43, 24, -68, 45, 30, -75, 46, 36, -82, + 37, -43, 41, 37, -42, 36, 37, -40, 29, 37, -37, 19, + 37, -34, 10, 38, -29, 1, 38, -23, -8, 39, -17, -17, + 40, -11, -26, 40, -5, -35, 41, 1, -43, 42, 8, -51, + 43, 15, -59, 44, 21, -66, 46, 28, -74, 47, 34, -80, + 38, -44, 42, 38, -43, 37, 38, -41, 30, 38, -39, 21, + 39, -35, 12, 39, -31, 3, 40, -25, -6, 40, -20, -15, + 41, -13, -24, 42, -7, -33, 42, 0, -41, 43, 5, -49, + 44, 12, -57, 46, 19, -64, 47, 25, -72, 48, 31, -79, + 39, -46, 43, 40, -45, 38, 40, -43, 32, 40, -40, 23, + 40, -37, 14, 40, -33, 5, 41, -27, -4, 41, -22, -13, + 42, -16, -22, 43, -9, -31, 44, -3, -39, 45, 3, -47, + 46, 10, -55, 47, 16, -63, 48, 23, -70, 49, 29, -77, + 41, -47, 45, 41, -46, 40, 41, -44, 33, 41, -42, 25, + 42, -39, 16, 42, -35, 7, 42, -29, -2, 43, -24, -11, + 43, -18, -20, 44, -12, -29, 45, -5, -37, 46, 0, -45, + 47, 7, -53, 48, 13, -61, 49, 20, -68, 50, 26, -75, + 42, -48, 46, 42, -47, 41, 43, -45, 35, 43, -43, 27, + 43, -40, 18, 43, -36, 9, 44, -31, 0, 44, -26, -9, + 45, -20, -18, 45, -14, -27, 46, -8, -35, 47, -1, -43, + 48, 5, -51, 49, 11, -59, 50, 18, -67, 51, 24, -74, + 44, -49, 47, 44, -48, 42, 44, -47, 36, 44, -45, 28, + 44, -42, 20, 45, -38, 11, 45, -33, 2, 46, -28, -7, + 46, -22, -16, 47, -16, -25, 47, -10, -33, 48, -4, -41, + 49, 2, -49, 50, 8, -57, 51, 15, -65, 52, 21, -72, + 45, -51, 48, 45, -50, 44, 45, -48, 38, 45, -46, 30, + 46, -43, 22, 46, -40, 13, 46, -35, 4, 47, -30, -5, + 47, -24, -14, 48, -19, -23, 49, -12, -31, 50, -6, -39, + 50, 0, -47, 51, 6, -55, 52, 12, -63, 53, 19, -70, + 47, -52, 49, 47, -51, 46, 47, -50, 40, 47, -48, 32, + 47, -45, 24, 48, -42, 15, 48, -37, 6, 49, -32, -2, + 49, -27, -12, 50, -21, -20, 50, -15, -29, 51, -9, -37, + 52, -3, -45, 53, 3, -53, 54, 9, -61, 55, 16, -68, + 48, -53, 51, 48, -52, 47, 48, -51, 41, 49, -49, 34, + 49, -46, 26, 49, -43, 17, 49, -39, 8, 50, -34, -1, + 50, -29, -10, 51, -23, -18, 52, -17, -27, 52, -11, -35, + 53, -5, -43, 54, 0, -51, 55, 7, -59, 56, 13, -66, + 50, -54, 52, 50, -53, 48, 50, -52, 43, 50, -50, 36, + 50, -48, 27, 50, -45, 19, 51, -41, 10, 51, -36, 1, + 52, -31, -8, 52, -25, -16, 53, -19, -25, 54, -14, -33, + 54, -8, -41, 55, -1, -49, 56, 4, -57, 57, 11, -64, + 51, -55, 53, 51, -55, 50, 51, -53, 44, 51, -51, 37, + 52, -49, 29, 52, -46, 21, 52, -42, 12, 52, -38, 3, + 53, -33, -6, 53, -27, -14, 54, -22, -23, 55, -16, -31, + 55, -10, -39, 56, -3, -47, 57, 2, -55, 58, 8, -63, + 52, -57, 54, 53, -56, 51, 53, -55, 45, 53, -53, 39, + 53, -50, 31, 53, -48, 23, 53, -44, 14, 54, -40, 5, + 54, -35, -4, 55, -29, -12, 55, -24, -21, 56, -18, -29, + 57, -12, -37, 57, -6, -46, 58, 0, -53, 59, 6, -61, + 54, -58, 55, 54, -57, 52, 54, -56, 47, 54, -54, 40, + 54, -52, 33, 54, -49, 24, 55, -45, 15, 55, -41, 7, + 56, -36, -2, 56, -31, -10, 57, -26, -19, 57, -20, -28, + 58, -14, -36, 59, -8, -44, 59, -2, -52, 60, 3, -59, + 55, -59, 56, 55, -58, 53, 55, -57, 48, 55, -55, 42, + 56, -53, 34, 56, -50, 26, 56, -47, 17, 56, -43, 9, + 57, -38, 0, 57, -33, -9, 58, -28, -17, 58, -22, -26, + 59, -16, -34, 60, -10, -42, 61, -4, -50, 62, 1, -57, + 57, -60, 57, 57, -59, 55, 57, -58, 49, 57, -56, 43, + 57, -54, 36, 57, -52, 28, 57, -48, 19, 58, -45, 11, + 58, -40, 2, 59, -35, -7, 59, -30, -16, 60, -24, -24, + 60, -19, -32, 61, -13, -40, 62, -7, -48, 63, 0, -56, + 58, -61, 58, 58, -61, 56, 58, -59, 51, 58, -58, 45, + 58, -56, 37, 58, -53, 30, 59, -50, 21, 59, -46, 12, + 59, -42, 3, 60, -37, -5, 60, -32, -14, 61, -26, -22, + 62, -21, -30, 62, -15, -38, 63, -9, -46, 64, -3, -54, + 59, -62, 59, 59, -62, 57, 59, -61, 52, 59, -59, 46, + 60, -57, 39, 60, -54, 31, 60, -51, 23, 60, -48, 14, + 61, -44, 5, 61, -39, -3, 62, -34, -12, 62, -28, -20, + 63, -23, -28, 63, -17, -36, 64, -11, -44, 65, -5, -52, + 61, -63, 60, 61, -63, 58, 61, -62, 54, 61, -60, 48, + 61, -58, 40, 61, -56, 33, 61, -53, 24, 62, -49, 16, + 62, -45, 7, 62, -41, -1, 63, -36, -10, 63, -30, -18, + 64, -25, -26, 65, -19, -34, 65, -13, -42, 66, -7, -50, + 62, -65, 61, 62, -64, 59, 62, -63, 55, 62, -61, 49, + 62, -59, 42, 62, -57, 35, 63, -54, 26, 63, -51, 18, + 63, -47, 9, 64, -42, 1, 64, -37, -8, 65, -32, -16, + 65, -27, -24, 66, -21, -33, 67, -15, -40, 67, -9, -48, + 63, -66, 63, 63, -65, 60, 63, -64, 56, 63, -63, 50, + 64, -61, 43, 64, -58, 36, 64, -56, 28, 64, -52, 20, + 65, -48, 11, 65, -44, 2, 65, -39, -6, 66, -34, -14, + 66, -29, -22, 67, -23, -31, 68, -18, -39, 68, -12, -46, + 65, -67, 64, 65, -66, 61, 65, -65, 57, 65, -64, 52, + 65, -62, 45, 65, -60, 38, 65, -57, 29, 66, -54, 21, + 66, -50, 13, 66, -46, 4, 67, -41, -4, 67, -36, -12, + 68, -31, -21, 68, -25, -29, 69, -20, -37, 70, -14, -45, + 66, -68, 65, 66, -67, 63, 66, -66, 59, 66, -65, 53, + 66, -63, 46, 66, -61, 39, 67, -58, 31, 67, -55, 23, + 67, -52, 14, 67, -48, 6, 68, -43, -2, 68, -38, -11, + 69, -33, -19, 69, -27, -27, 70, -22, -35, 71, -16, -43, + 67, -69, 66, 67, -68, 64, 67, -68, 60, 67, -66, 54, + 67, -64, 48, 68, -62, 41, 68, -60, 33, 68, -57, 25, + 68, -53, 16, 69, -49, 8, 69, -45, -1, 70, -40, -9, + 70, -35, -17, 71, -29, -25, 71, -24, -33, 72, -18, -41, + 68, -70, 67, 68, -69, 65, 69, -69, 61, 69, -67, 56, + 69, -66, 49, 69, -64, 42, 69, -61, 34, 69, -58, 26, + 70, -55, 18, 70, -51, 10, 70, -46, 1, 71, -42, -7, + 71, -36, -15, 72, -31, -23, 72, -26, -31, 73, -20, -39, + 70, -71, 68, 70, -71, 66, 70, -70, 62, 70, -68, 57, + 70, -67, 51, 70, -65, 44, 70, -62, 36, 71, -59, 28, + 71, -56, 20, 71, -52, 11, 72, -48, 3, 72, -43, -5, + 73, -38, -13, 73, -33, -22, 74, -28, -29, 74, -22, -37, + 71, -72, 69, 71, -72, 67, 71, -71, 64, 71, -70, 58, + 71, -68, 52, 71, -66, 45, 72, -64, 38, 72, -61, 30, + 72, -57, 21, 72, -54, 13, 73, -50, 5, 73, -45, -3, + 74, -40, -11, 74, -35, -20, 75, -30, -28, 75, -24, -36, + 72, -73, 70, 72, -73, 68, 72, -72, 65, 72, -71, 59, + 73, -69, 53, 73, -67, 47, 73, -65, 39, 73, -62, 31, + 73, -59, 23, 74, -55, 15, 74, -51, 6, 74, -47, -2, + 75, -42, -10, 75, -37, -18, 76, -32, -26, 77, -26, -34, + 74, -74, 71, 74, -74, 69, 74, -73, 66, 74, -72, 61, + 74, -70, 55, 74, -68, 48, 74, -66, 41, 74, -63, 33, + 75, -60, 25, 75, -57, 17, 75, -53, 8, 76, -49, 0, + 76, -44, -8, 77, -39, -16, 77, -34, -24, 78, -28, -32, + 75, -76, 72, 75, -75, 70, 75, -74, 68, 75, -73, 62, + 75, -72, 56, 76, -70, 50, 76, -68, 43, 76, -65, 35, + 76, -62, 27, 77, -59, 19, 77, -55, 10, 77, -51, 2, + 78, -46, -6, 78, -41, -14, 79, -36, -22, 79, -31, -30, + 76, -77, 73, 76, -76, 72, 77, -76, 69, 77, -74, 64, + 77, -73, 58, 77, -71, 52, 77, -69, 44, 77, -66, 37, + 77, -63, 28, 78, -60, 21, 78, -56, 12, 78, -52, 4, + 79, -48, -4, 79, -43, -12, 80, -38, -20, 80, -33, -28, + 78, -78, 74, 78, -77, 73, 78, -77, 70, 78, -76, 65, + 78, -74, 59, 78, -72, 53, 78, -70, 46, 78, -68, 38, + 79, -65, 30, 79, -62, 22, 79, -58, 14, 80, -54, 6, + 80, -49, -2, 81, -45, -10, 81, -40, -18, 82, -35, -26, + 79, -79, 75, 79, -78, 74, 79, -78, 71, 79, -77, 66, + 79, -75, 60, 79, -74, 54, 80, -71, 47, 80, -69, 40, + 80, -66, 32, 80, -63, 24, 81, -59, 16, 81, -55, 8, + 81, -51, 0, 82, -46, -9, 82, -41, -16, 83, -37, -24, + 80, -80, 76, 80, -79, 75, 80, -79, 72, 80, -78, 67, + 80, -76, 62, 81, -75, 56, 81, -73, 49, 81, -70, 41, + 81, -67, 33, 81, -64, 26, 82, -61, 17, 82, -57, 9, + 82, -53, 1, 83, -48, -7, 83, -43, -15, 84, -38, -23, + 82, -81, 78, 82, -81, 76, 82, -80, 73, 82, -79, 69, + 82, -78, 63, 82, -76, 57, 82, -74, 50, 82, -72, 43, + 82, -69, 35, 83, -66, 27, 83, -62, 19, 83, -59, 11, + 84, -54, 3, 84, -50, -5, 85, -45, -13, 85, -40, -21, + 83, -82, 79, 83, -82, 77, 83, -81, 74, 83, -80, 70, + 83, -79, 64, 83, -77, 58, 83, -75, 51, 83, -73, 44, + 84, -70, 37, 84, -67, 29, 84, -64, 21, 85, -60, 13, + 85, -56, 5, 85, -52, -3, 86, -47, -11, 86, -42, -19, + 84, -83, 80, 84, -83, 78, 84, -82, 75, 84, -81, 71, + 84, -80, 66, 84, -78, 60, 84, -76, 53, 85, -74, 46, + 85, -71, 38, 85, -68, 31, 85, -65, 22, 86, -62, 14, + 86, -57, 6, 86, -53, -2, 87, -49, -9, 87, -44, -17, + 85, -84, 81, 85, -84, 79, 85, -83, 76, 85, -82, 72, + 85, -81, 67, 86, -79, 61, 86, -77, 54, 86, -75, 47, + 86, -73, 40, 86, -70, 32, 87, -66, 24, 87, -63, 16, + 87, -59, 8, 88, -55, 0, 88, -50, -8, 89, -46, -16, + 87, -85, 82, 87, -85, 80, 87, -84, 77, 87, -83, 74, + 87, -82, 68, 87, -80, 62, 87, -78, 56, 87, -76, 49, + 87, -74, 41, 88, -71, 34, 88, -68, 26, 88, -64, 18, + 88, -61, 10, 89, -57, 2, 89, -52, -6, 90, -47, -14, + 88, -86, 83, 88, -86, 81, 88, -85, 79, 88, -84, 75, + 88, -83, 70, 88, -82, 64, 88, -80, 57, 88, -78, 50, + 89, -75, 43, 89, -72, 35, 89, -69, 27, 89, -66, 20, + 90, -62, 11, 90, -58, 4, 90, -54, -4, 91, -49, -12, + 0, 2, 0, 1, 5, -8, 1, 10, -20, 2, 18, -30, + 4, 28, -40, 6, 35, -48, 9, 41, -54, 11, 45, -60, + 14, 50, -67, 17, 54, -72, 19, 58, -78, 22, 62, -84, + 24, 66, -90, 27, 70, -95, 29, 74, -101, 31, 78, -106, + 1, 1, 0, 2, 4, -7, 2, 9, -19, 3, 17, -29, + 5, 26, -39, 7, 33, -46, 10, 38, -53, 12, 43, -59, + 15, 48, -66, 17, 53, -72, 20, 57, -78, 22, 61, -84, + 25, 65, -89, 27, 70, -95, 29, 74, -100, 32, 78, -106, + 2, 0, 1, 2, 2, -6, 3, 7, -17, 4, 15, -28, + 6, 24, -37, 8, 31, -45, 10, 36, -52, 13, 42, -59, + 15, 47, -65, 17, 51, -71, 20, 56, -77, 22, 60, -83, + 25, 65, -89, 27, 69, -94, 30, 73, -100, 32, 77, -105, + 3, -2, 2, 3, 1, -5, 4, 5, -16, 5, 13, -27, + 6, 22, -36, 8, 28, -44, 11, 34, -51, 13, 40, -58, + 16, 45, -64, 18, 50, -70, 20, 55, -77, 23, 59, -82, + 25, 64, -88, 27, 68, -94, 30, 72, -99, 32, 76, -105, + 4, -4, 4, 4, -1, -3, 5, 3, -15, 6, 11, -25, + 7, 19, -34, 9, 26, -42, 12, 32, -50, 14, 38, -57, + 16, 44, -63, 18, 49, -70, 21, 54, -76, 23, 58, -82, + 25, 63, -88, 28, 67, -94, 30, 71, -99, 32, 76, -105, + 5, -6, 5, 5, -4, -2, 6, 1, -13, 7, 9, -23, + 9, 16, -33, 10, 23, -41, 12, 30, -49, 14, 36, -55, + 17, 42, -62, 19, 47, -69, 21, 52, -75, 23, 57, -81, + 26, 61, -87, 28, 66, -93, 30, 70, -98, 33, 75, -104, + 6, -9, 7, 7, -7, 0, 7, -1, -11, 8, 6, -21, + 10, 13, -31, 11, 20, -39, 13, 27, -47, 15, 33, -54, + 17, 39, -61, 19, 45, -68, 22, 50, -74, 24, 55, -80, + 26, 60, -86, 28, 65, -92, 31, 69, -98, 33, 74, -104, + 8, -12, 10, 8, -10, 2, 9, -4, -8, 10, 3, -19, + 11, 10, -28, 13, 17, -37, 14, 24, -45, 16, 31, -53, + 18, 37, -60, 20, 43, -67, 22, 49, -73, 24, 54, -79, + 27, 58, -86, 29, 63, -91, 31, 68, -97, 33, 73, -103, + 10, -15, 12, 10, -13, 5, 11, -7, -6, 11, 0, -16, + 12, 7, -26, 14, 14, -35, 15, 22, -44, 17, 28, -51, + 19, 35, -59, 21, 41, -65, 23, 46, -72, 25, 52, -78, + 27, 57, -85, 29, 62, -91, 32, 67, -96, 34, 71, -102, + 11, -18, 15, 12, -15, 7, 12, -9, -3, 13, -3, -14, + 14, 4, -24, 15, 11, -33, 17, 19, -42, 18, 25, -50, + 20, 32, -57, 22, 38, -64, 24, 44, -71, 26, 50, -77, + 28, 55, -84, 30, 60, -90, 32, 65, -96, 34, 70, -101, + 13, -20, 17, 13, -17, 9, 14, -12, -1, 14, -6, -12, + 15, 1, -22, 16, 8, -31, 18, 16, -40, 19, 23, -48, + 21, 30, -56, 23, 36, -63, 25, 42, -70, 26, 48, -76, + 28, 53, -83, 30, 58, -89, 33, 63, -95, 35, 68, -101, + 15, -22, 20, 15, -20, 12, 16, -15, 1, 16, -9, -9, + 17, -2, -19, 18, 5, -28, 19, 12, -37, 21, 19, -46, + 22, 26, -53, 24, 32, -61, 26, 39, -68, 27, 45, -74, + 29, 50, -81, 31, 56, -87, 33, 61, -93, 35, 66, -99, + 17, -24, 22, 17, -22, 14, 17, -17, 3, 18, -12, -7, + 19, -5, -17, 19, 2, -26, 21, 9, -35, 22, 16, -44, + 23, 23, -52, 25, 30, -59, 27, 36, -66, 28, 42, -73, + 30, 48, -80, 32, 54, -86, 34, 59, -92, 36, 64, -98, + 18, -25, 24, 19, -23, 16, 19, -19, 5, 19, -14, -4, + 20, -7, -14, 21, -1, -24, 22, 6, -33, 23, 13, -42, + 25, 21, -50, 26, 27, -57, 28, 34, -65, 29, 40, -72, + 31, 46, -79, 33, 52, -85, 35, 57, -91, 37, 62, -97, + 20, -27, 25, 20, -25, 18, 20, -22, 8, 21, -16, -2, + 21, -10, -12, 22, -3, -22, 23, 3, -31, 24, 10, -40, + 26, 18, -48, 27, 24, -56, 29, 31, -63, 30, 37, -70, + 32, 43, -77, 34, 49, -83, 35, 55, -90, 37, 60, -96, + 22, -28, 27, 22, -27, 19, 22, -23, 10, 22, -18, 0, + 23, -12, -10, 24, -6, -19, 25, 1, -29, 26, 8, -38, + 27, 15, -46, 28, 22, -54, 30, 28, -62, 31, 35, -69, + 33, 41, -76, 34, 47, -82, 36, 53, -89, 38, 58, -95, + 23, -30, 29, 23, -28, 21, 23, -25, 12, 24, -20, 2, + 24, -15, -8, 25, -9, -17, 26, -1, -27, 27, 5, -36, + 28, 12, -44, 29, 19, -52, 31, 26, -60, 32, 32, -67, + 34, 38, -74, 35, 44, -81, 37, 50, -87, 39, 56, -94, + 25, -31, 30, 25, -30, 23, 25, -27, 14, 25, -22, 4, + 26, -17, -5, 26, -11, -15, 27, -4, -25, 28, 2, -33, + 29, 9, -42, 31, 16, -50, 32, 23, -58, 33, 29, -65, + 35, 36, -73, 36, 42, -79, 38, 48, -86, 40, 54, -92, + 26, -33, 32, 26, -31, 25, 27, -29, 16, 27, -24, 6, + 27, -19, -3, 28, -13, -13, 29, -7, -23, 30, 0, -31, + 31, 7, -40, 32, 13, -48, 33, 20, -56, 34, 27, -64, + 36, 33, -71, 37, 39, -78, 39, 46, -85, 40, 51, -91, + 28, -34, 33, 28, -33, 27, 28, -30, 18, 28, -26, 8, + 29, -21, -1, 29, -16, -11, 30, -9, -20, 31, -3, -29, + 32, 4, -38, 33, 11, -46, 34, 18, -54, 35, 24, -62, + 37, 31, -69, 38, 37, -76, 40, 43, -83, 41, 49, -90, + 29, -36, 34, 29, -34, 28, 30, -32, 20, 30, -28, 10, + 30, -23, 0, 31, -18, -9, 31, -11, -18, 32, -5, -27, + 33, 1, -36, 34, 8, -44, 35, 15, -53, 37, 21, -60, + 38, 28, -68, 39, 34, -75, 41, 41, -82, 42, 47, -88, + 31, -37, 36, 31, -36, 30, 31, -33, 22, 31, -30, 12, + 32, -25, 2, 32, -20, -6, 33, -14, -16, 34, -8, -25, + 34, -1, -34, 35, 5, -42, 36, 12, -51, 38, 19, -58, + 39, 25, -66, 40, 32, -73, 42, 38, -80, 43, 44, -87, + 32, -38, 37, 32, -37, 31, 32, -35, 23, 33, -32, 14, + 33, -27, 4, 34, -22, -4, 34, -16, -14, 35, -10, -23, + 36, -3, -32, 37, 3, -40, 38, 10, -49, 39, 16, -56, + 40, 23, -64, 41, 29, -71, 43, 36, -78, 44, 42, -85, + 34, -40, 38, 34, -38, 33, 34, -36, 25, 34, -34, 16, + 35, -29, 6, 35, -24, -2, 36, -18, -12, 36, -12, -21, + 37, -6, -30, 38, 0, -38, 39, 7, -47, 40, 13, -55, + 41, 20, -62, 42, 27, -70, 44, 33, -77, 45, 39, -84, + 35, -41, 40, 35, -40, 34, 35, -38, 27, 36, -35, 18, + 36, -31, 8, 36, -26, 0, 37, -21, -10, 38, -15, -19, + 38, -8, -28, 39, -2, -36, 40, 4, -45, 41, 11, -53, + 42, 18, -60, 43, 24, -68, 45, 30, -75, 46, 37, -82, + 37, -42, 41, 37, -41, 36, 37, -39, 29, 37, -37, 20, + 37, -33, 10, 38, -28, 1, 38, -23, -8, 39, -17, -17, + 40, -11, -26, 40, -4, -34, 41, 2, -43, 42, 8, -51, + 43, 15, -59, 45, 21, -66, 46, 28, -73, 47, 34, -80, + 38, -44, 42, 38, -42, 37, 38, -41, 30, 39, -38, 21, + 39, -35, 12, 39, -30, 3, 40, -25, -6, 40, -19, -15, + 41, -13, -24, 42, -7, -32, 43, 0, -41, 43, 6, -49, + 44, 12, -57, 46, 19, -64, 47, 25, -72, 48, 31, -79, + 40, -45, 44, 40, -44, 38, 40, -42, 32, 40, -40, 23, + 40, -36, 14, 41, -32, 5, 41, -27, -4, 42, -21, -13, + 42, -15, -22, 43, -9, -30, 44, -2, -39, 45, 3, -47, + 46, 10, -55, 47, 16, -62, 48, 23, -70, 49, 29, -77, + 41, -46, 45, 41, -45, 40, 41, -43, 33, 41, -41, 25, + 42, -38, 16, 42, -34, 7, 42, -29, -2, 43, -23, -11, + 44, -17, -20, 44, -11, -28, 45, -5, -37, 46, 1, -45, + 47, 7, -53, 48, 14, -61, 49, 20, -68, 50, 26, -75, + 42, -47, 46, 42, -46, 41, 43, -45, 35, 43, -43, 27, + 43, -39, 18, 43, -36, 9, 44, -31, 0, 44, -26, -9, + 45, -20, -18, 46, -14, -26, 46, -7, -35, 47, -1, -43, + 48, 5, -51, 49, 11, -59, 50, 18, -67, 51, 24, -74, + 44, -49, 47, 44, -48, 43, 44, -46, 36, 44, -44, 29, + 44, -41, 20, 45, -37, 11, 45, -33, 2, 46, -28, -7, + 46, -22, -16, 47, -16, -24, 48, -10, -33, 48, -3, -41, + 49, 2, -49, 50, 9, -57, 51, 15, -65, 52, 21, -72, + 45, -50, 48, 45, -49, 44, 45, -47, 38, 46, -45, 30, + 46, -42, 22, 46, -39, 13, 46, -34, 4, 47, -30, -5, + 47, -24, -14, 48, -18, -23, 49, -12, -31, 50, -6, -39, + 50, 0, -47, 51, 6, -55, 52, 13, -63, 53, 19, -70, + 47, -51, 50, 47, -50, 46, 47, -49, 40, 47, -47, 32, + 48, -44, 24, 48, -41, 15, 48, -37, 6, 49, -32, -2, + 49, -26, -12, 50, -21, -20, 50, -15, -29, 51, -9, -37, + 52, -3, -45, 53, 3, -53, 54, 10, -61, 55, 16, -68, + 48, -53, 51, 48, -52, 47, 49, -50, 41, 49, -48, 34, + 49, -46, 26, 49, -43, 17, 49, -38, 8, 50, -34, 0, + 50, -28, -10, 51, -23, -18, 52, -17, -27, 52, -11, -35, + 53, -5, -43, 54, 1, -51, 55, 7, -59, 56, 13, -66, + 50, -54, 52, 50, -53, 48, 50, -52, 43, 50, -50, 36, + 50, -47, 28, 50, -44, 19, 51, -40, 10, 51, -36, 1, + 52, -30, -8, 52, -25, -16, 53, -19, -25, 54, -13, -33, + 54, -7, -41, 55, -1, -49, 56, 5, -57, 57, 11, -64, + 51, -55, 53, 51, -54, 50, 51, -53, 44, 51, -51, 37, + 52, -49, 29, 52, -46, 21, 52, -42, 12, 53, -37, 3, + 53, -32, -6, 54, -27, -14, 54, -21, -23, 55, -16, -31, + 56, -10, -39, 56, -3, -47, 57, 2, -55, 58, 8, -63, + 53, -56, 54, 53, -55, 51, 53, -54, 45, 53, -52, 39, + 53, -50, 31, 53, -47, 23, 54, -43, 14, 54, -39, 5, + 54, -34, -4, 55, -29, -12, 55, -23, -21, 56, -18, -29, + 57, -12, -37, 58, -6, -45, 58, 0, -53, 59, 6, -61, + 54, -57, 55, 54, -57, 52, 54, -55, 47, 54, -53, 40, + 54, -51, 33, 55, -49, 24, 55, -45, 15, 55, -41, 7, + 56, -36, -2, 56, -31, -10, 57, -25, -19, 57, -20, -27, + 58, -14, -35, 59, -8, -44, 60, -2, -51, 60, 4, -59, + 55, -58, 56, 55, -58, 53, 55, -57, 48, 55, -55, 42, + 56, -53, 34, 56, -50, 26, 56, -47, 17, 56, -43, 9, + 57, -38, 0, 57, -33, -8, 58, -27, -17, 59, -22, -26, + 59, -16, -34, 60, -10, -42, 61, -4, -50, 62, 1, -57, + 57, -60, 57, 57, -59, 55, 57, -58, 50, 57, -56, 43, + 57, -54, 36, 57, -51, 28, 57, -48, 19, 58, -44, 11, + 58, -40, 2, 59, -35, -7, 59, -29, -15, 60, -24, -24, + 60, -18, -32, 61, -12, -40, 62, -6, -48, 63, 0, -55, + 58, -61, 58, 58, -60, 56, 58, -59, 51, 58, -57, 45, + 58, -55, 37, 59, -53, 30, 59, -50, 21, 59, -46, 12, + 59, -41, 3, 60, -37, -5, 60, -31, -14, 61, -26, -22, + 62, -20, -30, 62, -15, -38, 63, -9, -46, 64, -2, -54, + 59, -62, 59, 59, -61, 57, 59, -60, 52, 59, -58, 46, + 60, -56, 39, 60, -54, 31, 60, -51, 23, 60, -48, 14, + 61, -43, 5, 61, -39, -3, 62, -33, -12, 62, -28, -20, + 63, -22, -28, 63, -17, -36, 64, -11, -44, 65, -5, -52, + 61, -63, 60, 61, -62, 58, 61, -61, 54, 61, -60, 48, + 61, -58, 41, 61, -55, 33, 61, -52, 24, 62, -49, 16, + 62, -45, 7, 62, -40, -1, 63, -35, -10, 63, -30, -18, + 64, -24, -26, 65, -19, -34, 65, -13, -42, 66, -7, -50, + 62, -64, 62, 62, -64, 59, 62, -63, 55, 62, -61, 49, + 62, -59, 42, 62, -57, 35, 63, -54, 26, 63, -51, 18, + 63, -47, 9, 64, -42, 1, 64, -37, -8, 65, -32, -16, + 65, -26, -24, 66, -21, -32, 67, -15, -40, 67, -9, -48, + 63, -65, 63, 63, -65, 60, 63, -64, 56, 63, -62, 50, + 64, -60, 44, 64, -58, 36, 64, -55, 28, 64, -52, 20, + 65, -48, 11, 65, -44, 3, 65, -39, -6, 66, -34, -14, + 66, -28, -22, 67, -23, -31, 68, -17, -39, 68, -11, -46, + 65, -66, 64, 65, -66, 62, 65, -65, 57, 65, -63, 52, + 65, -62, 45, 65, -59, 38, 65, -57, 30, 66, -53, 21, + 66, -50, 13, 66, -46, 4, 67, -41, -4, 67, -36, -12, + 68, -30, -20, 68, -25, -29, 69, -19, -37, 70, -14, -45, + 66, -67, 65, 66, -67, 63, 66, -66, 59, 66, -65, 53, + 66, -63, 46, 66, -61, 39, 67, -58, 31, 67, -55, 23, + 67, -51, 14, 68, -47, 6, 68, -43, -2, 68, -38, -11, + 69, -32, -19, 69, -27, -27, 70, -21, -35, 71, -16, -43, + 67, -69, 66, 67, -68, 64, 67, -67, 60, 67, -66, 54, + 67, -64, 48, 68, -62, 41, 68, -59, 33, 68, -56, 25, + 68, -53, 16, 69, -49, 8, 69, -44, -1, 70, -40, -9, + 70, -34, -17, 71, -29, -25, 71, -23, -33, 72, -18, -41, + 68, -70, 67, 69, -69, 65, 69, -68, 61, 69, -67, 56, + 69, -65, 49, 69, -63, 42, 69, -61, 34, 69, -58, 27, + 70, -54, 18, 70, -51, 10, 70, -46, 1, 71, -41, -7, + 71, -36, -15, 72, -31, -23, 72, -25, -31, 73, -20, -39, + 70, -71, 68, 70, -70, 66, 70, -69, 62, 70, -68, 57, + 70, -66, 51, 70, -64, 44, 70, -62, 36, 71, -59, 28, + 71, -56, 20, 71, -52, 12, 72, -48, 3, 72, -43, -5, + 73, -38, -13, 73, -33, -21, 74, -28, -29, 74, -22, -37, + 71, -72, 69, 71, -71, 67, 71, -71, 64, 71, -69, 58, + 71, -68, 52, 71, -66, 45, 72, -63, 38, 72, -61, 30, + 72, -57, 21, 73, -54, 13, 73, -49, 5, 73, -45, -3, + 74, -40, -11, 74, -35, -20, 75, -29, -28, 75, -24, -36, + 72, -73, 70, 72, -72, 68, 72, -72, 65, 73, -70, 60, + 73, -69, 53, 73, -67, 47, 73, -65, 39, 73, -62, 32, + 73, -59, 23, 74, -55, 15, 74, -51, 6, 75, -47, -2, + 75, -42, -10, 75, -37, -18, 76, -31, -26, 77, -26, -34, + 74, -74, 71, 74, -74, 69, 74, -73, 66, 74, -72, 61, + 74, -70, 55, 74, -68, 48, 74, -66, 41, 74, -63, 33, + 75, -60, 25, 75, -57, 17, 75, -53, 8, 76, -48, 0, + 76, -44, -8, 77, -39, -16, 77, -33, -24, 78, -28, -32, + 75, -75, 72, 75, -75, 71, 75, -74, 68, 75, -73, 62, + 75, -72, 57, 76, -70, 50, 76, -67, 43, 76, -65, 35, + 76, -62, 27, 77, -58, 19, 77, -55, 10, 77, -50, 2, + 78, -46, -6, 78, -41, -14, 79, -36, -22, 79, -31, -30, + 77, -76, 73, 77, -76, 72, 77, -75, 69, 77, -74, 64, + 77, -73, 58, 77, -71, 52, 77, -69, 44, 77, -66, 37, + 78, -63, 29, 78, -60, 21, 78, -56, 12, 78, -52, 4, + 79, -47, -4, 79, -43, -12, 80, -38, -20, 80, -33, -28, + 78, -77, 74, 78, -77, 73, 78, -76, 70, 78, -75, 65, + 78, -74, 59, 78, -72, 53, 78, -70, 46, 78, -67, 38, + 79, -65, 30, 79, -61, 22, 79, -58, 14, 80, -54, 6, + 80, -49, -2, 81, -45, -10, 81, -39, -18, 82, -34, -26, + 79, -79, 76, 79, -78, 74, 79, -77, 71, 79, -76, 66, + 79, -75, 61, 79, -73, 54, 80, -71, 47, 80, -69, 40, + 80, -66, 32, 80, -63, 24, 81, -59, 16, 81, -55, 8, + 81, -51, 0, 82, -46, -8, 82, -41, -16, 83, -36, -24, + 80, -80, 77, 80, -79, 75, 80, -79, 72, 80, -77, 67, + 81, -76, 62, 81, -74, 56, 81, -72, 49, 81, -70, 41, + 81, -67, 33, 81, -64, 26, 82, -61, 17, 82, -57, 9, + 83, -52, 1, 83, -48, -7, 83, -43, -15, 84, -38, -23, + 82, -81, 78, 82, -80, 76, 82, -80, 73, 82, -79, 69, + 82, -77, 63, 82, -76, 57, 82, -74, 50, 82, -71, 43, + 82, -69, 35, 83, -66, 27, 83, -62, 19, 83, -58, 11, + 84, -54, 3, 84, -50, -5, 85, -45, -13, 85, -40, -21, + 83, -82, 79, 83, -81, 77, 83, -81, 74, 83, -80, 70, + 83, -78, 65, 83, -77, 59, 83, -75, 52, 83, -73, 44, + 84, -70, 37, 84, -67, 29, 84, -63, 21, 85, -60, 13, + 85, -56, 5, 85, -51, -3, 86, -47, -11, 86, -42, -19, + 84, -83, 80, 84, -82, 78, 84, -82, 75, 84, -81, 71, + 84, -80, 66, 84, -78, 60, 85, -76, 53, 85, -74, 46, + 85, -71, 38, 85, -68, 31, 85, -65, 22, 86, -61, 15, + 86, -57, 6, 87, -53, -1, 87, -48, -9, 87, -44, -17, + 85, -84, 81, 85, -83, 79, 85, -83, 76, 85, -82, 72, + 85, -81, 67, 86, -79, 61, 86, -77, 54, 86, -75, 47, + 86, -72, 40, 86, -70, 32, 87, -66, 24, 87, -63, 16, + 87, -59, 8, 88, -55, 0, 88, -50, -8, 89, -45, -16, + 87, -85, 82, 87, -84, 80, 87, -84, 78, 87, -83, 74, + 87, -82, 68, 87, -80, 63, 87, -78, 56, 87, -76, 49, + 87, -74, 41, 88, -71, 34, 88, -68, 26, 88, -64, 18, + 88, -60, 10, 89, -56, 2, 89, -52, -6, 90, -47, -14, + 88, -86, 83, 88, -86, 81, 88, -85, 79, 88, -84, 75, + 88, -83, 70, 88, -81, 64, 88, -79, 57, 88, -77, 50, + 89, -75, 43, 89, -72, 35, 89, -69, 27, 89, -66, 20, + 90, -62, 11, 90, -58, 4, 90, -53, -4, 91, -49, -12, + 1, 5, 0, 1, 8, -8, 2, 13, -19, 3, 21, -30, + 5, 29, -39, 7, 36, -47, 9, 41, -53, 12, 45, -60, + 15, 50, -66, 17, 54, -72, 20, 58, -78, 22, 62, -84, + 24, 66, -89, 27, 70, -95, 29, 74, -100, 32, 78, -106, + 2, 3, 1, 2, 6, -6, 3, 11, -18, 4, 19, -28, + 5, 27, -38, 7, 33, -45, 10, 39, -52, 12, 43, -59, + 15, 48, -65, 17, 53, -71, 20, 57, -77, 22, 61, -83, + 25, 65, -89, 27, 70, -95, 29, 74, -100, 32, 78, -106, + 3, 1, 2, 3, 5, -5, 4, 9, -17, 5, 17, -27, + 6, 25, -37, 8, 31, -44, 11, 37, -51, 13, 42, -58, + 15, 47, -65, 18, 51, -71, 20, 56, -77, 23, 60, -83, + 25, 65, -88, 27, 69, -94, 30, 73, -100, 32, 77, -105, + 3, 0, 3, 4, 3, -4, 4, 8, -15, 5, 16, -26, + 7, 23, -35, 9, 29, -43, 11, 35, -50, 13, 40, -57, + 16, 45, -64, 18, 50, -70, 21, 55, -76, 23, 59, -82, + 25, 64, -88, 28, 68, -94, 30, 72, -99, 32, 76, -105, + 4, -1, 4, 5, 1, -3, 5, 6, -14, 6, 14, -24, + 8, 20, -34, 10, 26, -42, 12, 33, -49, 14, 38, -56, + 16, 44, -63, 19, 49, -69, 21, 54, -76, 23, 58, -82, + 26, 63, -87, 28, 67, -93, 30, 71, -99, 32, 76, -104, + 6, -4, 6, 6, -1, -1, 7, 3, -12, 8, 11, -22, + 9, 17, -32, 11, 24, -40, 13, 30, -48, 15, 36, -55, + 17, 42, -62, 19, 47, -68, 21, 52, -75, 24, 57, -81, + 26, 62, -87, 28, 66, -93, 30, 70, -98, 33, 75, -104, + 7, -7, 8, 7, -4, 1, 8, 0, -10, 9, 8, -20, + 10, 14, -30, 12, 21, -38, 14, 28, -47, 15, 34, -54, + 18, 40, -61, 20, 45, -67, 22, 51, -74, 24, 55, -80, + 26, 60, -86, 29, 65, -92, 31, 69, -98, 33, 74, -103, + 8, -10, 11, 9, -7, 3, 9, -2, -7, 10, 4, -18, + 12, 11, -28, 13, 18, -36, 15, 25, -45, 16, 31, -52, + 18, 37, -60, 20, 43, -66, 23, 49, -73, 25, 54, -79, + 27, 59, -85, 29, 64, -91, 31, 68, -97, 33, 73, -103, + 10, -13, 13, 10, -10, 5, 11, -5, -5, 12, 1, -16, + 13, 8, -26, 14, 15, -35, 16, 22, -43, 17, 29, -51, + 19, 35, -58, 21, 41, -65, 23, 47, -72, 25, 52, -78, + 27, 57, -84, 29, 62, -90, 32, 67, -96, 34, 71, -102, + 12, -15, 15, 12, -12, 8, 12, -7, -3, 13, -1, -14, + 14, 5, -23, 15, 12, -32, 17, 19, -41, 18, 26, -49, + 20, 32, -57, 22, 38, -64, 24, 44, -71, 26, 50, -77, + 28, 55, -83, 30, 60, -89, 32, 65, -95, 34, 70, -101, + 13, -17, 17, 14, -15, 10, 14, -10, -1, 15, -4, -11, + 16, 2, -21, 17, 9, -30, 18, 17, -40, 19, 23, -47, + 21, 30, -55, 23, 36, -62, 25, 42, -69, 27, 48, -76, + 29, 53, -82, 31, 58, -88, 33, 63, -95, 35, 68, -100, + 15, -20, 20, 16, -17, 12, 16, -13, 2, 17, -7, -9, + 17, -1, -18, 18, 6, -28, 20, 13, -37, 21, 20, -45, + 22, 27, -53, 24, 33, -60, 26, 39, -68, 28, 45, -74, + 30, 50, -81, 31, 56, -87, 33, 61, -93, 35, 66, -99, + 17, -21, 22, 17, -19, 14, 18, -15, 4, 18, -10, -6, + 19, -3, -16, 20, 3, -26, 21, 10, -35, 22, 17, -43, + 24, 24, -51, 25, 30, -59, 27, 37, -66, 28, 42, -73, + 30, 48, -80, 32, 54, -86, 34, 59, -92, 36, 64, -98, + 19, -23, 24, 19, -21, 16, 19, -18, 6, 20, -12, -4, + 20, -6, -14, 21, 0, -23, 22, 7, -33, 23, 14, -41, + 25, 21, -50, 26, 27, -57, 28, 34, -65, 29, 40, -71, + 31, 46, -78, 33, 52, -85, 35, 57, -91, 37, 62, -97, + 20, -25, 26, 20, -23, 18, 21, -20, 8, 21, -15, -2, + 22, -9, -12, 22, -2, -21, 23, 4, -31, 25, 11, -39, + 26, 18, -48, 27, 25, -55, 29, 31, -63, 30, 37, -70, + 32, 44, -77, 34, 49, -83, 35, 55, -90, 37, 60, -96, + 22, -27, 27, 22, -25, 20, 22, -22, 10, 23, -17, 0, + 23, -11, -10, 24, -5, -19, 25, 1, -29, 26, 8, -37, + 27, 15, -46, 28, 22, -54, 30, 29, -61, 31, 35, -68, + 33, 41, -75, 35, 47, -82, 36, 53, -89, 38, 58, -95, + 23, -28, 29, 23, -27, 22, 24, -24, 12, 24, -19, 2, + 25, -14, -7, 25, -8, -17, 26, -1, -27, 27, 5, -35, + 28, 13, -44, 29, 19, -52, 31, 26, -60, 32, 32, -67, + 34, 39, -74, 35, 45, -81, 37, 50, -87, 39, 56, -93, + 25, -30, 31, 25, -28, 23, 25, -26, 14, 26, -21, 4, + 26, -16, -5, 27, -10, -15, 27, -3, -24, 28, 3, -33, + 29, 10, -42, 31, 16, -50, 32, 23, -58, 33, 30, -65, + 35, 36, -72, 36, 42, -79, 38, 48, -86, 40, 54, -92, + 26, -31, 32, 26, -30, 25, 27, -27, 16, 27, -23, 6, + 27, -18, -3, 28, -12, -13, 29, -6, -22, 30, 0, -31, + 31, 7, -40, 32, 14, -48, 33, 21, -56, 34, 27, -63, + 36, 34, -71, 37, 40, -78, 39, 46, -84, 41, 52, -91, + 28, -33, 33, 28, -31, 27, 28, -29, 18, 28, -25, 8, + 29, -20, -1, 29, -15, -10, 30, -8, -20, 31, -2, -29, + 32, 4, -38, 33, 11, -46, 34, 18, -54, 36, 24, -62, + 37, 31, -69, 38, 37, -76, 40, 43, -83, 41, 49, -89, + 29, -34, 35, 29, -33, 29, 30, -31, 20, 30, -27, 10, + 30, -23, 0, 31, -17, -8, 32, -11, -18, 32, -5, -27, + 33, 2, -36, 34, 8, -44, 35, 15, -52, 37, 22, -60, + 38, 28, -67, 39, 35, -74, 41, 41, -81, 42, 47, -88, + 31, -36, 36, 31, -34, 30, 31, -32, 22, 31, -29, 12, + 32, -25, 2, 32, -19, -6, 33, -13, -16, 34, -7, -25, + 35, 0, -34, 35, 6, -42, 37, 13, -51, 38, 19, -58, + 39, 26, -66, 40, 32, -73, 42, 38, -80, 43, 44, -86, + 32, -37, 37, 32, -36, 32, 33, -34, 24, 33, -31, 14, + 33, -27, 5, 34, -21, -4, 34, -15, -14, 35, -9, -23, + 36, -3, -32, 37, 3, -40, 38, 10, -49, 39, 16, -56, + 40, 23, -64, 41, 29, -71, 43, 36, -78, 44, 42, -85, + 34, -39, 39, 34, -37, 33, 34, -35, 25, 34, -33, 16, + 35, -28, 7, 35, -24, -2, 36, -18, -12, 36, -12, -21, + 37, -5, -30, 38, 0, -38, 39, 7, -47, 40, 14, -54, + 41, 21, -62, 42, 27, -69, 44, 33, -77, 45, 39, -83, + 35, -40, 40, 35, -39, 34, 36, -37, 27, 36, -34, 18, + 36, -30, 9, 36, -26, 0, 37, -20, -10, 38, -14, -19, + 38, -8, -28, 39, -1, -36, 40, 5, -45, 41, 11, -53, + 42, 18, -60, 44, 24, -68, 45, 31, -75, 46, 37, -82, + 37, -41, 41, 37, -40, 36, 37, -38, 29, 37, -36, 20, + 38, -32, 11, 38, -28, 1, 38, -22, -8, 39, -16, -17, + 40, -10, -26, 41, -4, -34, 41, 2, -43, 42, 9, -51, + 43, 15, -58, 45, 22, -66, 46, 28, -73, 47, 34, -80, + 38, -43, 42, 38, -42, 37, 38, -40, 30, 39, -37, 22, + 39, -34, 13, 39, -30, 3, 40, -24, -6, 40, -19, -15, + 41, -12, -24, 42, -6, -32, 43, 0, -41, 44, 6, -49, + 45, 13, -57, 46, 19, -64, 47, 26, -72, 48, 32, -79, + 40, -44, 44, 40, -43, 39, 40, -41, 32, 40, -39, 23, + 40, -36, 14, 41, -31, 5, 41, -26, -4, 42, -21, -13, + 42, -15, -22, 43, -9, -30, 44, -2, -39, 45, 3, -47, + 46, 10, -55, 47, 16, -62, 48, 23, -70, 49, 29, -77, + 41, -45, 45, 41, -44, 40, 41, -43, 34, 41, -40, 25, + 42, -37, 16, 42, -33, 7, 42, -28, -2, 43, -23, -11, + 44, -17, -20, 44, -11, -28, 45, -4, -37, 46, 1, -45, + 47, 8, -53, 48, 14, -61, 49, 20, -68, 50, 27, -75, + 43, -47, 46, 43, -46, 41, 43, -44, 35, 43, -42, 27, + 43, -39, 18, 43, -35, 9, 44, -30, 0, 44, -25, -9, + 45, -19, -18, 46, -13, -26, 46, -7, -35, 47, -1, -43, + 48, 5, -51, 49, 11, -59, 50, 18, -66, 51, 24, -73, + 44, -48, 47, 44, -47, 43, 44, -45, 37, 44, -43, 29, + 44, -40, 20, 45, -37, 11, 45, -32, 2, 46, -27, -7, + 46, -21, -16, 47, -16, -24, 48, -9, -33, 48, -3, -41, + 49, 3, -49, 50, 9, -57, 51, 15, -65, 52, 22, -72, + 45, -49, 48, 45, -48, 44, 45, -47, 38, 46, -45, 30, + 46, -42, 22, 46, -38, 13, 47, -34, 4, 47, -29, -5, + 48, -23, -14, 48, -18, -22, 49, -11, -31, 50, -5, -39, + 50, 0, -47, 51, 6, -55, 52, 13, -63, 53, 19, -70, + 47, -51, 50, 47, -50, 46, 47, -48, 40, 47, -46, 33, + 48, -44, 24, 48, -40, 15, 48, -36, 6, 49, -31, -2, + 49, -26, -11, 50, -20, -20, 50, -14, -29, 51, -8, -37, + 52, -2, -45, 53, 3, -53, 54, 10, -61, 55, 16, -68, + 48, -52, 51, 49, -51, 47, 49, -50, 41, 49, -48, 34, + 49, -45, 26, 49, -42, 17, 50, -38, 8, 50, -33, 0, + 50, -28, -9, 51, -23, -18, 52, -17, -27, 52, -11, -35, + 53, -5, -43, 54, 1, -51, 55, 7, -59, 56, 13, -66, + 50, -53, 52, 50, -52, 48, 50, -51, 43, 50, -49, 36, + 50, -47, 28, 51, -44, 19, 51, -40, 10, 51, -35, 1, + 52, -30, -8, 52, -25, -16, 53, -19, -25, 54, -13, -33, + 54, -7, -41, 55, -1, -49, 56, 5, -57, 57, 11, -64, + 51, -54, 53, 51, -54, 50, 51, -52, 44, 51, -50, 37, + 52, -48, 29, 52, -45, 21, 52, -41, 12, 53, -37, 3, + 53, -32, -6, 54, -27, -14, 54, -21, -23, 55, -15, -31, + 56, -9, -39, 56, -3, -47, 57, 2, -55, 58, 9, -63, + 53, -56, 54, 53, -55, 51, 53, -54, 45, 53, -52, 39, + 53, -49, 31, 53, -47, 23, 54, -43, 14, 54, -39, 5, + 54, -34, -4, 55, -29, -12, 55, -23, -21, 56, -17, -29, + 57, -12, -37, 58, -5, -45, 58, 0, -53, 59, 6, -61, + 54, -57, 55, 54, -56, 52, 54, -55, 47, 54, -53, 41, + 54, -51, 33, 55, -48, 25, 55, -45, 16, 55, -41, 7, + 56, -36, -2, 56, -31, -10, 57, -25, -19, 57, -20, -27, + 58, -14, -35, 59, -8, -44, 60, -2, -51, 60, 4, -59, + 55, -58, 56, 55, -57, 53, 55, -56, 48, 56, -54, 42, + 56, -52, 34, 56, -49, 26, 56, -46, 17, 57, -42, 9, + 57, -38, 0, 57, -33, -8, 58, -27, -17, 59, -22, -25, + 59, -16, -33, 60, -10, -42, 61, -4, -49, 62, 1, -57, + 57, -59, 57, 57, -58, 55, 57, -57, 50, 57, -55, 43, + 57, -53, 36, 57, -51, 28, 58, -48, 19, 58, -44, 11, + 58, -39, 2, 59, -35, -7, 59, -29, -15, 60, -24, -24, + 60, -18, -32, 61, -12, -40, 62, -6, -48, 63, 0, -55, + 58, -60, 58, 58, -60, 56, 58, -58, 51, 58, -57, 45, + 58, -55, 38, 59, -52, 30, 59, -49, 21, 59, -46, 13, + 60, -41, 4, 60, -36, -5, 60, -31, -13, 61, -26, -22, + 62, -20, -30, 62, -14, -38, 63, -8, -46, 64, -2, -54, + 59, -61, 59, 59, -61, 57, 59, -60, 52, 60, -58, 46, + 60, -56, 39, 60, -54, 31, 60, -51, 23, 60, -47, 14, + 61, -43, 5, 61, -38, -3, 62, -33, -12, 62, -28, -20, + 63, -22, -28, 64, -16, -36, 64, -11, -44, 65, -5, -52, + 61, -63, 61, 61, -62, 58, 61, -61, 54, 61, -59, 48, + 61, -57, 41, 61, -55, 33, 61, -52, 24, 62, -49, 16, + 62, -44, 7, 63, -40, -1, 63, -35, -10, 64, -30, -18, + 64, -24, -26, 65, -19, -34, 65, -13, -42, 66, -7, -50, + 62, -64, 62, 62, -63, 59, 62, -62, 55, 62, -61, 49, + 62, -59, 42, 63, -56, 35, 63, -53, 26, 63, -50, 18, + 63, -46, 9, 64, -42, 1, 64, -37, -8, 65, -32, -16, + 65, -26, -24, 66, -21, -32, 67, -15, -40, 67, -9, -48, + 63, -65, 63, 63, -64, 60, 63, -63, 56, 63, -62, 50, + 64, -60, 44, 64, -58, 36, 64, -55, 28, 64, -52, 20, + 65, -48, 11, 65, -44, 3, 65, -39, -6, 66, -34, -14, + 67, -28, -22, 67, -23, -31, 68, -17, -38, 69, -11, -46, + 65, -66, 64, 65, -65, 62, 65, -64, 58, 65, -63, 52, + 65, -61, 45, 65, -59, 38, 65, -56, 30, 66, -53, 21, + 66, -49, 13, 66, -45, 4, 67, -40, -4, 67, -36, -12, + 68, -30, -20, 68, -25, -29, 69, -19, -37, 70, -13, -44, + 66, -67, 65, 66, -67, 63, 66, -66, 59, 66, -64, 53, + 66, -62, 47, 66, -60, 39, 67, -58, 31, 67, -55, 23, + 67, -51, 14, 68, -47, 6, 68, -42, -2, 68, -37, -11, + 69, -32, -19, 70, -27, -27, 70, -21, -35, 71, -16, -43, + 67, -68, 66, 67, -68, 64, 67, -67, 60, 67, -65, 54, + 68, -64, 48, 68, -62, 41, 68, -59, 33, 68, -56, 25, + 68, -52, 16, 69, -49, 8, 69, -44, -1, 70, -39, -9, + 70, -34, -17, 71, -29, -25, 71, -23, -33, 72, -18, -41, + 69, -69, 67, 69, -69, 65, 69, -68, 61, 69, -67, 56, + 69, -65, 49, 69, -63, 42, 69, -60, 34, 69, -57, 27, + 70, -54, 18, 70, -50, 10, 70, -46, 1, 71, -41, -7, + 71, -36, -15, 72, -31, -23, 72, -25, -31, 73, -20, -39, + 70, -70, 68, 70, -70, 66, 70, -69, 63, 70, -68, 57, + 70, -66, 51, 70, -64, 44, 70, -62, 36, 71, -59, 28, + 71, -55, 20, 71, -52, 12, 72, -47, 3, 72, -43, -5, + 73, -38, -13, 73, -33, -21, 74, -27, -29, 74, -22, -37, + 71, -71, 69, 71, -71, 67, 71, -70, 64, 71, -69, 58, + 71, -67, 52, 72, -65, 45, 72, -63, 38, 72, -60, 30, + 72, -57, 21, 73, -53, 13, 73, -49, 5, 73, -45, -3, + 74, -40, -11, 74, -35, -20, 75, -29, -27, 75, -24, -35, + 72, -73, 70, 72, -72, 68, 72, -71, 65, 73, -70, 60, + 73, -69, 54, 73, -67, 47, 73, -64, 39, 73, -62, 32, + 73, -58, 23, 74, -55, 15, 74, -51, 7, 75, -46, -1, + 75, -41, -9, 76, -37, -18, 76, -31, -26, 77, -26, -34, + 74, -74, 71, 74, -73, 69, 74, -72, 66, 74, -71, 61, + 74, -70, 55, 74, -68, 48, 74, -66, 41, 74, -63, 33, + 75, -60, 25, 75, -56, 17, 75, -52, 8, 76, -48, 0, + 76, -43, -8, 77, -38, -16, 77, -33, -24, 78, -28, -32, + 75, -75, 72, 75, -75, 71, 75, -74, 68, 75, -73, 62, + 76, -71, 57, 76, -69, 50, 76, -67, 43, 76, -65, 35, + 76, -61, 27, 77, -58, 19, 77, -54, 11, 77, -50, 3, + 78, -45, -5, 78, -41, -14, 79, -36, -22, 79, -30, -30, + 77, -76, 73, 77, -76, 72, 77, -75, 69, 77, -74, 64, + 77, -72, 58, 77, -71, 52, 77, -68, 44, 77, -66, 37, + 78, -63, 29, 78, -60, 21, 78, -56, 12, 79, -52, 4, + 79, -47, -4, 79, -43, -12, 80, -37, -20, 80, -32, -28, + 78, -77, 75, 78, -77, 73, 78, -76, 70, 78, -75, 65, + 78, -74, 59, 78, -72, 53, 78, -70, 46, 79, -67, 38, + 79, -64, 30, 79, -61, 22, 79, -57, 14, 80, -53, 6, + 80, -49, -2, 81, -44, -10, 81, -39, -18, 82, -34, -26, + 79, -78, 76, 79, -78, 74, 79, -77, 71, 79, -76, 66, + 79, -75, 61, 79, -73, 54, 80, -71, 47, 80, -68, 40, + 80, -66, 32, 80, -62, 24, 81, -59, 16, 81, -55, 8, + 81, -51, 0, 82, -46, -8, 82, -41, -16, 83, -36, -24, + 80, -79, 77, 80, -79, 75, 80, -78, 72, 80, -77, 68, + 81, -76, 62, 81, -74, 56, 81, -72, 49, 81, -70, 41, + 81, -67, 33, 82, -64, 26, 82, -60, 17, 82, -57, 9, + 83, -52, 1, 83, -48, -7, 83, -43, -15, 84, -38, -23, + 82, -80, 78, 82, -80, 76, 82, -79, 73, 82, -78, 69, + 82, -77, 63, 82, -75, 57, 82, -73, 50, 82, -71, 43, + 82, -68, 35, 83, -65, 27, 83, -62, 19, 83, -58, 11, + 84, -54, 3, 84, -50, -5, 85, -45, -13, 85, -40, -21, + 83, -81, 79, 83, -81, 77, 83, -80, 74, 83, -79, 70, + 83, -78, 65, 83, -77, 59, 83, -74, 52, 83, -72, 45, + 84, -70, 37, 84, -67, 29, 84, -63, 21, 85, -60, 13, + 85, -55, 5, 85, -51, -3, 86, -46, -11, 86, -42, -19, + 84, -82, 80, 84, -82, 78, 84, -81, 75, 84, -81, 71, + 84, -79, 66, 84, -78, 60, 85, -76, 53, 85, -74, 46, + 85, -71, 38, 85, -68, 31, 85, -65, 22, 86, -61, 15, + 86, -57, 6, 87, -53, -1, 87, -48, -9, 87, -43, -17, + 85, -84, 81, 85, -83, 79, 85, -83, 76, 85, -82, 72, + 86, -80, 67, 86, -79, 61, 86, -77, 54, 86, -75, 48, + 86, -72, 40, 86, -69, 32, 87, -66, 24, 87, -63, 16, + 87, -59, 8, 88, -55, 0, 88, -50, -8, 89, -45, -16, + 87, -85, 82, 87, -84, 80, 87, -84, 78, 87, -83, 74, + 87, -82, 68, 87, -80, 63, 87, -78, 56, 87, -76, 49, + 87, -73, 41, 88, -71, 34, 88, -67, 26, 88, -64, 18, + 89, -60, 10, 89, -56, 2, 89, -52, -6, 90, -47, -14, + 88, -86, 83, 88, -85, 81, 88, -85, 79, 88, -84, 75, + 88, -83, 70, 88, -81, 64, 88, -79, 57, 88, -77, 50, + 89, -75, 43, 89, -72, 35, 89, -69, 27, 89, -66, 20, + 90, -62, 11, 90, -58, 4, 90, -53, -4, 91, -49, -12, + 2, 8, 1, 2, 12, -6, 3, 17, -18, 4, 24, -28, + 5, 31, -38, 8, 37, -45, 10, 41, -52, 12, 46, -59, + 15, 50, -65, 17, 54, -71, 20, 58, -77, 22, 62, -83, + 25, 66, -89, 27, 71, -94, 29, 74, -100, 32, 78, -105, + 3, 7, 2, 3, 10, -5, 4, 15, -16, 5, 22, -27, + 6, 29, -36, 8, 34, -44, 11, 39, -51, 13, 44, -58, + 15, 49, -64, 18, 53, -71, 20, 57, -77, 23, 62, -83, + 25, 66, -88, 27, 70, -94, 30, 74, -99, 32, 78, -105, + 3, 5, 3, 4, 9, -4, 4, 13, -15, 5, 20, -26, + 7, 27, -35, 9, 32, -43, 11, 37, -50, 13, 42, -57, + 16, 47, -64, 18, 52, -70, 21, 56, -76, 23, 61, -82, + 25, 65, -88, 28, 69, -94, 30, 73, -99, 32, 77, -105, + 4, 4, 4, 5, 7, -3, 5, 12, -14, 6, 18, -24, + 8, 24, -34, 10, 30, -42, 12, 35, -49, 14, 41, -56, + 16, 46, -63, 19, 50, -69, 21, 55, -76, 23, 60, -82, + 26, 64, -87, 28, 68, -93, 30, 72, -99, 32, 77, -104, + 5, 2, 6, 6, 5, -1, 6, 10, -12, 7, 16, -23, + 9, 22, -32, 10, 27, -40, 12, 33, -48, 15, 39, -55, + 17, 44, -62, 19, 49, -69, 21, 54, -75, 23, 59, -81, + 26, 63, -87, 28, 67, -93, 30, 72, -98, 33, 76, -104, + 6, 0, 8, 7, 3, 0, 7, 7, -10, 8, 13, -21, + 10, 19, -30, 11, 25, -39, 13, 31, -47, 15, 37, -54, + 17, 42, -61, 19, 47, -68, 22, 53, -74, 24, 57, -80, + 26, 62, -86, 28, 66, -92, 31, 71, -98, 33, 75, -104, + 8, -3, 10, 8, 0, 2, 9, 4, -8, 10, 10, -19, + 11, 16, -29, 12, 22, -37, 14, 29, -46, 16, 34, -53, + 18, 40, -60, 20, 46, -67, 22, 51, -73, 24, 56, -79, + 27, 60, -86, 29, 65, -92, 31, 69, -97, 33, 74, -103, + 9, -6, 12, 10, -3, 4, 10, 1, -6, 11, 7, -17, + 12, 13, -27, 14, 19, -36, 15, 26, -44, 17, 32, -52, + 19, 38, -59, 21, 44, -66, 23, 49, -72, 25, 54, -79, + 27, 59, -85, 29, 64, -91, 31, 68, -97, 34, 73, -102, + 11, -8, 14, 11, -6, 6, 12, -1, -4, 12, 4, -15, + 13, 10, -25, 15, 17, -34, 16, 23, -42, 18, 29, -50, + 20, 36, -58, 21, 41, -64, 24, 47, -71, 25, 52, -78, + 28, 57, -84, 30, 62, -90, 32, 67, -96, 34, 72, -102, + 12, -11, 16, 13, -9, 8, 13, -4, -2, 14, 1, -13, + 15, 7, -23, 16, 14, -32, 17, 20, -41, 19, 27, -48, + 21, 33, -56, 22, 39, -63, 24, 45, -70, 26, 50, -76, + 28, 55, -83, 30, 61, -89, 32, 65, -95, 34, 70, -101, + 14, -13, 18, 14, -11, 11, 15, -7, 0, 15, -1, -11, + 16, 4, -20, 17, 11, -30, 18, 18, -39, 20, 24, -47, + 22, 31, -55, 23, 37, -62, 25, 43, -69, 27, 48, -75, + 29, 53, -82, 31, 59, -88, 33, 64, -94, 35, 69, -100, + 16, -16, 21, 16, -14, 13, 16, -10, 2, 17, -5, -8, + 18, 0, -18, 19, 7, -27, 20, 14, -36, 21, 21, -45, + 23, 27, -53, 24, 33, -60, 26, 40, -67, 28, 45, -74, + 30, 51, -80, 32, 56, -87, 34, 61, -93, 36, 66, -99, + 17, -18, 23, 18, -16, 15, 18, -13, 4, 18, -7, -6, + 19, -1, -16, 20, 4, -25, 21, 11, -34, 22, 18, -43, + 24, 25, -51, 25, 31, -58, 27, 37, -66, 29, 43, -72, + 30, 49, -79, 32, 54, -85, 34, 59, -92, 36, 65, -98, + 19, -20, 24, 19, -18, 17, 19, -15, 6, 20, -10, -3, + 21, -4, -13, 21, 1, -23, 22, 8, -32, 24, 15, -41, + 25, 22, -49, 26, 28, -57, 28, 35, -64, 30, 40, -71, + 31, 46, -78, 33, 52, -84, 35, 57, -91, 37, 63, -97, + 21, -22, 26, 21, -20, 18, 21, -17, 8, 21, -12, -1, + 22, -7, -11, 23, -1, -21, 24, 5, -30, 25, 12, -39, + 26, 19, -47, 27, 25, -55, 29, 32, -63, 31, 38, -69, + 32, 44, -77, 34, 50, -83, 36, 55, -90, 38, 61, -96, + 22, -24, 28, 22, -22, 20, 22, -19, 10, 23, -15, 0, + 23, -9, -9, 24, -3, -19, 25, 3, -28, 26, 9, -37, + 27, 16, -46, 29, 23, -53, 30, 29, -61, 32, 35, -68, + 33, 42, -75, 35, 47, -82, 36, 53, -88, 38, 59, -94, + 24, -26, 29, 24, -24, 22, 24, -21, 13, 24, -17, 3, + 25, -12, -7, 26, -6, -16, 26, 0, -26, 27, 6, -35, + 29, 13, -44, 30, 20, -51, 31, 27, -59, 33, 33, -66, + 34, 39, -74, 36, 45, -80, 37, 51, -87, 39, 56, -93, + 25, -27, 31, 25, -26, 24, 25, -23, 14, 26, -19, 5, + 26, -14, -5, 27, -8, -14, 28, -2, -24, 29, 4, -33, + 30, 11, -42, 31, 17, -50, 32, 24, -58, 34, 30, -65, + 35, 37, -72, 37, 43, -79, 38, 48, -86, 40, 54, -92, + 27, -29, 32, 27, -27, 26, 27, -25, 16, 27, -21, 7, + 28, -17, -3, 28, -11, -12, 29, -5, -22, 30, 1, -31, + 31, 8, -40, 32, 14, -48, 33, 21, -56, 35, 28, -63, + 36, 34, -70, 38, 40, -77, 39, 46, -84, 41, 52, -91, + 28, -31, 34, 28, -29, 27, 28, -27, 18, 29, -24, 9, + 29, -19, -1, 30, -13, -10, 30, -7, -20, 31, -1, -29, + 32, 5, -38, 33, 12, -46, 34, 19, -54, 36, 25, -61, + 37, 31, -69, 38, 37, -76, 40, 44, -83, 42, 49, -89, + 30, -32, 35, 30, -31, 29, 30, -29, 20, 30, -26, 11, + 31, -21, 1, 31, -16, -8, 32, -10, -18, 33, -3, -27, + 33, 3, -36, 34, 9, -44, 36, 16, -52, 37, 22, -60, + 38, 29, -67, 39, 35, -74, 41, 41, -81, 42, 47, -88, + 31, -34, 36, 31, -32, 30, 31, -30, 22, 32, -27, 13, + 32, -23, 3, 33, -18, -6, 33, -12, -16, 34, -6, -25, + 35, 0, -34, 36, 6, -42, 37, 13, -50, 38, 20, -58, + 39, 26, -65, 41, 32, -72, 42, 39, -80, 43, 45, -86, + 33, -35, 38, 33, -34, 32, 33, -32, 24, 33, -29, 14, + 33, -25, 5, 34, -20, -4, 35, -14, -14, 35, -8, -23, + 36, -2, -32, 37, 4, -40, 38, 11, -48, 39, 17, -56, + 40, 24, -64, 42, 30, -71, 43, 36, -78, 44, 42, -85, + 34, -37, 39, 34, -36, 33, 34, -34, 26, 35, -31, 16, + 35, -27, 7, 35, -22, -2, 36, -17, -12, 37, -11, -20, + 37, -4, -30, 38, 1, -38, 39, 8, -46, 40, 14, -54, + 41, 21, -62, 43, 27, -69, 44, 34, -76, 45, 40, -83, + 36, -38, 40, 36, -37, 35, 36, -35, 27, 36, -33, 18, + 36, -29, 9, 37, -24, 0, 37, -19, -9, 38, -13, -18, + 39, -7, -28, 39, -1, -36, 40, 5, -45, 41, 12, -52, + 42, 18, -60, 44, 25, -67, 45, 31, -75, 46, 37, -82, + 37, -40, 41, 37, -39, 36, 37, -37, 29, 37, -34, 20, + 38, -31, 11, 38, -26, 2, 39, -21, -7, 39, -15, -16, + 40, -9, -26, 41, -3, -34, 42, 3, -43, 43, 9, -50, + 44, 16, -58, 45, 22, -66, 46, 29, -73, 47, 35, -80, + 38, -41, 43, 38, -40, 37, 39, -38, 31, 39, -36, 22, + 39, -33, 13, 39, -28, 4, 40, -23, -5, 40, -18, -14, + 41, -12, -24, 42, -5, -32, 43, 0, -41, 44, 6, -49, + 45, 13, -56, 46, 19, -64, 47, 26, -71, 48, 32, -78, + 40, -43, 44, 40, -42, 39, 40, -40, 32, 40, -38, 24, + 40, -34, 15, 41, -30, 6, 41, -25, -3, 42, -20, -12, + 42, -14, -22, 43, -8, -30, 44, -1, -39, 45, 4, -47, + 46, 11, -55, 47, 17, -62, 48, 23, -70, 49, 29, -77, + 41, -44, 45, 41, -43, 40, 41, -41, 34, 42, -39, 25, + 42, -36, 17, 42, -32, 8, 43, -27, -1, 43, -22, -10, + 44, -16, -20, 44, -10, -28, 45, -4, -37, 46, 1, -45, + 47, 8, -53, 48, 14, -60, 49, 21, -68, 50, 27, -75, + 43, -45, 46, 43, -44, 41, 43, -43, 35, 43, -41, 27, + 43, -38, 18, 44, -34, 10, 44, -29, 0, 44, -24, -8, + 45, -18, -18, 46, -13, -26, 47, -6, -35, 47, 0, -43, + 48, 6, -51, 49, 12, -59, 50, 18, -66, 51, 24, -73, + 44, -47, 47, 44, -46, 43, 44, -44, 37, 44, -42, 29, + 45, -39, 20, 45, -36, 11, 45, -31, 2, 46, -26, -6, + 46, -21, -16, 47, -15, -24, 48, -9, -33, 49, -3, -41, + 49, 3, -49, 50, 9, -57, 51, 16, -64, 53, 22, -72, + 45, -48, 48, 46, -47, 44, 46, -46, 38, 46, -43, 31, + 46, -41, 22, 46, -37, 13, 47, -33, 4, 47, -28, -4, + 48, -23, -14, 48, -17, -22, 49, -11, -31, 50, -5, -39, + 51, 1, -47, 52, 7, -55, 52, 13, -63, 54, 19, -70, + 47, -50, 50, 47, -49, 46, 47, -47, 40, 47, -45, 33, + 48, -43, 24, 48, -39, 16, 48, -35, 6, 49, -31, -2, + 49, -25, -11, 50, -20, -20, 51, -14, -29, 51, -8, -37, + 52, -2, -45, 53, 4, -53, 54, 10, -60, 55, 16, -68, + 49, -51, 51, 49, -50, 47, 49, -49, 41, 49, -47, 34, + 49, -44, 26, 49, -41, 18, 50, -37, 8, 50, -33, 0, + 51, -27, -9, 51, -22, -18, 52, -16, -27, 53, -10, -35, + 53, -4, -43, 54, 1, -51, 55, 8, -59, 56, 14, -66, + 50, -52, 52, 50, -51, 49, 50, -50, 43, 50, -48, 36, + 50, -46, 28, 51, -43, 19, 51, -39, 10, 51, -34, 1, + 52, -29, -7, 52, -24, -16, 53, -18, -25, 54, -12, -33, + 54, -6, -41, 55, 0, -49, 56, 5, -57, 57, 11, -64, + 51, -53, 53, 51, -53, 50, 51, -51, 44, 52, -49, 38, + 52, -47, 30, 52, -44, 21, 52, -41, 12, 53, -36, 3, + 53, -31, -5, 54, -26, -14, 54, -20, -23, 55, -15, -31, + 56, -9, -39, 56, -3, -47, 57, 3, -55, 58, 9, -62, + 53, -55, 54, 53, -54, 51, 53, -53, 46, 53, -51, 39, + 53, -48, 31, 53, -46, 23, 54, -42, 14, 54, -38, 5, + 54, -33, -4, 55, -28, -12, 56, -22, -21, 56, -17, -29, + 57, -11, -37, 58, -5, -45, 58, 1, -53, 59, 7, -61, + 54, -56, 55, 54, -55, 52, 54, -54, 47, 54, -52, 41, + 54, -50, 33, 55, -47, 25, 55, -44, 16, 55, -40, 7, + 56, -35, -2, 56, -30, -10, 57, -25, -19, 57, -19, -27, + 58, -13, -35, 59, -7, -43, 60, -1, -51, 61, 4, -59, + 55, -57, 56, 55, -56, 54, 56, -55, 48, 56, -53, 42, + 56, -51, 35, 56, -49, 26, 56, -45, 17, 57, -42, 9, + 57, -37, 0, 58, -32, -8, 58, -27, -17, 59, -21, -25, + 59, -15, -33, 60, -9, -42, 61, -4, -49, 62, 2, -57, + 57, -58, 57, 57, -58, 55, 57, -56, 50, 57, -55, 44, + 57, -53, 36, 57, -50, 28, 58, -47, 19, 58, -43, 11, + 58, -39, 2, 59, -34, -6, 59, -29, -15, 60, -23, -23, + 61, -18, -31, 61, -12, -40, 62, -6, -48, 63, 0, -55, + 58, -59, 58, 58, -59, 56, 58, -58, 51, 58, -56, 45, + 58, -54, 38, 59, -52, 30, 59, -48, 21, 59, -45, 13, + 60, -40, 4, 60, -36, -4, 61, -31, -13, 61, -25, -22, + 62, -20, -30, 62, -14, -38, 63, -8, -46, 64, -2, -53, + 59, -61, 60, 59, -60, 57, 60, -59, 52, 60, -57, 46, + 60, -55, 39, 60, -53, 32, 60, -50, 23, 61, -46, 14, + 61, -42, 6, 61, -38, -3, 62, -33, -11, 62, -27, -20, + 63, -22, -28, 64, -16, -36, 64, -10, -44, 65, -4, -52, + 61, -62, 61, 61, -61, 58, 61, -60, 54, 61, -59, 48, + 61, -57, 41, 61, -54, 33, 62, -51, 25, 62, -48, 16, + 62, -44, 7, 63, -39, -1, 63, -34, -10, 64, -29, -18, + 64, -24, -26, 65, -18, -34, 65, -12, -42, 66, -6, -50, + 62, -63, 62, 62, -62, 59, 62, -61, 55, 62, -60, 49, + 62, -58, 42, 63, -56, 35, 63, -53, 26, 63, -50, 18, + 63, -46, 9, 64, -41, 1, 64, -36, -8, 65, -31, -16, + 65, -26, -24, 66, -20, -32, 67, -15, -40, 67, -9, -48, + 63, -64, 63, 63, -64, 61, 63, -63, 56, 64, -61, 51, + 64, -59, 44, 64, -57, 36, 64, -54, 28, 64, -51, 20, + 65, -47, 11, 65, -43, 3, 66, -38, -6, 66, -33, -14, + 67, -28, -22, 67, -22, -30, 68, -17, -38, 69, -11, -46, + 65, -65, 64, 65, -65, 62, 65, -64, 58, 65, -62, 52, + 65, -60, 45, 65, -58, 38, 65, -56, 30, 66, -53, 22, + 66, -49, 13, 66, -45, 5, 67, -40, -4, 67, -35, -12, + 68, -30, -20, 68, -24, -29, 69, -19, -36, 70, -13, -44, + 66, -66, 65, 66, -66, 63, 66, -65, 59, 66, -64, 53, + 66, -62, 47, 66, -60, 40, 67, -57, 31, 67, -54, 23, + 67, -50, 15, 68, -46, 6, 68, -42, -2, 69, -37, -10, + 69, -32, -18, 70, -27, -27, 70, -21, -35, 71, -15, -43, + 67, -67, 66, 67, -67, 64, 67, -66, 60, 67, -65, 55, + 68, -63, 48, 68, -61, 41, 68, -58, 33, 68, -55, 25, + 69, -52, 16, 69, -48, 8, 69, -44, 0, 70, -39, -9, + 70, -34, -17, 71, -29, -25, 71, -23, -33, 72, -17, -41, + 69, -69, 67, 69, -68, 65, 69, -67, 61, 69, -66, 56, + 69, -64, 49, 69, -62, 43, 69, -60, 35, 69, -57, 27, + 70, -53, 18, 70, -50, 10, 71, -45, 1, 71, -41, -7, + 71, -36, -15, 72, -30, -23, 73, -25, -31, 73, -19, -39, + 70, -70, 68, 70, -69, 66, 70, -68, 63, 70, -67, 57, + 70, -66, 51, 70, -64, 44, 71, -61, 36, 71, -58, 28, + 71, -55, 20, 71, -51, 12, 72, -47, 3, 72, -42, -5, + 73, -37, -13, 73, -32, -21, 74, -27, -29, 74, -22, -37, + 71, -71, 69, 71, -70, 67, 71, -70, 64, 71, -68, 58, + 71, -67, 52, 72, -65, 46, 72, -62, 38, 72, -60, 30, + 72, -56, 22, 73, -53, 13, 73, -49, 5, 73, -44, -3, + 74, -39, -11, 74, -34, -19, 75, -29, -27, 76, -24, -35, + 72, -72, 70, 72, -72, 68, 73, -71, 65, 73, -70, 60, + 73, -68, 54, 73, -66, 47, 73, -64, 39, 73, -61, 32, + 74, -58, 23, 74, -54, 15, 74, -50, 7, 75, -46, -1, + 75, -41, -9, 76, -36, -18, 76, -31, -26, 77, -26, -34, + 74, -73, 71, 74, -73, 69, 74, -72, 66, 74, -71, 61, + 74, -69, 55, 74, -67, 48, 74, -65, 41, 75, -62, 33, + 75, -59, 25, 75, -56, 17, 75, -52, 8, 76, -48, 0, + 76, -43, -8, 77, -38, -16, 77, -33, -24, 78, -28, -32, + 75, -74, 73, 75, -74, 71, 75, -73, 68, 75, -72, 63, + 76, -71, 57, 76, -69, 50, 76, -67, 43, 76, -64, 35, + 76, -61, 27, 77, -58, 19, 77, -54, 11, 77, -50, 3, + 78, -45, -5, 78, -40, -14, 79, -35, -22, 79, -30, -30, + 77, -76, 74, 77, -75, 72, 77, -74, 69, 77, -73, 64, + 77, -72, 58, 77, -70, 52, 77, -68, 44, 77, -65, 37, + 78, -62, 29, 78, -59, 21, 78, -55, 12, 79, -51, 4, + 79, -47, -4, 79, -42, -12, 80, -37, -20, 81, -32, -28, + 78, -77, 75, 78, -76, 73, 78, -76, 70, 78, -74, 65, + 78, -73, 59, 78, -71, 53, 78, -69, 46, 79, -67, 38, + 79, -64, 30, 79, -61, 22, 79, -57, 14, 80, -53, 6, + 80, -49, -2, 81, -44, -10, 81, -39, -18, 82, -34, -26, + 79, -78, 76, 79, -77, 74, 79, -77, 71, 79, -76, 66, + 79, -74, 61, 79, -72, 55, 80, -70, 47, 80, -68, 40, + 80, -65, 32, 80, -62, 24, 81, -58, 16, 81, -55, 8, + 81, -50, 0, 82, -46, -8, 82, -41, -16, 83, -36, -24, + 80, -79, 77, 80, -78, 75, 80, -78, 72, 81, -77, 68, + 81, -75, 62, 81, -74, 56, 81, -72, 49, 81, -69, 42, + 81, -66, 34, 82, -63, 26, 82, -60, 17, 82, -56, 10, + 83, -52, 1, 83, -47, -7, 83, -43, -14, 84, -38, -22, + 82, -80, 78, 82, -79, 76, 82, -79, 73, 82, -78, 69, + 82, -77, 63, 82, -75, 57, 82, -73, 50, 82, -71, 43, + 83, -68, 35, 83, -65, 27, 83, -61, 19, 83, -58, 11, + 84, -54, 3, 84, -49, -5, 85, -44, -13, 85, -40, -21, + 83, -81, 79, 83, -81, 77, 83, -80, 74, 83, -79, 70, + 83, -78, 65, 83, -76, 59, 83, -74, 52, 84, -72, 45, + 84, -69, 37, 84, -66, 29, 84, -63, 21, 85, -59, 13, + 85, -55, 5, 85, -51, -3, 86, -46, -11, 86, -41, -19, + 84, -82, 80, 84, -82, 78, 84, -81, 75, 84, -80, 71, + 84, -79, 66, 84, -77, 60, 85, -75, 53, 85, -73, 46, + 85, -70, 38, 85, -68, 31, 86, -64, 22, 86, -61, 15, + 86, -57, 6, 87, -53, -1, 87, -48, -9, 87, -43, -17, + 85, -83, 81, 85, -83, 79, 85, -82, 77, 85, -81, 72, + 86, -80, 67, 86, -78, 61, 86, -76, 55, 86, -74, 48, + 86, -72, 40, 86, -69, 32, 87, -66, 24, 87, -62, 16, + 87, -58, 8, 88, -54, 0, 88, -50, -7, 89, -45, -15, + 87, -84, 82, 87, -84, 80, 87, -83, 78, 87, -82, 74, + 87, -81, 69, 87, -80, 63, 87, -78, 56, 87, -76, 49, + 87, -73, 41, 88, -70, 34, 88, -67, 26, 88, -64, 18, + 89, -60, 10, 89, -56, 2, 89, -51, -6, 90, -47, -14, + 88, -85, 83, 88, -85, 81, 88, -84, 79, 88, -83, 75, + 88, -82, 70, 88, -81, 64, 88, -79, 57, 88, -77, 51, + 89, -74, 43, 89, -72, 35, 89, -68, 27, 89, -65, 20, + 90, -61, 12, 90, -57, 4, 91, -53, -4, 91, -48, -12, + 3, 13, 3, 3, 16, -5, 4, 21, -16, 5, 27, -26, + 6, 33, -36, 8, 37, -44, 11, 42, -51, 13, 46, -58, + 16, 50, -64, 18, 54, -70, 20, 59, -77, 23, 63, -82, + 25, 67, -88, 27, 71, -94, 30, 75, -99, 32, 78, -105, + 4, 12, 4, 4, 15, -3, 5, 19, -15, 6, 25, -25, + 7, 30, -35, 9, 35, -42, 11, 40, -50, 14, 44, -57, + 16, 49, -64, 18, 53, -70, 21, 58, -76, 23, 62, -82, + 25, 66, -88, 28, 70, -94, 30, 74, -99, 32, 78, -105, + 4, 10, 5, 5, 13, -2, 5, 18, -13, 6, 23, -24, + 8, 28, -33, 10, 33, -41, 12, 38, -49, 14, 43, -56, + 16, 48, -63, 19, 52, -69, 21, 57, -75, 23, 61, -81, + 26, 65, -87, 28, 69, -93, 30, 73, -99, 32, 77, -104, + 5, 8, 6, 6, 12, -1, 6, 16, -12, 7, 21, -23, + 9, 26, -32, 11, 31, -40, 13, 36, -48, 15, 41, -55, + 17, 46, -62, 19, 51, -68, 21, 56, -75, 24, 60, -81, + 26, 64, -87, 28, 68, -93, 30, 73, -98, 33, 77, -104, + 6, 6, 8, 7, 9, 0, 7, 13, -11, 8, 18, -21, + 10, 23, -31, 11, 29, -39, 13, 34, -47, 15, 39, -54, + 17, 45, -61, 19, 49, -68, 22, 54, -74, 24, 59, -80, + 26, 63, -86, 28, 68, -92, 31, 72, -98, 33, 76, -104, + 7, 4, 9, 8, 7, 2, 8, 11, -9, 9, 15, -19, + 11, 21, -29, 12, 26, -38, 14, 32, -46, 16, 37, -53, + 18, 43, -60, 20, 48, -67, 22, 53, -74, 24, 57, -80, + 27, 62, -86, 29, 67, -92, 31, 71, -97, 33, 75, -103, + 9, 1, 11, 9, 4, 4, 10, 8, -7, 11, 12, -18, + 12, 18, -27, 13, 24, -36, 15, 30, -45, 17, 35, -52, + 19, 41, -59, 20, 46, -66, 23, 51, -73, 25, 56, -79, + 27, 61, -85, 29, 65, -91, 31, 70, -97, 33, 74, -103, + 10, -1, 13, 10, 1, 6, 11, 5, -5, 12, 9, -16, + 13, 15, -25, 14, 21, -34, 16, 27, -43, 17, 33, -51, + 19, 39, -58, 21, 44, -65, 23, 49, -72, 25, 54, -78, + 27, 59, -84, 29, 64, -90, 32, 68, -96, 34, 73, -102, + 12, -4, 15, 12, -2, 8, 12, 2, -3, 13, 6, -14, + 14, 12, -23, 15, 18, -33, 17, 24, -42, 18, 30, -49, + 20, 36, -57, 22, 42, -64, 24, 47, -71, 26, 53, -77, + 28, 57, -83, 30, 62, -89, 32, 67, -95, 34, 72, -101, + 13, -6, 17, 13, -4, 10, 14, -1, -1, 14, 3, -12, + 15, 9, -21, 16, 15, -31, 18, 22, -40, 19, 28, -48, + 21, 34, -55, 23, 40, -62, 25, 45, -70, 27, 51, -76, + 29, 56, -82, 30, 61, -89, 33, 65, -95, 35, 70, -101, + 15, -9, 19, 15, -7, 11, 15, -4, 1, 16, 1, -10, + 17, 6, -19, 18, 12, -29, 19, 19, -38, 20, 25, -46, + 22, 31, -54, 24, 37, -61, 25, 43, -68, 27, 49, -75, + 29, 54, -81, 31, 59, -88, 33, 64, -94, 35, 69, -100, + 17, -12, 22, 17, -10, 14, 17, -7, 3, 18, -2, -7, + 18, 3, -17, 19, 9, -26, 20, 15, -36, 22, 22, -44, + 23, 28, -52, 25, 34, -59, 26, 40, -67, 28, 46, -73, + 30, 51, -80, 32, 57, -86, 34, 62, -93, 36, 67, -98, + 18, -14, 23, 18, -13, 16, 18, -10, 5, 19, -5, -5, + 20, 0, -15, 21, 6, -24, 22, 12, -34, 23, 19, -42, + 24, 25, -50, 26, 31, -58, 27, 38, -65, 29, 43, -72, + 31, 49, -79, 33, 55, -85, 35, 60, -91, 36, 65, -97, + 20, -17, 25, 20, -15, 17, 20, -12, 7, 20, -7, -3, + 21, -2, -13, 22, 3, -22, 23, 10, -32, 24, 16, -40, + 25, 23, -49, 27, 29, -56, 28, 35, -64, 30, 41, -70, + 32, 47, -77, 33, 52, -84, 35, 58, -90, 37, 63, -96, + 21, -19, 27, 21, -17, 19, 21, -14, 9, 22, -10, -1, + 22, -5, -10, 23, 0, -20, 24, 7, -30, 25, 13, -38, + 26, 20, -47, 28, 26, -54, 29, 33, -62, 31, 38, -69, + 32, 44, -76, 34, 50, -83, 36, 55, -89, 38, 61, -95, + 23, -21, 28, 23, -19, 21, 23, -17, 11, 23, -12, 1, + 24, -7, -8, 25, -2, -18, 25, 4, -28, 26, 10, -36, + 28, 17, -45, 29, 23, -53, 30, 30, -60, 32, 36, -67, + 33, 42, -75, 35, 48, -81, 37, 53, -88, 39, 59, -94, + 24, -23, 30, 24, -21, 23, 24, -19, 13, 25, -15, 3, + 25, -10, -6, 26, -4, -16, 27, 1, -26, 28, 8, -34, + 29, 14, -43, 30, 21, -51, 31, 27, -59, 33, 33, -66, + 34, 40, -73, 36, 45, -80, 38, 51, -87, 39, 57, -93, + 26, -25, 31, 26, -23, 24, 26, -21, 15, 26, -17, 5, + 27, -12, -4, 27, -7, -14, 28, 0, -23, 29, 5, -32, + 30, 12, -41, 31, 18, -49, 32, 25, -57, 34, 31, -64, + 35, 37, -72, 37, 43, -78, 38, 49, -85, 40, 54, -92, + 27, -27, 33, 27, -25, 26, 27, -23, 17, 28, -19, 7, + 28, -15, -2, 29, -9, -12, 29, -3, -21, 30, 2, -30, + 31, 9, -39, 32, 15, -47, 34, 22, -55, 35, 28, -63, + 36, 34, -70, 38, 40, -77, 39, 46, -84, 41, 52, -90, + 28, -28, 34, 29, -27, 28, 29, -25, 19, 29, -21, 9, + 29, -17, 0, 30, -12, -10, 31, -6, -19, 32, 0, -28, + 32, 6, -37, 33, 13, -45, 35, 19, -54, 36, 25, -61, + 37, 32, -68, 39, 38, -75, 40, 44, -82, 42, 50, -89, + 30, -30, 35, 30, -29, 29, 30, -27, 21, 30, -24, 11, + 31, -19, 1, 31, -14, -8, 32, -8, -17, 33, -2, -26, + 34, 4, -35, 35, 10, -44, 36, 17, -52, 37, 23, -59, + 38, 29, -67, 40, 35, -74, 41, 42, -81, 43, 47, -87, + 31, -32, 37, 31, -30, 31, 32, -28, 23, 32, -26, 13, + 32, -21, 3, 33, -16, -5, 33, -11, -15, 34, -5, -24, + 35, 1, -33, 36, 7, -42, 37, 14, -50, 38, 20, -57, + 39, 27, -65, 41, 33, -72, 42, 39, -79, 44, 45, -86, + 33, -33, 38, 33, -32, 32, 33, -30, 24, 33, -27, 15, + 34, -23, 5, 34, -19, -3, 35, -13, -13, 35, -7, -22, + 36, -1, -31, 37, 5, -40, 38, 11, -48, 39, 18, -56, + 40, 24, -63, 42, 30, -71, 43, 37, -78, 44, 42, -84, + 34, -35, 39, 34, -34, 34, 35, -32, 26, 35, -29, 17, + 35, -26, 7, 36, -21, -1, 36, -15, -11, 37, -10, -20, + 38, -3, -29, 38, 2, -38, 39, 9, -46, 40, 15, -54, + 42, 22, -62, 43, 28, -69, 44, 34, -76, 45, 40, -83, + 36, -36, 40, 36, -35, 35, 36, -33, 28, 36, -31, 18, + 36, -27, 9, 37, -23, 0, 37, -18, -9, 38, -12, -18, + 39, -6, -27, 40, 0, -36, 41, 6, -44, 42, 12, -52, + 43, 19, -60, 44, 25, -67, 45, 31, -75, 46, 37, -81, + 37, -38, 42, 37, -37, 36, 37, -35, 29, 38, -33, 20, + 38, -29, 11, 38, -25, 2, 39, -20, -7, 39, -14, -16, + 40, -8, -25, 41, -2, -34, 42, 3, -42, 43, 10, -50, + 44, 16, -58, 45, 22, -65, 46, 29, -73, 47, 35, -80, + 39, -39, 43, 39, -38, 38, 39, -37, 31, 39, -34, 22, + 39, -31, 13, 40, -27, 4, 40, -22, -5, 41, -17, -14, + 41, -11, -23, 42, -5, -32, 43, 1, -40, 44, 7, -48, + 45, 14, -56, 46, 20, -64, 47, 26, -71, 48, 32, -78, + 40, -41, 44, 40, -40, 39, 40, -38, 33, 40, -36, 24, + 41, -33, 15, 41, -29, 6, 41, -24, -3, 42, -19, -12, + 43, -13, -21, 43, -7, -30, 44, -1, -39, 45, 5, -46, + 46, 11, -54, 47, 17, -62, 48, 24, -70, 49, 30, -76, + 41, -42, 45, 41, -41, 40, 42, -40, 34, 42, -38, 26, + 42, -35, 17, 42, -31, 8, 43, -26, -1, 43, -21, -10, + 44, -15, -19, 45, -9, -28, 45, -3, -37, 46, 2, -45, + 47, 9, -52, 48, 15, -60, 49, 21, -68, 51, 27, -75, + 43, -44, 46, 43, -43, 42, 43, -41, 36, 43, -39, 27, + 43, -36, 19, 44, -33, 10, 44, -28, 0, 45, -23, -8, + 45, -17, -17, 46, -12, -26, 47, -5, -35, 47, 0, -43, + 48, 6, -51, 49, 12, -58, 50, 19, -66, 52, 25, -73, + 44, -45, 47, 44, -44, 43, 44, -43, 37, 45, -41, 29, + 45, -38, 21, 45, -35, 12, 45, -30, 2, 46, -25, -6, + 47, -20, -15, 47, -14, -24, 48, -8, -33, 49, -2, -41, + 50, 4, -49, 51, 10, -57, 52, 16, -64, 53, 22, -71, + 46, -47, 49, 46, -46, 44, 46, -44, 38, 46, -42, 31, + 46, -39, 22, 46, -36, 14, 47, -32, 4, 47, -27, -4, + 48, -22, -13, 48, -16, -22, 49, -10, -31, 50, -4, -39, + 51, 1, -47, 52, 7, -55, 53, 14, -62, 54, 20, -70, + 47, -48, 50, 47, -47, 46, 47, -46, 40, 48, -44, 33, + 48, -41, 25, 48, -38, 16, 48, -34, 7, 49, -30, -2, + 49, -24, -11, 50, -19, -20, 51, -13, -28, 51, -7, -36, + 52, -1, -44, 53, 4, -52, 54, 11, -60, 55, 17, -67, + 49, -50, 51, 49, -49, 47, 49, -47, 42, 49, -45, 35, + 49, -43, 26, 49, -40, 18, 50, -36, 8, 50, -32, 0, + 51, -26, -9, 51, -21, -18, 52, -15, -26, 53, -10, -35, + 53, -4, -43, 54, 2, -51, 55, 8, -58, 56, 14, -66, + 50, -51, 52, 50, -50, 49, 50, -49, 43, 50, -47, 36, + 51, -44, 28, 51, -42, 20, 51, -38, 10, 52, -34, 2, + 52, -28, -7, 53, -23, -16, 53, -17, -25, 54, -12, -33, + 55, -6, -41, 55, 0, -49, 56, 6, -57, 57, 12, -64, + 51, -52, 53, 52, -51, 50, 52, -50, 44, 52, -48, 38, + 52, -46, 30, 52, -43, 21, 52, -40, 12, 53, -35, 4, + 53, -30, -5, 54, -25, -14, 54, -20, -23, 55, -14, -31, + 56, -8, -39, 57, -2, -47, 57, 3, -55, 58, 9, -62, + 53, -53, 54, 53, -53, 51, 53, -51, 46, 53, -50, 39, + 53, -47, 31, 53, -45, 23, 54, -41, 14, 54, -37, 5, + 55, -32, -3, 55, -27, -12, 56, -22, -21, 56, -16, -29, + 57, -10, -37, 58, -4, -45, 59, 1, -53, 60, 7, -60, + 54, -55, 55, 54, -54, 53, 54, -53, 47, 54, -51, 41, + 55, -49, 33, 55, -46, 25, 55, -43, 16, 55, -39, 7, + 56, -34, -1, 56, -29, -10, 57, -24, -19, 58, -18, -27, + 58, -13, -35, 59, -7, -43, 60, -1, -51, 61, 5, -59, + 56, -56, 56, 56, -55, 54, 56, -54, 49, 56, -52, 42, + 56, -50, 35, 56, -48, 27, 56, -44, 18, 57, -41, 9, + 57, -36, 0, 58, -31, -8, 58, -26, -17, 59, -21, -25, + 59, -15, -33, 60, -9, -41, 61, -3, -49, 62, 2, -57, + 57, -57, 58, 57, -57, 55, 57, -55, 50, 57, -54, 44, + 57, -52, 36, 57, -49, 28, 58, -46, 19, 58, -42, 11, + 58, -38, 2, 59, -33, -6, 59, -28, -15, 60, -23, -23, + 61, -17, -31, 61, -11, -40, 62, -5, -47, 63, 0, -55, + 58, -58, 59, 58, -58, 56, 58, -57, 51, 58, -55, 45, + 59, -53, 38, 59, -51, 30, 59, -48, 21, 59, -44, 13, + 60, -40, 4, 60, -35, -4, 61, -30, -13, 61, -25, -21, + 62, -19, -29, 63, -13, -38, 63, -8, -46, 64, -1, -53, + 60, -60, 60, 60, -59, 57, 60, -58, 53, 60, -56, 47, + 60, -54, 39, 60, -52, 32, 60, -49, 23, 61, -46, 15, + 61, -41, 6, 61, -37, -2, 62, -32, -11, 62, -27, -19, + 63, -21, -28, 64, -16, -36, 64, -10, -44, 65, -4, -51, + 61, -61, 61, 61, -60, 58, 61, -59, 54, 61, -58, 48, + 61, -56, 41, 61, -53, 33, 62, -51, 25, 62, -47, 16, + 62, -43, 8, 63, -39, -1, 63, -34, -9, 64, -29, -18, + 64, -23, -26, 65, -18, -34, 66, -12, -42, 66, -6, -50, + 62, -62, 62, 62, -61, 60, 62, -60, 55, 62, -59, 49, + 62, -57, 42, 63, -55, 35, 63, -52, 26, 63, -49, 18, + 64, -45, 9, 64, -41, 1, 64, -36, -8, 65, -31, -16, + 65, -25, -24, 66, -20, -32, 67, -14, -40, 68, -8, -48, + 63, -63, 63, 64, -63, 61, 64, -62, 56, 64, -60, 51, + 64, -58, 44, 64, -56, 37, 64, -53, 28, 64, -50, 20, + 65, -47, 11, 65, -42, 3, 66, -38, -6, 66, -33, -14, + 67, -27, -22, 67, -22, -30, 68, -16, -38, 69, -10, -46, + 65, -64, 64, 65, -64, 62, 65, -63, 58, 65, -62, 52, + 65, -60, 45, 65, -58, 38, 65, -55, 30, 66, -52, 22, + 66, -48, 13, 66, -44, 5, 67, -39, -4, 67, -35, -12, + 68, -29, -20, 68, -24, -28, 69, -18, -36, 70, -13, -44, + 66, -66, 65, 66, -65, 63, 66, -64, 59, 66, -63, 53, + 66, -61, 47, 67, -59, 40, 67, -56, 31, 67, -53, 23, + 67, -50, 15, 68, -46, 6, 68, -41, -2, 69, -36, -10, + 69, -31, -18, 70, -26, -27, 70, -20, -35, 71, -15, -42, + 67, -67, 66, 67, -66, 64, 67, -65, 60, 68, -64, 55, + 68, -62, 48, 68, -60, 41, 68, -58, 33, 68, -55, 25, + 69, -51, 16, 69, -47, 8, 69, -43, 0, 70, -38, -8, + 70, -33, -16, 71, -28, -25, 71, -22, -33, 72, -17, -41, + 69, -68, 67, 69, -67, 65, 69, -67, 62, 69, -65, 56, + 69, -64, 50, 69, -62, 43, 69, -59, 35, 70, -56, 27, + 70, -53, 18, 70, -49, 10, 71, -45, 1, 71, -40, -7, + 72, -35, -15, 72, -30, -23, 73, -24, -31, 73, -19, -39, + 70, -69, 68, 70, -69, 66, 70, -68, 63, 70, -66, 57, + 70, -65, 51, 70, -63, 44, 71, -60, 36, 71, -58, 29, + 71, -54, 20, 71, -51, 12, 72, -46, 3, 72, -42, -5, + 73, -37, -13, 73, -32, -21, 74, -26, -29, 74, -21, -37, + 71, -70, 69, 71, -70, 67, 71, -69, 64, 71, -68, 59, + 72, -66, 52, 72, -64, 46, 72, -62, 38, 72, -59, 30, + 72, -56, 22, 73, -52, 14, 73, -48, 5, 73, -44, -3, + 74, -39, -11, 74, -34, -19, 75, -28, -27, 76, -23, -35, + 73, -71, 70, 73, -71, 68, 73, -70, 65, 73, -69, 60, + 73, -67, 54, 73, -65, 47, 73, -63, 39, 73, -60, 32, + 74, -57, 23, 74, -54, 15, 74, -50, 7, 75, -45, -1, + 75, -41, -9, 76, -36, -18, 76, -30, -25, 77, -25, -33, + 74, -72, 71, 74, -72, 69, 74, -71, 66, 74, -70, 61, + 74, -69, 55, 74, -67, 49, 74, -64, 41, 75, -62, 33, + 75, -59, 25, 75, -55, 17, 76, -51, 9, 76, -47, 1, + 76, -42, -7, 77, -38, -16, 77, -32, -24, 78, -27, -32, + 75, -74, 73, 75, -73, 71, 75, -73, 68, 76, -72, 63, + 76, -70, 57, 76, -68, 50, 76, -66, 43, 76, -63, 35, + 76, -60, 27, 77, -57, 19, 77, -53, 11, 77, -49, 3, + 78, -45, -5, 78, -40, -14, 79, -35, -21, 79, -30, -29, + 77, -75, 74, 77, -74, 72, 77, -74, 69, 77, -73, 64, + 77, -71, 58, 77, -69, 52, 77, -67, 44, 77, -65, 37, + 78, -62, 29, 78, -59, 21, 78, -55, 12, 79, -51, 4, + 79, -46, -3, 80, -42, -12, 80, -37, -20, 81, -32, -28, + 78, -76, 75, 78, -76, 73, 78, -75, 70, 78, -74, 65, + 78, -72, 59, 78, -71, 53, 78, -69, 46, 79, -66, 39, + 79, -63, 30, 79, -60, 23, 80, -56, 14, 80, -53, 6, + 80, -48, -2, 81, -44, -10, 81, -39, -18, 82, -34, -26, + 79, -77, 76, 79, -77, 74, 79, -76, 71, 79, -75, 66, + 79, -74, 61, 80, -72, 55, 80, -70, 47, 80, -67, 40, + 80, -65, 32, 80, -62, 24, 81, -58, 16, 81, -54, 8, + 81, -50, 0, 82, -45, -8, 82, -40, -16, 83, -35, -24, + 80, -78, 77, 80, -78, 75, 81, -77, 72, 81, -76, 68, + 81, -75, 62, 81, -73, 56, 81, -71, 49, 81, -69, 42, + 81, -66, 34, 82, -63, 26, 82, -59, 18, 82, -56, 10, + 83, -51, 1, 83, -47, -6, 84, -42, -14, 84, -37, -22, + 82, -79, 78, 82, -79, 76, 82, -78, 73, 82, -77, 69, + 82, -76, 63, 82, -74, 57, 82, -72, 50, 82, -70, 43, + 83, -67, 35, 83, -64, 28, 83, -61, 19, 83, -57, 11, + 84, -53, 3, 84, -49, -5, 85, -44, -13, 85, -39, -21, + 83, -80, 79, 83, -80, 77, 83, -79, 74, 83, -78, 70, + 83, -77, 65, 83, -76, 59, 83, -73, 52, 84, -71, 45, + 84, -69, 37, 84, -66, 29, 84, -62, 21, 85, -59, 13, + 85, -55, 5, 85, -50, -3, 86, -46, -11, 86, -41, -19, + 84, -81, 80, 84, -81, 78, 84, -80, 76, 84, -80, 71, + 84, -78, 66, 85, -77, 60, 85, -75, 53, 85, -73, 46, + 85, -70, 38, 85, -67, 31, 86, -64, 23, 86, -60, 15, + 86, -56, 7, 87, -52, -1, 87, -47, -9, 88, -43, -17, + 85, -83, 81, 85, -82, 79, 85, -82, 77, 86, -81, 73, + 86, -79, 67, 86, -78, 61, 86, -76, 55, 86, -74, 48, + 86, -71, 40, 86, -69, 32, 87, -65, 24, 87, -62, 16, + 87, -58, 8, 88, -54, 0, 88, -49, -7, 89, -45, -15, + 87, -84, 82, 87, -83, 80, 87, -83, 78, 87, -82, 74, + 87, -81, 69, 87, -79, 63, 87, -77, 56, 87, -75, 49, + 87, -73, 41, 88, -70, 34, 88, -67, 26, 88, -63, 18, + 89, -59, 10, 89, -55, 2, 89, -51, -6, 90, -46, -14, + 88, -85, 83, 88, -84, 81, 88, -84, 79, 88, -83, 75, + 88, -82, 70, 88, -80, 64, 88, -78, 57, 88, -76, 51, + 89, -74, 43, 89, -71, 36, 89, -68, 27, 89, -65, 20, + 90, -61, 12, 90, -57, 4, 91, -53, -4, 91, -48, -12, + 4, 19, 5, 5, 22, -2, 5, 25, -14, 6, 30, -24, + 8, 34, -34, 10, 38, -42, 12, 42, -49, 14, 46, -56, + 16, 51, -63, 19, 55, -69, 21, 59, -76, 23, 63, -82, + 26, 67, -87, 28, 71, -93, 30, 75, -99, 32, 79, -104, + 5, 17, 6, 5, 20, -1, 6, 23, -12, 7, 27, -23, + 9, 32, -32, 10, 36, -41, 12, 40, -48, 14, 45, -55, + 17, 49, -62, 19, 53, -69, 21, 58, -75, 23, 62, -81, + 26, 66, -87, 28, 70, -93, 30, 74, -98, 33, 78, -104, + 6, 16, 7, 6, 18, 0, 7, 21, -11, 8, 25, -22, + 9, 30, -31, 11, 34, -40, 13, 39, -48, 15, 43, -55, + 17, 48, -62, 19, 52, -68, 22, 57, -75, 24, 61, -81, + 26, 65, -87, 28, 69, -92, 31, 73, -98, 33, 77, -104, + 7, 14, 8, 7, 16, 1, 8, 19, -10, 9, 23, -20, + 10, 28, -30, 12, 32, -39, 13, 37, -47, 15, 42, -54, + 17, 47, -61, 20, 51, -67, 22, 56, -74, 24, 60, -80, + 26, 64, -86, 28, 69, -92, 31, 73, -98, 33, 77, -103, + 8, 11, 10, 8, 13, 2, 9, 17, -8, 10, 21, -19, + 11, 25, -29, 12, 30, -37, 14, 35, -46, 16, 40, -53, + 18, 45, -60, 20, 50, -67, 22, 55, -73, 24, 59, -79, + 27, 63, -86, 29, 68, -92, 31, 72, -97, 33, 76, -103, + 9, 9, 11, 9, 11, 4, 10, 14, -7, 11, 18, -18, + 12, 23, -27, 13, 28, -36, 15, 33, -45, 17, 38, -52, + 19, 44, -59, 20, 48, -66, 23, 53, -73, 25, 58, -79, + 27, 62, -85, 29, 67, -91, 31, 71, -97, 33, 75, -103, + 10, 6, 13, 10, 8, 6, 11, 11, -5, 12, 15, -16, + 13, 20, -26, 14, 25, -35, 16, 31, -43, 17, 36, -51, + 19, 42, -58, 21, 47, -65, 23, 52, -72, 25, 56, -78, + 27, 61, -84, 29, 66, -90, 32, 70, -96, 34, 74, -102, + 11, 3, 15, 12, 5, 7, 12, 8, -3, 13, 12, -14, + 14, 17, -24, 15, 23, -33, 17, 28, -42, 18, 34, -50, + 20, 40, -57, 22, 45, -64, 24, 50, -71, 26, 55, -77, + 28, 59, -84, 30, 64, -90, 32, 69, -96, 34, 73, -101, + 13, 0, 17, 13, 2, 9, 13, 5, -1, 14, 9, -12, + 15, 15, -22, 16, 20, -31, 17, 26, -40, 19, 31, -48, + 21, 37, -56, 22, 43, -63, 24, 48, -70, 26, 53, -76, + 28, 58, -83, 30, 63, -89, 32, 67, -95, 34, 72, -101, + 14, -2, 19, 14, 0, 11, 15, 3, 0, 15, 7, -10, + 16, 12, -20, 17, 17, -29, 19, 23, -39, 20, 29, -47, + 22, 35, -55, 23, 40, -62, 25, 46, -69, 27, 51, -75, + 29, 56, -82, 31, 61, -88, 33, 66, -94, 35, 70, -100, + 15, -5, 20, 16, -3, 13, 16, 0, 2, 17, 4, -8, + 17, 9, -18, 18, 14, -28, 20, 20, -37, 21, 26, -45, + 22, 32, -53, 24, 38, -60, 26, 44, -68, 28, 49, -74, + 30, 54, -81, 31, 59, -87, 33, 64, -93, 35, 69, -99, + 17, -8, 23, 17, -6, 15, 18, -3, 4, 18, 0, -6, + 19, 5, -16, 20, 11, -25, 21, 17, -35, 22, 23, -43, + 24, 29, -51, 25, 35, -58, 27, 41, -66, 29, 46, -73, + 30, 52, -79, 32, 57, -86, 34, 62, -92, 36, 67, -98, + 19, -10, 24, 19, -9, 17, 19, -6, 6, 20, -2, -4, + 20, 3, -14, 21, 8, -23, 22, 14, -33, 23, 20, -41, + 25, 26, -50, 26, 32, -57, 28, 38, -64, 29, 44, -71, + 31, 49, -78, 33, 55, -85, 35, 60, -91, 37, 65, -97, + 20, -13, 26, 20, -11, 18, 21, -9, 8, 21, -5, -2, + 22, 0, -12, 22, 5, -21, 23, 11, -31, 24, 17, -39, + 26, 24, -48, 27, 30, -55, 29, 36, -63, 30, 42, -70, + 32, 47, -77, 34, 53, -83, 35, 58, -90, 37, 63, -96, + 22, -15, 28, 22, -13, 20, 22, -11, 10, 22, -7, 0, + 23, -2, -10, 24, 2, -19, 25, 8, -29, 26, 15, -37, + 27, 21, -46, 28, 27, -54, 30, 33, -61, 31, 39, -68, + 33, 45, -76, 35, 51, -82, 36, 56, -89, 38, 61, -95, + 23, -17, 29, 23, -16, 22, 23, -13, 12, 24, -10, 2, + 24, -5, -8, 25, 0, -17, 26, 6, -27, 27, 12, -36, + 28, 18, -44, 29, 24, -52, 31, 31, -60, 32, 37, -67, + 34, 43, -74, 35, 48, -81, 37, 54, -87, 39, 59, -94, + 24, -19, 31, 25, -18, 23, 25, -16, 14, 25, -12, 4, + 26, -8, -6, 26, -2, -15, 27, 3, -25, 28, 9, -34, + 29, 16, -42, 30, 22, -50, 32, 28, -58, 33, 34, -65, + 35, 40, -73, 36, 46, -79, 38, 51, -86, 40, 57, -92, + 26, -22, 32, 26, -20, 25, 26, -18, 16, 27, -15, 6, + 27, -10, -4, 28, -5, -13, 28, 0, -23, 29, 6, -32, + 30, 13, -41, 31, 19, -49, 33, 25, -57, 34, 31, -64, + 36, 38, -71, 37, 43, -78, 39, 49, -85, 40, 55, -91, + 27, -23, 33, 28, -22, 27, 28, -20, 18, 28, -17, 8, + 28, -12, -2, 29, -7, -11, 30, -1, -21, 31, 4, -30, + 32, 10, -39, 33, 16, -47, 34, 23, -55, 35, 29, -62, + 36, 35, -70, 38, 41, -76, 39, 47, -83, 41, 53, -90, + 29, -25, 34, 29, -24, 28, 29, -22, 19, 29, -19, 10, + 30, -15, 0, 30, -10, -9, 31, -4, -19, 32, 1, -28, + 33, 7, -37, 34, 14, -45, 35, 20, -53, 36, 26, -60, + 37, 33, -68, 39, 38, -75, 40, 44, -82, 42, 50, -88, + 30, -27, 36, 30, -26, 30, 31, -24, 21, 31, -21, 12, + 31, -17, 2, 32, -12, -7, 32, -7, -17, 33, -1, -26, + 34, 5, -35, 35, 11, -43, 36, 17, -51, 37, 24, -59, + 39, 30, -66, 40, 36, -73, 41, 42, -81, 43, 48, -87, + 32, -29, 37, 32, -28, 31, 32, -26, 23, 32, -23, 13, + 33, -19, 4, 33, -15, -5, 34, -9, -15, 34, -3, -24, + 35, 2, -33, 36, 8, -41, 37, 15, -49, 38, 21, -57, + 40, 27, -65, 41, 33, -72, 42, 40, -79, 44, 45, -86, + 33, -31, 38, 33, -30, 33, 33, -28, 25, 34, -25, 15, + 34, -21, 6, 34, -17, -3, 35, -11, -13, 36, -6, -22, + 37, 0, -31, 37, 6, -39, 38, 12, -48, 40, 18, -55, + 41, 25, -63, 42, 31, -70, 43, 37, -77, 45, 43, -84, + 35, -33, 40, 35, -31, 34, 35, -29, 26, 35, -27, 17, + 35, -24, 8, 36, -19, -1, 36, -14, -11, 37, -8, -20, + 38, -2, -29, 39, 3, -37, 40, 10, -46, 41, 16, -53, + 42, 22, -61, 43, 28, -68, 44, 35, -76, 46, 40, -83, + 36, -34, 41, 36, -33, 35, 36, -31, 28, 36, -29, 19, + 37, -25, 10, 37, -21, 0, 38, -16, -9, 38, -11, -18, + 39, -5, -27, 40, 0, -35, 41, 7, -44, 42, 13, -52, + 43, 20, -59, 44, 26, -67, 45, 32, -74, 47, 38, -81, + 37, -36, 42, 38, -35, 37, 38, -33, 30, 38, -31, 21, + 38, -27, 12, 39, -23, 2, 39, -18, -7, 40, -13, -16, + 40, -7, -25, 41, -1, -33, 42, 4, -42, 43, 11, -50, + 44, 17, -58, 45, 23, -65, 46, 29, -73, 48, 35, -79, + 39, -37, 43, 39, -36, 38, 39, -35, 31, 39, -32, 22, + 40, -29, 13, 40, -25, 4, 40, -20, -5, 41, -15, -14, + 42, -9, -23, 42, -4, -31, 43, 2, -40, 44, 8, -48, + 45, 14, -56, 46, 21, -63, 47, 27, -71, 49, 33, -78, + 40, -39, 44, 40, -38, 39, 40, -36, 33, 41, -34, 24, + 41, -31, 15, 41, -27, 6, 42, -23, -3, 42, -18, -12, + 43, -12, -21, 44, -6, -29, 44, 0, -38, 45, 5, -46, + 46, 12, -54, 47, 18, -62, 48, 24, -69, 50, 30, -76, + 42, -40, 46, 42, -39, 41, 42, -38, 34, 42, -36, 26, + 42, -33, 17, 43, -29, 8, 43, -25, -1, 44, -20, -10, + 44, -14, -19, 45, -8, -27, 46, -2, -36, 46, 3, -44, + 47, 9, -52, 48, 15, -60, 49, 22, -67, 51, 28, -75, + 43, -42, 47, 43, -41, 42, 43, -39, 36, 43, -37, 28, + 44, -35, 19, 44, -31, 10, 44, -27, 1, 45, -22, -8, + 45, -16, -17, 46, -11, -26, 47, -5, -34, 48, 0, -42, + 49, 7, -50, 50, 13, -58, 51, 19, -66, 52, 25, -73, + 44, -43, 48, 44, -42, 43, 45, -41, 37, 45, -39, 29, + 45, -36, 21, 45, -33, 12, 46, -29, 3, 46, -24, -6, + 47, -18, -15, 47, -13, -24, 48, -7, -32, 49, -1, -40, + 50, 4, -48, 51, 10, -56, 52, 17, -64, 53, 23, -71, + 46, -45, 49, 46, -44, 45, 46, -43, 39, 46, -41, 31, + 46, -38, 23, 47, -35, 14, 47, -31, 4, 47, -26, -4, + 48, -21, -13, 49, -15, -22, 49, -9, -30, 50, -3, -39, + 51, 2, -46, 52, 8, -54, 53, 14, -62, 54, 20, -69, + 48, -47, 50, 48, -46, 46, 48, -44, 40, 48, -42, 33, + 48, -40, 25, 48, -37, 16, 49, -33, 7, 49, -29, -2, + 50, -23, -11, 50, -18, -19, 51, -12, -28, 52, -6, -36, + 52, 0, -44, 53, 5, -52, 54, 11, -60, 55, 17, -67, + 49, -48, 51, 49, -47, 48, 49, -46, 42, 49, -44, 35, + 49, -42, 27, 50, -39, 18, 50, -35, 9, 50, -30, 0, + 51, -25, -9, 51, -20, -17, 52, -14, -26, 53, -9, -34, + 54, -3, -42, 54, 2, -50, 55, 9, -58, 56, 15, -66, + 50, -49, 52, 50, -49, 49, 50, -47, 43, 51, -45, 36, + 51, -43, 28, 51, -40, 20, 51, -37, 11, 52, -32, 2, + 52, -27, -7, 53, -22, -15, 53, -17, -24, 54, -11, -32, + 55, -5, -40, 56, 0, -48, 56, 6, -56, 57, 12, -64, + 52, -51, 53, 52, -50, 50, 52, -49, 45, 52, -47, 38, + 52, -45, 30, 52, -42, 22, 53, -38, 12, 53, -34, 4, + 53, -29, -5, 54, -24, -14, 55, -19, -22, 55, -13, -31, + 56, -7, -39, 57, -1, -47, 58, 4, -54, 59, 10, -62, + 53, -52, 55, 53, -51, 51, 53, -50, 46, 53, -48, 40, + 53, -46, 32, 54, -43, 23, 54, -40, 14, 54, -36, 6, + 55, -31, -3, 55, -26, -12, 56, -21, -20, 56, -15, -29, + 57, -10, -37, 58, -4, -45, 59, 2, -53, 60, 8, -60, + 54, -53, 56, 54, -53, 53, 54, -51, 47, 55, -50, 41, + 55, -48, 33, 55, -45, 25, 55, -42, 16, 56, -38, 8, + 56, -33, -1, 57, -28, -10, 57, -23, -19, 58, -18, -27, + 58, -12, -35, 59, -6, -43, 60, 0, -51, 61, 5, -58, + 56, -55, 57, 56, -54, 54, 56, -53, 49, 56, -51, 43, + 56, -49, 35, 56, -47, 27, 57, -43, 18, 57, -40, 9, + 57, -35, 0, 58, -30, -8, 58, -25, -17, 59, -20, -25, + 60, -14, -33, 60, -8, -41, 61, -2, -49, 62, 3, -57, + 57, -56, 58, 57, -55, 55, 57, -54, 50, 57, -52, 44, + 57, -50, 36, 58, -48, 29, 58, -45, 20, 58, -41, 11, + 59, -37, 2, 59, -32, -6, 60, -27, -15, 60, -22, -23, + 61, -16, -31, 61, -11, -39, 62, -5, -47, 63, 0, -55, + 58, -57, 59, 58, -57, 56, 58, -56, 51, 59, -54, 45, + 59, -52, 38, 59, -50, 30, 59, -46, 21, 59, -43, 13, + 60, -39, 4, 60, -34, -4, 61, -29, -13, 61, -24, -21, + 62, -18, -29, 63, -13, -37, 63, -7, -45, 64, -1, -53, + 60, -58, 60, 60, -58, 57, 60, -57, 53, 60, -55, 47, + 60, -53, 40, 60, -51, 32, 60, -48, 23, 61, -45, 15, + 61, -41, 6, 62, -36, -2, 62, -31, -11, 63, -26, -19, + 63, -20, -27, 64, -15, -36, 65, -9, -43, 65, -3, -51, + 61, -60, 61, 61, -59, 59, 61, -58, 54, 61, -57, 48, + 61, -55, 41, 62, -52, 34, 62, -49, 25, 62, -46, 17, + 62, -42, 8, 63, -38, 0, 63, -33, -9, 64, -28, -17, + 64, -23, -25, 65, -17, -34, 66, -11, -42, 66, -6, -49, + 62, -61, 62, 62, -60, 60, 62, -59, 55, 62, -58, 50, + 63, -56, 43, 63, -54, 35, 63, -51, 27, 63, -48, 18, + 64, -44, 10, 64, -40, 1, 65, -35, -7, 65, -30, -16, + 66, -25, -24, 66, -19, -32, 67, -13, -40, 68, -8, -48, + 64, -62, 63, 64, -62, 61, 64, -61, 57, 64, -59, 51, + 64, -57, 44, 64, -55, 37, 64, -52, 28, 65, -49, 20, + 65, -46, 11, 65, -42, 3, 66, -37, -6, 66, -32, -14, + 67, -27, -22, 67, -21, -30, 68, -16, -38, 69, -10, -46, + 65, -63, 64, 65, -63, 62, 65, -62, 58, 65, -60, 52, + 65, -59, 45, 65, -57, 38, 66, -54, 30, 66, -51, 22, + 66, -47, 13, 67, -43, 5, 67, -39, -4, 67, -34, -12, + 68, -29, -20, 69, -23, -28, 69, -18, -36, 70, -12, -44, + 66, -65, 65, 66, -64, 63, 66, -63, 59, 66, -62, 54, + 67, -60, 47, 67, -58, 40, 67, -55, 32, 67, -52, 24, + 67, -49, 15, 68, -45, 7, 68, -40, -2, 69, -36, -10, + 69, -31, -18, 70, -25, -26, 70, -20, -34, 71, -14, -42, + 68, -66, 66, 68, -65, 64, 68, -64, 60, 68, -63, 55, + 68, -61, 48, 68, -59, 41, 68, -57, 33, 68, -54, 25, + 69, -50, 17, 69, -47, 8, 69, -42, 0, 70, -38, -8, + 70, -33, -16, 71, -27, -25, 72, -22, -33, 72, -16, -40, + 69, -67, 67, 69, -66, 65, 69, -66, 62, 69, -64, 56, + 69, -63, 50, 69, -61, 43, 69, -58, 35, 70, -55, 27, + 70, -52, 18, 70, -48, 10, 71, -44, 2, 71, -39, -6, + 72, -34, -14, 72, -29, -23, 73, -24, -31, 73, -19, -39, + 70, -68, 68, 70, -68, 66, 70, -67, 63, 70, -66, 57, + 70, -64, 51, 71, -62, 44, 71, -60, 36, 71, -57, 29, + 71, -53, 20, 72, -50, 12, 72, -46, 3, 72, -41, -5, + 73, -36, -13, 73, -31, -21, 74, -26, -29, 75, -21, -37, + 71, -69, 69, 71, -69, 67, 71, -68, 64, 72, -67, 59, + 72, -65, 53, 72, -63, 46, 72, -61, 38, 72, -58, 30, + 72, -55, 22, 73, -52, 14, 73, -47, 5, 74, -43, -3, + 74, -38, -11, 75, -33, -19, 75, -28, -27, 76, -23, -35, + 73, -70, 70, 73, -70, 68, 73, -69, 65, 73, -68, 60, + 73, -66, 54, 73, -65, 47, 73, -62, 40, 73, -60, 32, + 74, -56, 24, 74, -53, 15, 74, -49, 7, 75, -45, -1, + 75, -40, -9, 76, -35, -17, 76, -30, -25, 77, -25, -33, + 74, -72, 71, 74, -71, 70, 74, -70, 66, 74, -69, 61, + 74, -68, 55, 74, -66, 49, 74, -64, 41, 75, -61, 34, + 75, -58, 25, 75, -55, 17, 76, -51, 9, 76, -47, 1, + 76, -42, -7, 77, -37, -16, 77, -32, -24, 78, -27, -32, + 75, -73, 73, 76, -73, 71, 76, -72, 68, 76, -71, 63, + 76, -69, 57, 76, -67, 51, 76, -65, 43, 76, -63, 36, + 77, -60, 27, 77, -56, 19, 77, -53, 11, 78, -49, 3, + 78, -44, -5, 78, -39, -13, 79, -34, -21, 79, -29, -29, + 77, -74, 74, 77, -74, 72, 77, -73, 69, 77, -72, 64, + 77, -70, 58, 77, -69, 52, 77, -67, 45, 78, -64, 37, + 78, -61, 29, 78, -58, 21, 78, -54, 13, 79, -50, 5, + 79, -46, -3, 80, -41, -12, 80, -36, -20, 81, -31, -28, + 78, -75, 75, 78, -75, 73, 78, -74, 70, 78, -73, 65, + 78, -72, 60, 78, -70, 53, 79, -68, 46, 79, -65, 39, + 79, -63, 31, 79, -59, 23, 80, -56, 14, 80, -52, 6, + 80, -47, -2, 81, -43, -10, 81, -38, -18, 82, -33, -26, + 79, -76, 76, 79, -76, 74, 79, -75, 71, 79, -74, 67, + 80, -73, 61, 80, -71, 55, 80, -69, 47, 80, -67, 40, + 80, -64, 32, 80, -61, 24, 81, -57, 16, 81, -54, 8, + 82, -49, 0, 82, -45, -8, 82, -40, -16, 83, -35, -24, + 81, -77, 77, 81, -77, 75, 81, -76, 72, 81, -75, 68, + 81, -74, 62, 81, -72, 56, 81, -70, 49, 81, -68, 42, + 81, -65, 34, 82, -62, 26, 82, -59, 18, 82, -55, 10, + 83, -51, 1, 83, -47, -6, 84, -42, -14, 84, -37, -22, + 82, -79, 78, 82, -78, 76, 82, -78, 73, 82, -77, 69, + 82, -75, 64, 82, -74, 57, 82, -72, 50, 82, -69, 43, + 83, -67, 35, 83, -64, 28, 83, -60, 19, 84, -57, 11, + 84, -53, 3, 84, -48, -5, 85, -43, -12, 85, -39, -20, + 83, -80, 79, 83, -79, 77, 83, -79, 75, 83, -78, 70, + 83, -76, 65, 83, -75, 59, 83, -73, 52, 84, -71, 45, + 84, -68, 37, 84, -65, 29, 84, -62, 21, 85, -58, 13, + 85, -54, 5, 86, -50, -3, 86, -45, -11, 86, -41, -19, + 84, -81, 80, 84, -80, 78, 84, -80, 76, 84, -79, 71, + 84, -78, 66, 85, -76, 60, 85, -74, 53, 85, -72, 46, + 85, -69, 38, 85, -67, 31, 86, -63, 23, 86, -60, 15, + 86, -56, 7, 87, -52, -1, 87, -47, -9, 88, -42, -17, + 86, -82, 81, 86, -82, 79, 86, -81, 77, 86, -80, 73, + 86, -79, 67, 86, -77, 61, 86, -75, 55, 86, -73, 48, + 86, -71, 40, 87, -68, 32, 87, -65, 24, 87, -61, 17, + 87, -57, 8, 88, -53, 0, 88, -49, -7, 89, -44, -15, + 87, -83, 82, 87, -83, 80, 87, -82, 78, 87, -81, 74, + 87, -80, 69, 87, -78, 63, 87, -77, 56, 87, -74, 49, + 88, -72, 42, 88, -69, 34, 88, -66, 26, 88, -63, 18, + 89, -59, 10, 89, -55, 2, 89, -50, -6, 90, -46, -14, + 88, -84, 83, 88, -84, 81, 88, -83, 79, 88, -82, 75, + 88, -81, 70, 88, -80, 64, 88, -78, 57, 89, -76, 51, + 89, -73, 43, 89, -71, 36, 89, -67, 28, 90, -64, 20, + 90, -61, 12, 90, -57, 4, 91, -52, -4, 91, -48, -12, + 6, 24, 7, 6, 26, 0, 7, 28, -11, 8, 32, -21, + 9, 35, -31, 11, 39, -39, 13, 43, -47, 15, 47, -55, + 17, 51, -62, 19, 55, -68, 22, 59, -75, 24, 63, -81, + 26, 67, -87, 28, 71, -92, 31, 75, -98, 33, 79, -104, + 7, 22, 8, 7, 24, 1, 8, 26, -10, 9, 29, -20, + 10, 33, -30, 12, 37, -38, 13, 41, -47, 15, 45, -54, + 18, 50, -61, 20, 54, -67, 22, 58, -74, 24, 62, -80, + 26, 66, -86, 28, 70, -92, 31, 74, -98, 33, 78, -103, + 7, 20, 10, 8, 21, 2, 8, 24, -9, 9, 27, -19, + 11, 31, -29, 12, 35, -38, 14, 40, -46, 16, 44, -53, + 18, 49, -60, 20, 53, -67, 22, 57, -74, 24, 61, -80, + 27, 65, -86, 29, 70, -92, 31, 74, -97, 33, 78, -103, + 8, 18, 11, 9, 19, 3, 9, 22, -7, 10, 25, -18, + 11, 29, -28, 13, 34, -37, 14, 38, -45, 16, 43, -52, + 18, 47, -60, 20, 52, -66, 22, 56, -73, 25, 60, -79, + 27, 65, -85, 29, 69, -91, 31, 73, -97, 33, 77, -103, + 9, 16, 12, 9, 17, 5, 10, 20, -6, 11, 23, -17, + 12, 27, -27, 13, 32, -36, 15, 37, -44, 17, 41, -52, + 19, 46, -59, 21, 50, -66, 23, 55, -72, 25, 59, -79, + 27, 64, -85, 29, 68, -91, 31, 72, -97, 33, 76, -102, + 10, 13, 14, 10, 14, 6, 11, 17, -5, 12, 21, -16, + 13, 25, -25, 14, 30, -34, 16, 35, -43, 17, 39, -51, + 19, 44, -58, 21, 49, -65, 23, 54, -72, 25, 58, -78, + 27, 63, -84, 29, 67, -90, 32, 71, -96, 34, 75, -102, + 11, 10, 15, 12, 12, 8, 12, 15, -3, 13, 18, -14, + 14, 23, -24, 15, 27, -33, 17, 32, -42, 18, 37, -49, + 20, 42, -57, 22, 47, -64, 24, 52, -71, 26, 57, -77, + 28, 61, -84, 30, 66, -90, 32, 70, -96, 34, 75, -101, + 13, 7, 17, 13, 9, 9, 13, 12, -1, 14, 16, -12, + 15, 20, -22, 16, 25, -31, 17, 30, -40, 19, 35, -48, + 21, 40, -56, 22, 45, -63, 24, 51, -70, 26, 55, -76, + 28, 60, -83, 30, 65, -89, 32, 69, -95, 34, 73, -101, + 14, 5, 18, 14, 6, 11, 14, 9, 0, 15, 13, -11, + 16, 17, -21, 17, 22, -30, 18, 28, -39, 20, 33, -47, + 21, 38, -55, 23, 43, -62, 25, 49, -69, 27, 54, -75, + 29, 58, -82, 31, 63, -88, 33, 68, -94, 35, 72, -100, + 15, 2, 20, 15, 3, 12, 16, 6, 1, 16, 10, -9, + 17, 15, -19, 18, 19, -28, 19, 25, -37, 21, 30, -46, + 22, 36, -54, 24, 41, -61, 26, 47, -68, 27, 52, -74, + 29, 57, -81, 31, 61, -87, 33, 66, -93, 35, 71, -99, + 16, 0, 22, 17, 1, 14, 17, 3, 3, 17, 7, -7, + 18, 12, -17, 19, 17, -26, 20, 22, -36, 22, 28, -44, + 23, 34, -52, 25, 39, -59, 26, 45, -67, 28, 50, -73, + 30, 55, -80, 32, 60, -86, 34, 64, -93, 36, 69, -99, + 18, -4, 24, 18, -2, 16, 19, 0, 5, 19, 4, -5, + 20, 8, -15, 21, 13, -24, 22, 19, -34, 23, 24, -42, + 24, 30, -50, 26, 36, -58, 27, 42, -65, 29, 47, -72, + 31, 52, -79, 33, 57, -85, 35, 62, -91, 36, 67, -97, + 19, -6, 25, 20, -5, 18, 20, -2, 7, 20, 1, -3, + 21, 5, -13, 22, 10, -22, 23, 16, -32, 24, 22, -40, + 25, 28, -49, 27, 33, -56, 28, 39, -64, 30, 45, -71, + 32, 50, -78, 33, 55, -84, 35, 60, -90, 37, 65, -96, + 21, -9, 27, 21, -7, 19, 21, -5, 9, 22, -1, -1, + 22, 3, -11, 23, 8, -20, 24, 13, -30, 25, 19, -38, + 26, 25, -47, 28, 31, -55, 29, 37, -62, 31, 42, -69, + 32, 48, -76, 34, 53, -83, 36, 58, -89, 38, 64, -95, + 22, -11, 29, 22, -10, 21, 23, -7, 11, 23, -4, 1, + 24, 0, -9, 24, 5, -18, 25, 11, -28, 26, 16, -37, + 27, 22, -45, 29, 28, -53, 30, 34, -61, 32, 40, -68, + 33, 45, -75, 35, 51, -81, 37, 56, -88, 38, 62, -94, + 24, -13, 30, 24, -12, 22, 24, -10, 13, 24, -7, 3, + 25, -2, -7, 26, 2, -16, 26, 8, -26, 27, 13, -35, + 29, 20, -44, 30, 26, -51, 31, 32, -59, 33, 37, -66, + 34, 43, -74, 36, 49, -80, 37, 54, -87, 39, 60, -93, + 25, -16, 31, 25, -14, 24, 25, -12, 15, 26, -9, 5, + 26, -5, -5, 27, 0, -14, 28, 5, -24, 29, 11, -33, + 30, 17, -42, 31, 23, -50, 32, 29, -58, 33, 35, -65, + 35, 41, -72, 37, 47, -79, 38, 52, -86, 40, 57, -92, + 26, -18, 33, 27, -16, 26, 27, -14, 16, 27, -12, 7, + 28, -7, -3, 28, -3, -12, 29, 2, -22, 30, 8, -31, + 31, 14, -40, 32, 20, -48, 33, 26, -56, 34, 32, -63, + 36, 38, -71, 37, 44, -77, 39, 50, -84, 41, 55, -91, + 28, -20, 34, 28, -19, 27, 28, -17, 18, 28, -14, 8, + 29, -10, -1, 29, -5, -10, 30, 0, -20, 31, 5, -29, + 32, 12, -38, 33, 17, -46, 34, 24, -54, 36, 30, -62, + 37, 36, -69, 38, 42, -76, 40, 47, -83, 41, 53, -89, + 29, -22, 35, 29, -21, 29, 30, -19, 20, 30, -16, 10, + 30, -12, 0, 31, -8, -8, 31, -2, -18, 32, 3, -27, + 33, 9, -36, 34, 15, -44, 35, 21, -52, 37, 27, -60, + 38, 33, -68, 39, 39, -74, 41, 45, -82, 42, 51, -88, + 31, -24, 36, 31, -23, 30, 31, -21, 22, 31, -18, 12, + 32, -15, 2, 32, -10, -6, 33, -5, -16, 34, 0, -25, + 34, 6, -34, 35, 12, -42, 36, 19, -51, 38, 25, -58, + 39, 31, -66, 40, 37, -73, 42, 43, -80, 43, 48, -87, + 32, -26, 38, 32, -25, 32, 32, -23, 24, 33, -20, 14, + 33, -17, 4, 33, -12, -4, 34, -7, -14, 35, -2, -23, + 36, 4, -32, 37, 10, -41, 38, 16, -49, 39, 22, -56, + 40, 28, -64, 41, 34, -71, 43, 40, -79, 44, 46, -85, + 34, -28, 39, 34, -27, 33, 34, -25, 25, 34, -22, 16, + 34, -19, 6, 35, -15, -2, 35, -10, -12, 36, -4, -21, + 37, 1, -30, 38, 7, -39, 39, 13, -47, 40, 19, -55, + 41, 26, -62, 42, 32, -70, 44, 38, -77, 45, 43, -84, + 35, -30, 40, 35, -29, 35, 35, -27, 27, 35, -24, 18, + 36, -21, 8, 36, -17, 0, 37, -12, -10, 37, -7, -19, + 38, -1, -28, 39, 4, -37, 40, 11, -45, 41, 17, -53, + 42, 23, -61, 43, 29, -68, 45, 35, -75, 46, 41, -82, + 36, -31, 41, 36, -30, 36, 37, -29, 29, 37, -26, 19, + 37, -23, 10, 38, -19, 1, 38, -14, -8, 39, -9, -17, + 39, -3, -26, 40, 2, -35, 41, 8, -43, 42, 14, -51, + 43, 20, -59, 44, 26, -66, 46, 33, -74, 47, 39, -81, + 38, -33, 42, 38, -32, 37, 38, -30, 30, 38, -28, 21, + 38, -25, 12, 39, -21, 3, 39, -16, -6, 40, -11, -15, + 41, -6, -24, 41, 0, -33, 42, 6, -42, 43, 11, -49, + 44, 18, -57, 45, 24, -65, 47, 30, -72, 48, 36, -79, + 39, -35, 44, 39, -34, 38, 39, -32, 32, 40, -30, 23, + 40, -27, 14, 40, -24, 5, 41, -19, -4, 41, -14, -13, + 42, -8, -22, 43, -2, -31, 43, 3, -40, 44, 9, -48, + 45, 15, -55, 46, 21, -63, 48, 28, -71, 49, 34, -77, + 41, -37, 45, 41, -36, 40, 41, -34, 33, 41, -32, 25, + 41, -29, 16, 42, -26, 7, 42, -21, -2, 43, -16, -11, + 43, -10, -21, 44, -5, -29, 45, 1, -38, 46, 6, -46, + 46, 13, -54, 48, 19, -61, 49, 25, -69, 50, 31, -76, + 42, -38, 46, 42, -37, 41, 42, -36, 35, 42, -34, 26, + 43, -31, 18, 43, -27, 9, 43, -23, 0, 44, -18, -9, + 44, -13, -19, 45, -7, -27, 46, -1, -36, 47, 4, -44, + 48, 10, -52, 49, 16, -59, 50, 23, -67, 51, 29, -74, + 43, -40, 47, 43, -39, 42, 43, -37, 36, 44, -35, 28, + 44, -33, 19, 44, -29, 11, 45, -25, 1, 45, -20, -7, + 46, -15, -17, 46, -9, -25, 47, -3, -34, 48, 1, -42, + 49, 8, -50, 50, 14, -58, 51, 20, -65, 52, 26, -72, + 45, -41, 48, 45, -40, 44, 45, -39, 38, 45, -37, 30, + 45, -34, 21, 46, -31, 12, 46, -27, 3, 46, -22, -5, + 47, -17, -15, 48, -12, -23, 48, -6, -32, 49, 0, -40, + 50, 5, -48, 51, 11, -56, 52, 18, -64, 53, 24, -71, + 46, -43, 49, 46, -42, 45, 46, -41, 39, 46, -39, 31, + 47, -36, 23, 47, -33, 14, 47, -29, 5, 48, -25, -4, + 48, -19, -13, 49, -14, -21, 50, -8, -30, 50, -2, -38, + 51, 3, -46, 52, 9, -54, 53, 15, -62, 54, 21, -69, + 48, -45, 50, 48, -44, 47, 48, -43, 41, 48, -41, 34, + 48, -38, 25, 49, -35, 17, 49, -31, 7, 49, -27, -1, + 50, -22, -10, 50, -17, -19, 51, -11, -28, 52, -5, -36, + 53, 0, -44, 53, 6, -52, 54, 12, -60, 55, 18, -67, + 49, -46, 52, 49, -45, 48, 49, -44, 42, 49, -42, 35, + 50, -40, 27, 50, -37, 18, 50, -33, 9, 51, -29, 0, + 51, -24, -8, 52, -19, -17, 52, -13, -26, 53, -8, -34, + 54, -2, -42, 55, 3, -50, 55, 9, -58, 57, 15, -65, + 50, -48, 53, 51, -47, 49, 51, -46, 44, 51, -44, 37, + 51, -41, 29, 51, -39, 20, 52, -35, 11, 52, -31, 2, + 52, -26, -7, 53, -21, -15, 54, -16, -24, 54, -10, -32, + 55, -4, -40, 56, 1, -48, 57, 7, -56, 58, 13, -63, + 52, -49, 54, 52, -48, 50, 52, -47, 45, 52, -45, 38, + 52, -43, 30, 53, -40, 22, 53, -37, 13, 53, -33, 4, + 54, -28, -5, 54, -23, -13, 55, -18, -22, 55, -12, -30, + 56, -6, -38, 57, -1, -46, 58, 5, -54, 59, 11, -62, + 53, -50, 55, 53, -50, 52, 53, -48, 46, 53, -47, 40, + 54, -45, 32, 54, -42, 24, 54, -39, 15, 54, -35, 6, + 55, -30, -3, 55, -25, -11, 56, -20, -20, 57, -15, -28, + 57, -9, -36, 58, -3, -45, 59, 2, -52, 60, 8, -60, + 55, -52, 56, 55, -51, 53, 55, -50, 48, 55, -48, 41, + 55, -46, 34, 55, -44, 25, 55, -40, 16, 56, -37, 8, + 56, -32, -1, 57, -27, -9, 57, -22, -18, 58, -17, -26, + 59, -11, -35, 59, -5, -43, 60, 0, -51, 61, 6, -58, + 56, -53, 57, 56, -52, 54, 56, -51, 49, 56, -50, 43, + 56, -48, 35, 56, -45, 27, 57, -42, 18, 57, -38, 10, + 57, -34, 1, 58, -29, -8, 58, -24, -16, 59, -19, -25, + 60, -13, -33, 60, -7, -41, 61, -2, -49, 62, 3, -56, + 57, -54, 58, 57, -54, 55, 57, -53, 50, 57, -51, 44, + 58, -49, 37, 58, -47, 29, 58, -44, 20, 58, -40, 11, + 59, -36, 2, 59, -31, -6, 60, -26, -15, 60, -21, -23, + 61, -15, -31, 62, -10, -39, 62, -4, -47, 63, 1, -55, + 59, -56, 59, 59, -55, 57, 59, -54, 52, 59, -52, 46, + 59, -50, 38, 59, -48, 30, 59, -45, 22, 60, -42, 13, + 60, -38, 4, 60, -33, -4, 61, -28, -13, 62, -23, -21, + 62, -17, -29, 63, -12, -37, 64, -6, -45, 64, 0, -53, + 60, -57, 60, 60, -56, 58, 60, -55, 53, 60, -54, 47, + 60, -52, 40, 60, -50, 32, 61, -47, 23, 61, -43, 15, + 61, -39, 6, 62, -35, -2, 62, -30, -11, 63, -25, -19, + 63, -20, -27, 64, -14, -35, 65, -8, -43, 65, -3, -51, + 61, -58, 61, 61, -58, 59, 61, -57, 54, 61, -55, 48, + 61, -53, 41, 62, -51, 34, 62, -48, 25, 62, -45, 17, + 63, -41, 8, 63, -37, 0, 63, -32, -9, 64, -27, -17, + 65, -22, -25, 65, -16, -34, 66, -11, -41, 67, -5, -49, + 62, -60, 62, 62, -59, 60, 63, -58, 56, 63, -57, 50, + 63, -55, 43, 63, -53, 35, 63, -50, 27, 63, -47, 19, + 64, -43, 10, 64, -39, 1, 65, -34, -7, 65, -29, -15, + 66, -24, -23, 66, -18, -32, 67, -13, -40, 68, -7, -47, + 64, -61, 63, 64, -60, 61, 64, -59, 57, 64, -58, 51, + 64, -56, 44, 64, -54, 37, 64, -51, 29, 65, -48, 20, + 65, -45, 12, 65, -41, 3, 66, -36, -5, 66, -31, -14, + 67, -26, -22, 68, -21, -30, 68, -15, -38, 69, -9, -46, + 65, -62, 64, 65, -62, 62, 65, -61, 58, 65, -59, 52, + 65, -57, 46, 66, -55, 38, 66, -53, 30, 66, -50, 22, + 66, -46, 13, 67, -42, 5, 67, -38, -4, 68, -33, -12, + 68, -28, -20, 69, -23, -28, 69, -17, -36, 70, -12, -44, + 66, -63, 65, 66, -63, 63, 66, -62, 59, 67, -61, 54, + 67, -59, 47, 67, -57, 40, 67, -54, 32, 67, -51, 24, + 68, -48, 15, 68, -44, 7, 68, -40, -2, 69, -35, -10, + 69, -30, -18, 70, -25, -26, 71, -19, -34, 71, -14, -42, + 68, -65, 66, 68, -64, 64, 68, -63, 61, 68, -62, 55, + 68, -60, 49, 68, -58, 42, 68, -56, 33, 69, -53, 26, + 69, -49, 17, 69, -46, 9, 70, -41, 0, 70, -37, -8, + 71, -32, -16, 71, -27, -24, 72, -21, -32, 72, -16, -40, + 69, -66, 67, 69, -65, 65, 69, -64, 62, 69, -63, 56, + 69, -62, 50, 69, -60, 43, 70, -57, 35, 70, -54, 27, + 70, -51, 19, 70, -47, 10, 71, -43, 2, 71, -39, -6, + 72, -34, -14, 72, -29, -23, 73, -23, -31, 74, -18, -39, + 70, -67, 68, 70, -66, 66, 70, -66, 63, 70, -64, 58, + 70, -63, 51, 71, -61, 45, 71, -59, 37, 71, -56, 29, + 71, -53, 20, 72, -49, 12, 72, -45, 4, 72, -40, -4, + 73, -36, -12, 73, -31, -21, 74, -25, -29, 75, -20, -37, + 71, -68, 69, 72, -68, 68, 72, -67, 64, 72, -66, 59, + 72, -64, 53, 72, -62, 46, 72, -60, 38, 72, -57, 30, + 73, -54, 22, 73, -51, 14, 73, -47, 5, 74, -42, -3, + 74, -37, -11, 75, -33, -19, 75, -27, -27, 76, -22, -35, + 73, -69, 71, 73, -69, 69, 73, -68, 65, 73, -67, 60, + 73, -65, 54, 73, -64, 47, 73, -61, 40, 74, -59, 32, + 74, -56, 24, 74, -52, 16, 75, -48, 7, 75, -44, -1, + 75, -39, -9, 76, -35, -17, 76, -29, -25, 77, -24, -33, + 74, -71, 72, 74, -70, 70, 74, -69, 67, 74, -68, 61, + 74, -67, 55, 74, -65, 49, 75, -63, 41, 75, -60, 34, + 75, -57, 25, 75, -54, 17, 76, -50, 9, 76, -46, 1, + 77, -41, -7, 77, -36, -15, 78, -31, -23, 78, -26, -31, + 76, -72, 73, 76, -72, 71, 76, -71, 68, 76, -70, 63, + 76, -68, 57, 76, -66, 51, 76, -64, 43, 76, -62, 36, + 77, -59, 27, 77, -56, 20, 77, -52, 11, 78, -48, 3, + 78, -43, -5, 79, -39, -13, 79, -34, -21, 80, -29, -29, + 77, -73, 74, 77, -73, 72, 77, -72, 69, 77, -71, 64, + 77, -70, 58, 77, -68, 52, 77, -66, 45, 78, -63, 37, + 78, -60, 29, 78, -57, 21, 78, -53, 13, 79, -50, 5, + 79, -45, -3, 80, -41, -11, 80, -36, -19, 81, -31, -27, + 78, -74, 75, 78, -74, 73, 78, -73, 70, 78, -72, 65, + 78, -71, 60, 78, -69, 53, 79, -67, 46, 79, -65, 39, + 79, -62, 31, 79, -59, 23, 80, -55, 14, 80, -51, 6, + 80, -47, -1, 81, -42, -10, 81, -37, -18, 82, -33, -26, + 79, -75, 76, 79, -75, 74, 79, -74, 71, 80, -73, 67, + 80, -72, 61, 80, -70, 55, 80, -68, 48, 80, -66, 40, + 80, -63, 32, 81, -60, 25, 81, -57, 16, 81, -53, 8, + 82, -49, 0, 82, -44, -8, 83, -39, -16, 83, -34, -24, + 81, -77, 77, 81, -76, 75, 81, -76, 72, 81, -75, 68, + 81, -73, 62, 81, -72, 56, 81, -69, 49, 81, -67, 42, + 82, -65, 34, 82, -62, 26, 82, -58, 18, 82, -54, 10, + 83, -50, 2, 83, -46, -6, 84, -41, -14, 84, -36, -22, + 82, -78, 78, 82, -77, 76, 82, -77, 74, 82, -76, 69, + 82, -74, 64, 82, -73, 58, 82, -71, 51, 83, -69, 43, + 83, -66, 35, 83, -63, 28, 83, -60, 19, 84, -56, 12, + 84, -52, 3, 84, -48, -4, 85, -43, -12, 85, -38, -20, + 83, -79, 79, 83, -78, 77, 83, -78, 75, 83, -77, 70, + 83, -76, 65, 83, -74, 59, 84, -72, 52, 84, -70, 45, + 84, -67, 37, 84, -64, 29, 85, -61, 21, 85, -58, 13, + 85, -54, 5, 86, -49, -3, 86, -45, -11, 87, -40, -19, + 84, -80, 80, 84, -80, 78, 84, -79, 76, 84, -78, 72, + 85, -77, 66, 85, -75, 60, 85, -73, 53, 85, -71, 46, + 85, -69, 39, 85, -66, 31, 86, -63, 23, 86, -59, 15, + 86, -55, 7, 87, -51, -1, 87, -46, -9, 88, -42, -17, + 86, -81, 81, 86, -81, 79, 86, -80, 77, 86, -79, 73, + 86, -78, 68, 86, -76, 62, 86, -75, 55, 86, -72, 48, + 86, -70, 40, 87, -67, 33, 87, -64, 24, 87, -61, 17, + 88, -57, 8, 88, -53, 1, 88, -48, -7, 89, -44, -15, + 87, -82, 82, 87, -82, 80, 87, -81, 78, 87, -80, 74, + 87, -79, 69, 87, -78, 63, 87, -76, 56, 87, -74, 49, + 88, -71, 42, 88, -69, 34, 88, -65, 26, 88, -62, 18, + 89, -58, 10, 89, -54, 2, 90, -50, -5, 90, -45, -13, + 88, -83, 83, 88, -83, 81, 88, -82, 79, 88, -81, 75, + 88, -80, 70, 88, -79, 64, 88, -77, 58, 89, -75, 51, + 89, -73, 43, 89, -70, 36, 89, -67, 28, 90, -64, 20, + 90, -60, 12, 90, -56, 4, 91, -52, -4, 91, -47, -12, + 8, 28, 11, 8, 29, 3, 9, 30, -7, 10, 33, -18, + 11, 36, -28, 13, 40, -37, 14, 44, -45, 16, 47, -52, + 18, 52, -60, 20, 55, -66, 22, 59, -73, 24, 63, -79, + 27, 67, -85, 29, 71, -91, 31, 75, -97, 33, 79, -103, + 9, 26, 12, 9, 27, 4, 10, 29, -6, 11, 31, -17, + 12, 35, -27, 13, 38, -36, 15, 42, -44, 17, 46, -52, + 19, 50, -59, 21, 54, -66, 23, 59, -73, 25, 63, -79, + 27, 66, -85, 29, 71, -91, 31, 74, -97, 33, 78, -103, + 10, 24, 13, 10, 25, 5, 10, 27, -5, 11, 30, -16, + 12, 33, -26, 14, 37, -35, 15, 41, -44, 17, 45, -51, + 19, 49, -59, 21, 53, -65, 23, 58, -72, 25, 62, -78, + 27, 66, -85, 29, 70, -91, 31, 74, -96, 34, 78, -102, + 10, 22, 14, 11, 23, 6, 11, 25, -4, 12, 28, -15, + 13, 31, -25, 14, 35, -34, 16, 40, -43, 17, 44, -50, + 19, 48, -58, 21, 52, -65, 23, 57, -72, 25, 61, -78, + 27, 65, -84, 29, 69, -90, 32, 73, -96, 34, 77, -102, + 11, 20, 15, 11, 21, 7, 12, 23, -3, 13, 26, -14, + 14, 30, -24, 15, 34, -33, 16, 38, -42, 18, 42, -50, + 20, 47, -57, 22, 51, -64, 24, 56, -71, 26, 60, -77, + 28, 64, -84, 30, 68, -90, 32, 72, -96, 34, 77, -102, + 12, 17, 16, 12, 18, 9, 13, 21, -2, 13, 24, -13, + 14, 28, -23, 16, 32, -32, 17, 36, -41, 19, 41, -49, + 20, 45, -57, 22, 50, -63, 24, 54, -70, 26, 59, -77, + 28, 63, -83, 30, 67, -89, 32, 71, -95, 34, 76, -101, + 13, 15, 18, 13, 16, 10, 14, 18, -1, 14, 22, -12, + 15, 25, -22, 16, 30, -31, 18, 34, -40, 19, 39, -48, + 21, 44, -56, 23, 48, -63, 25, 53, -70, 26, 57, -76, + 28, 62, -83, 30, 66, -89, 33, 70, -95, 35, 75, -101, + 14, 12, 19, 14, 13, 11, 15, 16, 0, 15, 19, -10, + 16, 23, -20, 17, 27, -29, 19, 32, -39, 20, 37, -47, + 22, 42, -55, 23, 46, -62, 25, 51, -69, 27, 56, -75, + 29, 60, -82, 31, 65, -88, 33, 69, -94, 35, 74, -100, + 15, 10, 20, 15, 11, 13, 16, 13, 2, 16, 17, -9, + 17, 20, -19, 18, 25, -28, 19, 30, -37, 21, 34, -45, + 22, 40, -53, 24, 44, -61, 26, 50, -68, 27, 54, -74, + 29, 59, -81, 31, 64, -87, 33, 68, -93, 35, 72, -99, + 16, 7, 22, 16, 8, 14, 17, 10, 3, 17, 14, -7, + 18, 18, -17, 19, 22, -26, 20, 27, -36, 22, 32, -44, + 23, 37, -52, 25, 42, -59, 26, 48, -67, 28, 52, -73, + 30, 57, -80, 32, 62, -86, 34, 67, -93, 36, 71, -99, + 18, 4, 23, 18, 5, 16, 18, 8, 5, 19, 11, -5, + 19, 15, -15, 20, 19, -25, 21, 25, -34, 22, 30, -43, + 24, 35, -51, 25, 40, -58, 27, 46, -66, 29, 50, -72, + 31, 55, -79, 32, 60, -85, 34, 65, -92, 36, 70, -98, + 19, 1, 25, 19, 2, 18, 20, 4, 7, 20, 8, -3, + 21, 12, -13, 22, 16, -22, 23, 21, -32, 24, 26, -41, + 25, 32, -49, 26, 37, -56, 28, 43, -64, 30, 48, -71, + 31, 53, -78, 33, 58, -84, 35, 63, -91, 37, 68, -97, + 20, -1, 27, 21, 0, 19, 21, 2, 9, 21, 5, -1, + 22, 9, -11, 23, 13, -21, 24, 19, -30, 25, 24, -39, + 26, 29, -47, 27, 35, -55, 29, 40, -63, 30, 46, -70, + 32, 51, -77, 34, 56, -83, 36, 61, -90, 38, 66, -96, + 22, -4, 28, 22, -2, 20, 22, 0, 10, 23, 2, 0, + 23, 6, -9, 24, 11, -19, 25, 16, -29, 26, 21, -37, + 27, 27, -46, 28, 32, -53, 30, 38, -61, 31, 43, -68, + 33, 49, -75, 35, 54, -82, 36, 59, -89, 38, 64, -95, + 23, -6, 30, 23, -5, 22, 23, -3, 12, 24, 0, 2, + 24, 4, -7, 25, 8, -17, 26, 13, -27, 27, 18, -35, + 28, 24, -44, 29, 30, -52, 31, 36, -60, 32, 41, -67, + 34, 46, -74, 35, 52, -81, 37, 57, -87, 39, 62, -94, + 24, -9, 31, 25, -8, 24, 25, -6, 14, 25, -3, 4, + 26, 1, -6, 26, 5, -15, 27, 10, -25, 28, 16, -34, + 29, 22, -42, 30, 27, -50, 32, 33, -58, 33, 39, -65, + 35, 44, -73, 36, 50, -79, 38, 55, -86, 40, 60, -92, + 26, -11, 32, 26, -10, 25, 26, -8, 16, 26, -5, 6, + 27, -2, -4, 28, 3, -13, 28, 8, -23, 29, 13, -32, + 30, 19, -41, 31, 24, -49, 33, 30, -57, 34, 36, -64, + 35, 42, -71, 37, 47, -78, 39, 53, -85, 40, 58, -91, + 27, -14, 34, 27, -12, 27, 27, -10, 17, 28, -8, 8, + 28, -4, -2, 29, 0, -11, 30, 5, -21, 30, 10, -30, + 31, 16, -39, 32, 22, -47, 34, 28, -55, 35, 34, -62, + 36, 39, -70, 38, 45, -77, 39, 50, -84, 41, 56, -90, + 29, -16, 35, 29, -15, 28, 29, -13, 19, 29, -10, 9, + 30, -7, 0, 30, -2, -9, 31, 2, -19, 32, 8, -28, + 33, 14, -37, 34, 19, -45, 35, 25, -53, 36, 31, -61, + 37, 37, -68, 39, 43, -75, 40, 48, -82, 42, 54, -89, + 30, -18, 36, 30, -17, 30, 30, -15, 21, 30, -13, 11, + 31, -9, 1, 31, -5, -7, 32, 0, -17, 33, 5, -26, + 34, 11, -35, 35, 17, -43, 36, 23, -52, 37, 28, -59, + 38, 34, -67, 40, 40, -74, 41, 46, -81, 43, 51, -87, + 31, -20, 37, 31, -19, 31, 32, -17, 23, 32, -15, 13, + 32, -11, 3, 33, -7, -5, 33, -2, -15, 34, 2, -24, + 35, 8, -33, 36, 14, -42, 37, 20, -50, 38, 26, -57, + 39, 32, -65, 41, 38, -72, 42, 43, -79, 44, 49, -86, + 33, -22, 38, 33, -21, 33, 33, -19, 24, 33, -17, 15, + 34, -14, 5, 34, -10, -4, 35, -5, -13, 35, 0, -22, + 36, 6, -31, 37, 11, -40, 38, 17, -48, 39, 23, -56, + 40, 29, -63, 42, 35, -71, 43, 41, -78, 44, 47, -85, + 34, -24, 39, 34, -23, 34, 34, -21, 26, 35, -19, 17, + 35, -16, 7, 35, -12, -2, 36, -7, -11, 37, -2, -20, + 37, 3, -30, 38, 9, -38, 39, 15, -46, 40, 21, -54, + 41, 27, -62, 43, 33, -69, 44, 38, -76, 45, 44, -83, + 35, -26, 41, 36, -25, 35, 36, -23, 28, 36, -21, 18, + 36, -18, 9, 37, -14, 0, 37, -9, -9, 38, -4, -18, + 39, 1, -28, 39, 6, -36, 40, 12, -45, 41, 18, -52, + 42, 24, -60, 44, 30, -67, 45, 36, -75, 46, 42, -82, + 37, -28, 42, 37, -27, 36, 37, -25, 29, 37, -23, 20, + 38, -20, 11, 38, -17, 2, 38, -12, -7, 39, -7, -16, + 40, -1, -26, 41, 4, -34, 41, 10, -43, 42, 15, -51, + 43, 22, -58, 45, 27, -66, 46, 33, -73, 47, 39, -80, + 38, -30, 43, 38, -29, 38, 38, -27, 31, 39, -25, 22, + 39, -22, 13, 39, -19, 4, 40, -14, -6, 40, -9, -15, + 41, -4, -24, 42, 1, -32, 43, 7, -41, 44, 13, -49, + 45, 19, -57, 46, 25, -64, 47, 31, -72, 48, 37, -79, + 40, -32, 44, 40, -31, 39, 40, -29, 32, 40, -27, 24, + 40, -24, 15, 41, -21, 5, 41, -16, -4, 42, -12, -13, + 42, -6, -22, 43, -1, -30, 44, 5, -39, 45, 10, -47, + 46, 16, -55, 47, 22, -62, 48, 28, -70, 49, 34, -77, + 41, -33, 45, 41, -33, 40, 41, -31, 34, 41, -29, 25, + 42, -26, 16, 42, -23, 7, 42, -19, -2, 43, -14, -11, + 44, -8, -20, 44, -3, -28, 45, 2, -37, 46, 8, -45, + 47, 14, -53, 48, 20, -61, 49, 26, -68, 50, 32, -75, + 42, -35, 46, 42, -34, 42, 42, -33, 35, 43, -31, 27, + 43, -28, 18, 43, -25, 9, 44, -21, 0, 44, -16, -9, + 45, -11, -18, 45, -5, -27, 46, 0, -35, 47, 5, -43, + 48, 11, -51, 49, 17, -59, 50, 23, -67, 51, 29, -74, + 44, -37, 47, 44, -36, 43, 44, -35, 37, 44, -33, 29, + 44, -30, 20, 45, -27, 11, 45, -23, 2, 45, -18, -7, + 46, -13, -16, 47, -8, -25, 47, -2, -33, 48, 3, -41, + 49, 9, -49, 50, 15, -57, 51, 21, -65, 52, 27, -72, + 45, -39, 48, 45, -38, 44, 45, -36, 38, 45, -34, 30, + 46, -32, 22, 46, -29, 13, 46, -25, 3, 47, -21, -5, + 47, -15, -14, 48, -10, -23, 49, -4, -32, 49, 0, -40, + 50, 6, -48, 51, 12, -55, 52, 18, -63, 53, 24, -70, + 46, -40, 50, 46, -39, 45, 47, -38, 40, 47, -36, 32, + 47, -34, 23, 47, -31, 15, 48, -27, 5, 48, -23, -3, + 49, -18, -12, 49, -12, -21, 50, -7, -30, 51, -1, -38, + 51, 4, -46, 52, 10, -54, 53, 16, -61, 54, 22, -69, + 48, -42, 51, 48, -41, 47, 48, -40, 41, 48, -38, 34, + 49, -36, 26, 49, -33, 17, 49, -29, 8, 50, -25, -1, + 50, -20, -10, 51, -15, -18, 51, -10, -27, 52, -4, -35, + 53, 1, -43, 54, 7, -51, 55, 13, -59, 56, 19, -67, + 49, -44, 52, 49, -43, 48, 50, -42, 43, 50, -40, 36, + 50, -38, 27, 50, -35, 19, 51, -31, 10, 51, -27, 1, + 51, -22, -8, 52, -17, -17, 53, -12, -25, 53, -6, -34, + 54, -1, -42, 55, 4, -50, 56, 10, -57, 57, 16, -65, + 51, -45, 53, 51, -44, 50, 51, -43, 44, 51, -41, 37, + 51, -39, 29, 51, -37, 21, 52, -33, 11, 52, -29, 3, + 53, -24, -6, 53, -20, -15, 54, -14, -24, 54, -9, -32, + 55, -3, -40, 56, 2, -48, 57, 8, -56, 58, 14, -63, + 52, -47, 54, 52, -46, 51, 52, -45, 45, 52, -43, 39, + 53, -41, 31, 53, -38, 22, 53, -35, 13, 53, -31, 5, + 54, -27, -4, 54, -22, -13, 55, -16, -22, 56, -11, -30, + 56, -5, -38, 57, 0, -46, 58, 6, -54, 59, 11, -61, + 53, -48, 55, 53, -47, 52, 54, -46, 47, 54, -45, 40, + 54, -42, 32, 54, -40, 24, 54, -37, 15, 55, -33, 6, + 55, -29, -2, 56, -24, -11, 56, -19, -20, 57, -13, -28, + 58, -8, -36, 58, -2, -44, 59, 3, -52, 60, 9, -60, + 55, -50, 56, 55, -49, 53, 55, -48, 48, 55, -46, 42, + 55, -44, 34, 55, -42, 26, 56, -38, 17, 56, -35, 8, + 56, -30, -1, 57, -26, -9, 57, -21, -18, 58, -15, -26, + 59, -10, -34, 59, -4, -42, 60, 1, -50, 61, 7, -58, + 56, -51, 57, 56, -50, 54, 56, -49, 49, 56, -48, 43, + 56, -46, 36, 57, -43, 27, 57, -40, 19, 57, -37, 10, + 58, -32, 1, 58, -28, -7, 59, -23, -16, 59, -18, -24, + 60, -12, -32, 61, -6, -41, 61, -1, -48, 62, 4, -56, + 57, -52, 58, 57, -52, 56, 58, -51, 51, 58, -49, 45, + 58, -47, 37, 58, -45, 29, 58, -42, 20, 59, -38, 12, + 59, -34, 3, 59, -30, -5, 60, -25, -14, 61, -20, -22, + 61, -14, -30, 62, -9, -39, 63, -3, -47, 63, 2, -54, + 59, -54, 59, 59, -53, 57, 59, -52, 52, 59, -51, 46, + 59, -49, 39, 59, -46, 31, 60, -43, 22, 60, -40, 14, + 60, -36, 5, 61, -32, -4, 61, -27, -12, 62, -22, -21, + 62, -16, -29, 63, -11, -37, 64, -5, -45, 65, 0, -53, + 60, -55, 60, 60, -55, 58, 60, -54, 53, 60, -52, 47, + 60, -50, 40, 61, -48, 32, 61, -45, 24, 61, -42, 15, + 62, -38, 6, 62, -34, -2, 62, -29, -11, 63, -24, -19, + 64, -18, -27, 64, -13, -35, 65, -7, -43, 66, -2, -51, + 61, -56, 61, 61, -56, 59, 61, -55, 55, 62, -53, 49, + 62, -52, 42, 62, -49, 34, 62, -47, 25, 62, -44, 17, + 63, -40, 8, 63, -36, 0, 64, -31, -9, 64, -26, -17, + 65, -21, -25, 65, -15, -33, 66, -10, -41, 67, -4, -49, + 63, -58, 62, 63, -57, 60, 63, -56, 56, 63, -55, 50, + 63, -53, 43, 63, -51, 36, 63, -48, 27, 64, -45, 19, + 64, -41, 10, 64, -37, 2, 65, -33, -7, 65, -28, -15, + 66, -23, -23, 67, -17, -31, 67, -12, -39, 68, -6, -47, + 64, -59, 63, 64, -59, 61, 64, -58, 57, 64, -56, 51, + 64, -54, 44, 64, -52, 37, 65, -50, 29, 65, -47, 21, + 65, -43, 12, 66, -39, 4, 66, -35, -5, 67, -30, -13, + 67, -25, -21, 68, -20, -30, 68, -14, -37, 69, -8, -45, + 65, -60, 64, 65, -60, 62, 65, -59, 58, 65, -58, 53, + 66, -56, 46, 66, -54, 39, 66, -51, 31, 66, -48, 22, + 67, -45, 14, 67, -41, 5, 67, -37, -3, 68, -32, -11, + 68, -27, -19, 69, -22, -28, 70, -16, -36, 70, -11, -44, + 67, -62, 66, 67, -61, 63, 67, -60, 60, 67, -59, 54, + 67, -57, 47, 67, -55, 40, 67, -53, 32, 67, -50, 24, + 68, -46, 15, 68, -43, 7, 69, -38, -1, 69, -34, -10, + 70, -29, -18, 70, -24, -26, 71, -18, -34, 71, -13, -42, + 68, -63, 67, 68, -62, 65, 68, -62, 61, 68, -60, 55, + 68, -59, 49, 68, -57, 42, 68, -54, 34, 69, -51, 26, + 69, -48, 17, 69, -44, 9, 70, -40, 0, 70, -36, -8, + 71, -31, -16, 71, -26, -24, 72, -20, -32, 73, -15, -40, + 69, -64, 68, 69, -64, 66, 69, -63, 62, 69, -62, 57, + 69, -60, 50, 70, -58, 43, 70, -56, 35, 70, -53, 27, + 70, -50, 19, 71, -46, 11, 71, -42, 2, 71, -38, -6, + 72, -33, -14, 72, -28, -22, 73, -22, -30, 74, -17, -38, + 70, -65, 69, 70, -65, 67, 70, -64, 63, 71, -63, 58, + 71, -61, 52, 71, -59, 45, 71, -57, 37, 71, -54, 29, + 72, -51, 21, 72, -48, 12, 72, -44, 4, 73, -39, -4, + 73, -35, -12, 74, -30, -21, 74, -24, -28, 75, -19, -36, + 72, -67, 70, 72, -66, 68, 72, -65, 64, 72, -64, 59, + 72, -63, 53, 72, -61, 46, 72, -59, 38, 72, -56, 31, + 73, -53, 22, 73, -49, 14, 73, -45, 6, 74, -41, -2, + 74, -36, -10, 75, -32, -19, 75, -26, -27, 76, -21, -35, + 73, -68, 71, 73, -67, 69, 73, -67, 66, 73, -66, 60, + 73, -64, 54, 73, -62, 48, 74, -60, 40, 74, -57, 32, + 74, -54, 24, 74, -51, 16, 75, -47, 7, 75, -43, -1, + 75, -38, -9, 76, -34, -17, 77, -28, -25, 77, -23, -33, + 74, -69, 72, 74, -69, 70, 74, -68, 67, 74, -67, 62, + 74, -65, 56, 75, -64, 49, 75, -61, 42, 75, -59, 34, + 75, -56, 26, 76, -53, 18, 76, -49, 9, 76, -45, 1, + 77, -40, -7, 77, -35, -15, 78, -30, -23, 78, -25, -31, + 76, -71, 73, 76, -70, 71, 76, -70, 68, 76, -68, 63, + 76, -67, 57, 76, -65, 51, 76, -63, 43, 77, -61, 36, + 77, -58, 28, 77, -55, 20, 77, -51, 11, 78, -47, 3, + 78, -42, -5, 79, -38, -13, 79, -33, -21, 80, -28, -29, + 77, -72, 74, 77, -71, 72, 77, -71, 69, 77, -70, 64, + 77, -68, 59, 77, -67, 52, 78, -64, 45, 78, -62, 37, + 78, -59, 29, 78, -56, 21, 79, -52, 13, 79, -49, 5, + 79, -44, -3, 80, -40, -11, 80, -35, -19, 81, -30, -27, + 78, -73, 75, 78, -73, 73, 78, -72, 70, 78, -71, 66, + 79, -70, 60, 79, -68, 54, 79, -66, 46, 79, -63, 39, + 79, -61, 31, 80, -58, 23, 80, -54, 15, 80, -50, 7, + 81, -46, -1, 81, -41, -9, 82, -37, -17, 82, -32, -25, + 80, -74, 76, 80, -74, 74, 80, -73, 72, 80, -72, 67, + 80, -71, 61, 80, -69, 55, 80, -67, 48, 80, -65, 41, + 80, -62, 33, 81, -59, 25, 81, -56, 16, 81, -52, 8, + 82, -48, 0, 82, -43, -8, 83, -38, -16, 83, -34, -24, + 81, -75, 77, 81, -75, 75, 81, -74, 73, 81, -73, 68, + 81, -72, 63, 81, -70, 56, 81, -68, 49, 81, -66, 42, + 82, -63, 34, 82, -61, 26, 82, -57, 18, 83, -53, 10, + 83, -49, 2, 83, -45, -6, 84, -40, -14, 84, -36, -22, + 82, -76, 78, 82, -76, 76, 82, -76, 74, 82, -75, 69, + 82, -73, 64, 82, -72, 58, 82, -70, 51, 83, -67, 44, + 83, -65, 36, 83, -62, 28, 83, -59, 20, 84, -55, 12, + 84, -51, 4, 85, -47, -4, 85, -42, -12, 86, -37, -20, + 83, -78, 79, 83, -77, 77, 83, -77, 75, 83, -76, 71, + 83, -75, 65, 84, -73, 59, 84, -71, 52, 84, -69, 45, + 84, -66, 37, 84, -63, 30, 85, -60, 21, 85, -57, 14, + 85, -53, 5, 86, -48, -3, 86, -44, -10, 87, -39, -18, + 84, -79, 80, 85, -78, 78, 85, -78, 76, 85, -77, 72, + 85, -76, 66, 85, -74, 60, 85, -72, 54, 85, -70, 47, + 85, -68, 39, 86, -65, 31, 86, -62, 23, 86, -58, 15, + 87, -54, 7, 87, -50, -1, 87, -46, -9, 88, -41, -17, + 86, -80, 81, 86, -80, 79, 86, -79, 77, 86, -78, 73, + 86, -77, 68, 86, -75, 62, 86, -73, 55, 86, -71, 48, + 87, -69, 40, 87, -66, 33, 87, -63, 25, 87, -60, 17, + 88, -56, 9, 88, -52, 1, 89, -47, -7, 89, -43, -15, + 87, -81, 82, 87, -81, 80, 87, -80, 78, 87, -79, 74, + 87, -78, 69, 87, -77, 63, 87, -75, 56, 88, -73, 50, + 88, -70, 42, 88, -68, 34, 88, -64, 26, 89, -61, 19, + 89, -58, 10, 89, -54, 2, 90, -49, -5, 90, -45, -13, + 88, -82, 83, 88, -82, 81, 88, -81, 79, 88, -80, 75, + 88, -79, 70, 88, -78, 64, 89, -76, 58, 89, -74, 51, + 89, -72, 43, 89, -69, 36, 89, -66, 28, 90, -63, 20, + 90, -59, 12, 90, -55, 4, 91, -51, -4, 91, -46, -11, + 10, 30, 14, 11, 31, 7, 11, 33, -4, 12, 35, -15, + 13, 38, -25, 14, 41, -34, 16, 45, -43, 17, 48, -50, + 19, 52, -58, 21, 56, -65, 23, 60, -72, 25, 64, -78, + 27, 68, -84, 29, 71, -90, 32, 75, -96, 34, 79, -102, + 11, 29, 15, 11, 29, 7, 12, 31, -3, 12, 33, -14, + 13, 36, -24, 15, 40, -33, 16, 43, -42, 18, 47, -50, + 20, 51, -57, 22, 55, -64, 24, 59, -71, 26, 63, -77, + 28, 67, -84, 30, 71, -90, 32, 75, -96, 34, 79, -102, + 11, 27, 16, 12, 28, 8, 12, 29, -3, 13, 32, -14, + 14, 35, -23, 15, 38, -33, 17, 42, -42, 18, 46, -49, + 20, 50, -57, 22, 54, -64, 24, 58, -71, 26, 62, -77, + 28, 66, -84, 30, 70, -89, 32, 74, -96, 34, 78, -101, + 12, 25, 17, 12, 26, 9, 13, 28, -2, 13, 30, -13, + 14, 33, -23, 16, 37, -32, 17, 41, -41, 19, 45, -49, + 20, 49, -56, 22, 53, -63, 24, 57, -70, 26, 61, -77, + 28, 65, -83, 30, 70, -89, 32, 73, -95, 34, 77, -101, + 13, 23, 18, 13, 24, 10, 13, 26, -1, 14, 29, -12, + 15, 32, -22, 16, 35, -31, 18, 39, -40, 19, 43, -48, + 21, 48, -56, 23, 52, -63, 24, 56, -70, 26, 60, -76, + 28, 65, -83, 30, 69, -89, 32, 73, -95, 34, 77, -101, + 14, 21, 19, 14, 22, 11, 14, 24, 0, 15, 27, -11, + 16, 30, -21, 17, 34, -30, 18, 38, -39, 20, 42, -47, + 21, 46, -55, 23, 51, -62, 25, 55, -69, 27, 59, -76, + 29, 63, -82, 31, 68, -88, 33, 72, -94, 35, 76, -100, + 14, 19, 20, 15, 20, 12, 15, 21, 1, 16, 24, -9, + 17, 28, -19, 18, 32, -29, 19, 36, -38, 20, 40, -46, + 22, 45, -54, 24, 49, -61, 25, 54, -68, 27, 58, -75, + 29, 62, -81, 31, 67, -88, 33, 71, -94, 35, 75, -100, + 15, 16, 21, 16, 17, 13, 16, 19, 2, 17, 22, -8, + 17, 26, -18, 18, 29, -27, 20, 34, -37, 21, 38, -45, + 22, 43, -53, 24, 47, -60, 26, 52, -67, 28, 57, -74, + 30, 61, -81, 31, 65, -87, 33, 70, -93, 35, 74, -99, + 16, 14, 22, 17, 15, 15, 17, 17, 4, 18, 20, -7, + 18, 23, -17, 19, 27, -26, 20, 32, -35, 22, 36, -44, + 23, 41, -52, 25, 46, -59, 26, 50, -67, 28, 55, -73, + 30, 59, -80, 32, 64, -86, 34, 68, -93, 36, 73, -98, + 18, 11, 24, 18, 12, 16, 18, 14, 5, 19, 17, -5, + 19, 21, -15, 20, 25, -25, 21, 29, -34, 23, 34, -42, + 24, 39, -51, 25, 44, -58, 27, 49, -66, 29, 53, -72, + 31, 58, -79, 32, 63, -85, 34, 67, -92, 36, 72, -98, + 19, 9, 25, 19, 9, 17, 19, 11, 7, 20, 14, -4, + 20, 18, -13, 21, 22, -23, 22, 27, -33, 23, 31, -41, + 25, 37, -49, 26, 41, -57, 28, 47, -64, 29, 51, -71, + 31, 56, -78, 33, 61, -84, 35, 65, -91, 37, 70, -97, + 20, 5, 27, 20, 6, 19, 21, 8, 9, 21, 11, -1, + 22, 15, -11, 22, 19, -21, 23, 24, -31, 25, 28, -39, + 26, 34, -48, 27, 39, -55, 29, 44, -63, 30, 49, -70, + 32, 54, -77, 34, 59, -83, 35, 63, -90, 37, 68, -96, + 21, 3, 28, 22, 4, 20, 22, 5, 10, 22, 8, 0, + 23, 12, -10, 24, 16, -19, 24, 21, -29, 26, 26, -38, + 27, 31, -46, 28, 36, -54, 30, 42, -62, 31, 47, -69, + 33, 52, -76, 34, 57, -82, 36, 62, -89, 38, 66, -95, + 23, 0, 30, 23, 1, 22, 23, 3, 12, 23, 6, 2, + 24, 9, -8, 25, 14, -18, 26, 18, -27, 27, 23, -36, + 28, 29, -45, 29, 34, -52, 30, 39, -60, 32, 44, -67, + 33, 49, -74, 35, 55, -81, 37, 60, -88, 39, 65, -94, + 24, -2, 31, 24, -1, 23, 24, 0, 13, 25, 3, 3, + 25, 7, -6, 26, 11, -16, 27, 16, -25, 28, 21, -34, + 29, 26, -43, 30, 31, -51, 31, 37, -59, 33, 42, -66, + 34, 47, -73, 36, 53, -80, 37, 58, -87, 39, 63, -93, + 25, -5, 32, 25, -3, 25, 26, -2, 15, 26, 1, 5, + 26, 4, -4, 27, 8, -14, 28, 13, -24, 29, 18, -32, + 30, 23, -41, 31, 29, -49, 32, 34, -57, 34, 40, -65, + 35, 45, -72, 37, 50, -79, 38, 55, -85, 40, 61, -92, + 27, -7, 33, 27, -6, 26, 27, -4, 17, 27, -2, 7, + 28, 2, -2, 28, 6, -12, 29, 10, -22, 30, 15, -31, + 31, 21, -40, 32, 26, -48, 33, 32, -56, 35, 37, -63, + 36, 43, -70, 37, 48, -77, 39, 53, -84, 41, 59, -91, + 28, -10, 34, 28, -8, 28, 28, -7, 19, 28, -4, 9, + 29, -1, -1, 29, 3, -10, 30, 8, -20, 31, 13, -29, + 32, 18, -38, 33, 23, -46, 34, 29, -54, 35, 35, -62, + 37, 40, -69, 38, 46, -76, 40, 51, -83, 41, 57, -89, + 29, -12, 36, 29, -11, 29, 29, -9, 20, 30, -7, 10, + 30, -3, 0, 31, 0, -8, 31, 5, -18, 32, 10, -27, + 33, 15, -36, 34, 21, -44, 35, 27, -53, 36, 32, -60, + 38, 38, -68, 39, 43, -74, 41, 49, -82, 42, 54, -88, + 31, -14, 37, 31, -13, 31, 31, -11, 22, 31, -9, 12, + 31, -6, 2, 32, -2, -6, 33, 2, -16, 33, 7, -25, + 34, 13, -34, 35, 18, -43, 36, 24, -51, 37, 30, -58, + 39, 35, -66, 40, 41, -73, 41, 46, -80, 43, 52, -87, + 32, -16, 38, 32, -15, 32, 32, -14, 24, 32, -11, 14, + 33, -8, 4, 33, -5, -5, 34, 0, -14, 35, 5, -23, + 35, 10, -32, 36, 16, -41, 37, 21, -49, 39, 27, -57, + 40, 33, -64, 41, 39, -72, 42, 44, -79, 44, 50, -85, + 33, -19, 39, 33, -18, 33, 33, -16, 25, 34, -14, 16, + 34, -11, 6, 35, -7, -3, 35, -2, -12, 36, 2, -21, + 37, 8, -31, 37, 13, -39, 39, 19, -47, 40, 25, -55, + 41, 30, -63, 42, 36, -70, 43, 42, -77, 45, 47, -84, + 35, -21, 40, 35, -20, 35, 35, -18, 27, 35, -16, 17, + 35, -13, 8, 36, -9, -1, 36, -5, -11, 37, 0, -20, + 38, 5, -29, 39, 10, -37, 40, 16, -46, 41, 22, -53, + 42, 28, -61, 43, 34, -68, 44, 39, -76, 46, 45, -83, + 36, -23, 41, 36, -22, 36, 36, -20, 28, 36, -18, 19, + 37, -15, 10, 37, -12, 0, 38, -7, -9, 38, -2, -18, + 39, 3, -27, 40, 8, -35, 41, 14, -44, 42, 19, -52, + 43, 25, -59, 44, 31, -67, 45, 37, -74, 47, 43, -81, + 37, -25, 42, 37, -24, 37, 38, -22, 30, 38, -20, 21, + 38, -17, 12, 38, -14, 2, 39, -10, -7, 39, -5, -16, + 40, 0, -25, 41, 5, -33, 42, 11, -42, 43, 17, -50, + 44, 23, -58, 45, 29, -65, 46, 34, -73, 48, 40, -80, + 39, -27, 44, 39, -26, 38, 39, -24, 31, 39, -22, 22, + 39, -19, 13, 40, -16, 4, 40, -12, -5, 41, -7, -14, + 41, -2, -23, 42, 3, -32, 43, 9, -40, 44, 14, -48, + 45, 20, -56, 46, 26, -64, 47, 32, -71, 48, 38, -78, + 40, -29, 45, 40, -28, 40, 40, -26, 33, 40, -24, 24, + 41, -22, 15, 41, -18, 6, 41, -14, -3, 42, -10, -12, + 43, -4, -21, 43, 0, -30, 44, 6, -38, 45, 12, -46, + 46, 18, -54, 47, 23, -62, 48, 29, -69, 49, 35, -76, + 41, -30, 46, 41, -30, 41, 42, -28, 34, 42, -26, 26, + 42, -24, 17, 42, -20, 8, 43, -16, -1, 43, -12, -10, + 44, -7, -19, 45, -1, -28, 45, 4, -37, 46, 9, -45, + 47, 15, -52, 48, 21, -60, 49, 27, -68, 50, 33, -75, + 43, -32, 47, 43, -31, 42, 43, -30, 36, 43, -28, 28, + 43, -26, 19, 44, -23, 10, 44, -19, 0, 45, -14, -8, + 45, -9, -17, 46, -4, -26, 47, 1, -35, 47, 7, -43, + 48, 13, -51, 49, 18, -58, 50, 24, -66, 51, 30, -73, + 44, -34, 48, 44, -33, 43, 44, -32, 37, 44, -30, 29, + 45, -28, 21, 45, -25, 12, 45, -21, 2, 46, -16, -6, + 46, -11, -16, 47, -6, -24, 48, 0, -33, 49, 4, -41, + 49, 10, -49, 50, 16, -57, 51, 22, -64, 53, 28, -72, + 45, -36, 49, 45, -35, 45, 46, -34, 39, 46, -32, 31, + 46, -29, 22, 46, -27, 13, 47, -23, 4, 47, -19, -4, + 48, -14, -14, 48, -9, -22, 49, -3, -31, 50, 2, -39, + 51, 8, -47, 51, 13, -55, 52, 19, -63, 54, 25, -70, + 47, -38, 50, 47, -37, 46, 47, -35, 40, 47, -34, 32, + 47, -31, 24, 48, -28, 15, 48, -25, 6, 48, -21, -3, + 49, -16, -12, 49, -11, -20, 50, -5, -29, 51, 0, -37, + 52, 5, -45, 53, 11, -53, 54, 17, -61, 55, 23, -68, + 48, -40, 51, 48, -39, 47, 49, -38, 42, 49, -36, 34, + 49, -34, 26, 49, -31, 17, 50, -27, 8, 50, -23, 0, + 50, -19, -9, 51, -14, -18, 52, -8, -27, 52, -3, -35, + 53, 2, -43, 54, 8, -51, 55, 14, -59, 56, 20, -66, + 50, -41, 52, 50, -40, 49, 50, -39, 43, 50, -37, 36, + 50, -35, 28, 50, -33, 19, 51, -29, 10, 51, -25, 1, + 52, -21, -8, 52, -16, -16, 53, -10, -25, 54, -5, -33, + 54, 0, -41, 55, 6, -49, 56, 11, -57, 57, 17, -64, + 51, -43, 53, 51, -42, 50, 51, -41, 44, 51, -39, 38, + 52, -37, 29, 52, -34, 21, 52, -31, 12, 52, -27, 3, + 53, -23, -6, 53, -18, -14, 54, -13, -23, 55, -7, -31, + 55, -2, -39, 56, 3, -47, 57, 9, -55, 58, 15, -63, + 52, -44, 54, 52, -44, 51, 53, -43, 46, 53, -41, 39, + 53, -39, 31, 53, -36, 23, 53, -33, 14, 54, -29, 5, + 54, -25, -4, 55, -20, -12, 55, -15, -21, 56, -10, -29, + 57, -4, -37, 57, 1, -46, 58, 7, -53, 59, 12, -61, + 54, -46, 55, 54, -45, 52, 54, -44, 47, 54, -42, 41, + 54, -40, 33, 54, -38, 24, 55, -35, 15, 55, -31, 7, + 55, -27, -2, 56, -22, -10, 57, -17, -19, 57, -12, -28, + 58, -6, -36, 59, -1, -44, 59, 4, -52, 60, 10, -59, + 55, -47, 56, 55, -47, 54, 55, -46, 48, 55, -44, 42, + 55, -42, 34, 56, -40, 26, 56, -37, 17, 56, -33, 9, + 57, -29, 0, 57, -24, -9, 58, -19, -18, 58, -14, -26, + 59, -9, -34, 60, -3, -42, 61, 2, -50, 61, 8, -57, + 56, -49, 58, 56, -48, 55, 56, -47, 50, 57, -46, 43, + 57, -44, 36, 57, -41, 28, 57, -38, 19, 58, -35, 10, + 58, -31, 1, 58, -26, -7, 59, -21, -16, 60, -16, -24, + 60, -11, -32, 61, -5, -40, 62, 0, -48, 62, 5, -56, + 58, -50, 59, 58, -50, 56, 58, -49, 51, 58, -47, 45, + 58, -45, 37, 58, -43, 30, 59, -40, 21, 59, -37, 12, + 59, -33, 3, 60, -28, -5, 60, -23, -14, 61, -19, -22, + 61, -13, -30, 62, -8, -38, 63, -2, -46, 64, 3, -54, + 59, -52, 60, 59, -51, 57, 59, -50, 52, 59, -49, 46, + 59, -47, 39, 60, -45, 31, 60, -42, 22, 60, -39, 14, + 60, -35, 5, 61, -30, -3, 61, -26, -12, 62, -21, -20, + 63, -15, -28, 63, -10, -37, 64, -4, -44, 65, 1, -52, + 60, -53, 61, 60, -53, 58, 60, -52, 54, 60, -50, 48, + 61, -48, 40, 61, -46, 33, 61, -43, 24, 61, -40, 16, + 62, -36, 7, 62, -32, -1, 63, -28, -10, 63, -23, -18, + 64, -17, -26, 64, -12, -35, 65, -6, -43, 66, -1, -50, + 62, -55, 62, 62, -54, 59, 62, -53, 55, 62, -52, 49, + 62, -50, 42, 62, -48, 34, 62, -45, 26, 63, -42, 18, + 63, -38, 9, 63, -34, 0, 64, -30, -8, 64, -25, -17, + 65, -19, -25, 66, -14, -33, 66, -9, -41, 67, -3, -49, + 63, -56, 63, 63, -55, 60, 63, -55, 56, 63, -53, 50, + 63, -51, 43, 63, -49, 36, 64, -47, 27, 64, -44, 19, + 64, -40, 10, 65, -36, 2, 65, -31, -7, 66, -27, -15, + 66, -22, -23, 67, -16, -31, 67, -11, -39, 68, -5, -47, + 64, -57, 64, 64, -57, 62, 64, -56, 57, 64, -55, 52, + 64, -53, 45, 65, -51, 37, 65, -48, 29, 65, -45, 21, + 65, -42, 12, 66, -38, 4, 66, -33, -5, 67, -29, -13, + 67, -24, -21, 68, -19, -29, 69, -13, -37, 69, -8, -45, + 65, -59, 65, 65, -58, 63, 66, -57, 59, 66, -56, 53, + 66, -54, 46, 66, -52, 39, 66, -50, 31, 66, -47, 23, + 67, -43, 14, 67, -40, 6, 68, -35, -3, 68, -31, -11, + 68, -26, -19, 69, -21, -27, 70, -15, -35, 70, -10, -43, + 67, -60, 66, 67, -60, 64, 67, -59, 60, 67, -57, 54, + 67, -56, 48, 67, -54, 41, 67, -51, 32, 68, -48, 24, + 68, -45, 16, 68, -41, 7, 69, -37, -1, 69, -33, -9, + 70, -28, -17, 70, -23, -26, 71, -17, -34, 72, -12, -42, + 68, -61, 67, 68, -61, 65, 68, -60, 61, 68, -59, 55, + 68, -57, 49, 68, -55, 42, 69, -53, 34, 69, -50, 26, + 69, -47, 17, 70, -43, 9, 70, -39, 1, 70, -35, -8, + 71, -30, -16, 71, -25, -24, 72, -19, -32, 73, -14, -40, + 69, -63, 68, 69, -62, 66, 69, -61, 62, 69, -60, 57, + 70, -59, 50, 70, -57, 44, 70, -54, 36, 70, -52, 28, + 70, -48, 19, 71, -45, 11, 71, -41, 2, 72, -36, -6, + 72, -32, -14, 73, -27, -22, 73, -21, -30, 74, -16, -38, + 71, -64, 69, 71, -63, 67, 71, -63, 63, 71, -62, 58, + 71, -60, 52, 71, -58, 45, 71, -56, 37, 71, -53, 29, + 72, -50, 21, 72, -47, 13, 72, -43, 4, 73, -38, -4, + 73, -34, -12, 74, -29, -20, 74, -23, -28, 75, -18, -36, + 72, -65, 70, 72, -65, 68, 72, -64, 65, 72, -63, 59, + 72, -61, 53, 72, -59, 46, 72, -57, 39, 73, -55, 31, + 73, -52, 23, 73, -48, 14, 74, -44, 6, 74, -40, -2, + 74, -35, -10, 75, -31, -19, 76, -25, -26, 76, -20, -34, + 73, -66, 71, 73, -66, 69, 73, -65, 66, 73, -64, 60, + 73, -63, 54, 73, -61, 48, 74, -59, 40, 74, -56, 33, + 74, -53, 24, 74, -50, 16, 75, -46, 8, 75, -42, 0, + 76, -37, -8, 76, -33, -17, 77, -27, -25, 77, -22, -33, + 74, -68, 72, 74, -67, 70, 74, -67, 67, 74, -65, 62, + 75, -64, 56, 75, -62, 49, 75, -60, 42, 75, -58, 34, + 75, -55, 26, 76, -51, 18, 76, -48, 9, 76, -44, 1, + 77, -39, -7, 77, -35, -15, 78, -29, -23, 78, -24, -31, + 76, -69, 73, 76, -69, 71, 76, -68, 68, 76, -67, 63, + 76, -66, 57, 76, -64, 51, 76, -62, 44, 77, -59, 36, + 77, -56, 28, 77, -53, 20, 78, -50, 11, 78, -46, 3, + 78, -41, -4, 79, -37, -13, 79, -32, -21, 80, -27, -29, + 77, -70, 74, 77, -70, 72, 77, -69, 70, 77, -68, 65, + 77, -67, 59, 78, -65, 52, 78, -63, 45, 78, -61, 38, + 78, -58, 30, 78, -55, 22, 79, -51, 13, 79, -48, 5, + 80, -43, -3, 80, -39, -11, 80, -34, -19, 81, -29, -27, + 78, -72, 75, 78, -71, 73, 78, -71, 71, 79, -70, 66, + 79, -68, 60, 79, -67, 54, 79, -64, 47, 79, -62, 39, + 79, -59, 31, 80, -56, 23, 80, -53, 15, 80, -49, 7, + 81, -45, -1, 81, -41, -9, 82, -36, -17, 82, -31, -25, + 80, -73, 76, 80, -73, 74, 80, -72, 72, 80, -71, 67, + 80, -70, 61, 80, -68, 55, 80, -66, 48, 80, -64, 41, + 81, -61, 33, 81, -58, 25, 81, -54, 17, 82, -51, 9, + 82, -47, 0, 82, -42, -8, 83, -38, -15, 83, -33, -23, + 81, -74, 77, 81, -74, 76, 81, -73, 73, 81, -72, 68, + 81, -71, 63, 81, -69, 57, 81, -67, 49, 82, -65, 42, + 82, -62, 34, 82, -59, 27, 82, -56, 18, 83, -52, 10, + 83, -48, 2, 84, -44, -6, 84, -39, -14, 84, -35, -22, + 82, -75, 78, 82, -75, 77, 82, -74, 74, 82, -73, 69, + 82, -72, 64, 82, -71, 58, 83, -68, 51, 83, -66, 44, + 83, -64, 36, 83, -61, 28, 84, -58, 20, 84, -54, 12, + 84, -50, 4, 85, -46, -4, 85, -41, -12, 86, -37, -20, + 83, -76, 79, 83, -76, 78, 83, -76, 75, 84, -75, 71, + 84, -73, 65, 84, -72, 59, 84, -70, 52, 84, -68, 45, + 84, -65, 37, 84, -62, 30, 85, -59, 22, 85, -56, 14, + 85, -52, 5, 86, -48, -2, 86, -43, -10, 87, -38, -18, + 85, -78, 80, 85, -77, 79, 85, -77, 76, 85, -76, 72, + 85, -75, 67, 85, -73, 61, 85, -71, 54, 85, -69, 47, + 85, -67, 39, 86, -64, 31, 86, -61, 23, 86, -57, 15, + 87, -53, 7, 87, -49, -1, 87, -45, -8, 88, -40, -16, + 86, -79, 81, 86, -78, 80, 86, -78, 77, 86, -77, 73, + 86, -76, 68, 86, -74, 62, 86, -72, 55, 86, -70, 48, + 87, -68, 41, 87, -65, 33, 87, -62, 25, 87, -59, 17, + 88, -55, 9, 88, -51, 1, 89, -47, -7, 89, -42, -15, + 87, -80, 82, 87, -80, 81, 87, -79, 78, 87, -78, 74, + 87, -77, 69, 87, -76, 63, 88, -74, 57, 88, -72, 50, + 88, -69, 42, 88, -67, 35, 88, -64, 26, 89, -60, 19, + 89, -57, 11, 89, -53, 3, 90, -48, -5, 90, -44, -13, + 88, -81, 83, 88, -81, 82, 88, -80, 79, 88, -79, 75, + 88, -78, 70, 89, -77, 65, 89, -75, 58, 89, -73, 51, + 89, -71, 44, 89, -68, 36, 90, -65, 28, 90, -62, 20, + 90, -58, 12, 91, -54, 4, 91, -50, -3, 91, -46, -11, + 12, 33, 17, 13, 33, 10, 13, 35, -1, 14, 37, -12, + 15, 39, -22, 16, 42, -31, 17, 46, -41, 19, 49, -48, + 21, 53, -56, 22, 56, -63, 24, 60, -70, 26, 64, -76, + 28, 68, -83, 30, 72, -89, 32, 76, -95, 34, 79, -101, + 13, 31, 18, 13, 32, 10, 14, 33, 0, 14, 35, -12, + 15, 38, -21, 16, 41, -31, 18, 44, -40, 19, 48, -48, + 21, 52, -56, 23, 56, -63, 25, 60, -70, 26, 63, -76, + 28, 67, -83, 30, 71, -89, 33, 75, -95, 35, 79, -101, + 13, 30, 19, 14, 30, 11, 14, 32, 0, 15, 34, -11, + 16, 37, -21, 17, 40, -30, 18, 43, -39, 20, 47, -47, + 21, 51, -55, 23, 55, -62, 25, 59, -69, 27, 63, -76, + 29, 67, -82, 31, 71, -88, 33, 74, -94, 35, 78, -100, + 14, 28, 20, 14, 29, 12, 15, 30, 0, 15, 32, -10, + 16, 35, -20, 17, 38, -29, 18, 42, -39, 20, 46, -47, + 22, 50, -55, 23, 54, -62, 25, 58, -69, 27, 62, -75, + 29, 66, -82, 31, 70, -88, 33, 74, -94, 35, 78, -100, + 15, 27, 20, 15, 27, 13, 15, 29, 1, 16, 31, -9, + 17, 34, -19, 18, 37, -29, 19, 41, -38, 20, 45, -46, + 22, 49, -54, 24, 53, -61, 25, 57, -68, 27, 61, -75, + 29, 65, -81, 31, 69, -87, 33, 73, -94, 35, 77, -100, + 15, 25, 21, 15, 25, 14, 16, 27, 2, 16, 29, -8, + 17, 32, -18, 18, 35, -28, 19, 39, -37, 21, 43, -45, + 22, 48, -53, 24, 52, -60, 26, 56, -68, 28, 60, -74, + 29, 64, -81, 31, 68, -87, 33, 72, -93, 35, 76, -99, + 16, 22, 22, 16, 23, 15, 17, 25, 3, 17, 27, -7, + 18, 30, -17, 19, 34, -27, 20, 38, -36, 21, 42, -44, + 23, 46, -52, 24, 50, -60, 26, 55, -67, 28, 59, -74, + 30, 63, -80, 32, 67, -86, 34, 71, -93, 36, 75, -99, + 17, 20, 23, 17, 21, 16, 17, 22, 5, 18, 25, -6, + 19, 28, -16, 20, 32, -25, 21, 36, -35, 22, 40, -43, + 24, 44, -51, 25, 48, -59, 27, 53, -66, 28, 57, -73, + 30, 62, -80, 32, 66, -86, 34, 70, -92, 36, 74, -98, + 18, 18, 24, 18, 19, 17, 18, 20, 6, 19, 23, -5, + 20, 26, -15, 20, 29, -24, 22, 34, -34, 23, 38, -42, + 24, 42, -50, 26, 47, -58, 27, 51, -65, 29, 56, -72, + 31, 60, -79, 33, 65, -85, 34, 69, -92, 36, 73, -97, + 19, 15, 26, 19, 16, 18, 19, 18, 7, 20, 20, -3, + 20, 23, -13, 21, 27, -23, 22, 31, -32, 24, 36, -41, + 25, 40, -49, 26, 45, -57, 28, 50, -64, 30, 54, -71, + 31, 59, -78, 33, 63, -84, 35, 67, -91, 37, 72, -97, + 20, 13, 27, 20, 14, 19, 20, 15, 8, 21, 18, -2, + 21, 21, -12, 22, 25, -21, 23, 29, -31, 24, 33, -39, + 26, 38, -48, 27, 43, -55, 29, 48, -63, 30, 52, -70, + 32, 57, -77, 34, 62, -83, 35, 66, -90, 37, 71, -96, + 21, 10, 28, 21, 10, 21, 22, 12, 10, 22, 15, 0, + 23, 18, -10, 23, 22, -19, 24, 26, -29, 25, 30, -38, + 27, 35, -46, 28, 40, -54, 30, 45, -62, 31, 50, -69, + 33, 54, -76, 34, 59, -82, 36, 64, -89, 38, 69, -95, + 22, 7, 30, 23, 8, 22, 23, 9, 12, 23, 12, 2, + 24, 15, -8, 25, 19, -18, 25, 23, -27, 26, 28, -36, + 28, 33, -45, 29, 38, -53, 30, 43, -60, 32, 48, -67, + 33, 52, -75, 35, 58, -81, 37, 62, -88, 38, 67, -94, + 24, 4, 31, 24, 5, 23, 24, 7, 13, 24, 9, 3, + 25, 13, -6, 26, 16, -16, 26, 21, -26, 27, 25, -35, + 29, 30, -43, 30, 35, -51, 31, 41, -59, 33, 46, -66, + 34, 50, -73, 36, 56, -80, 37, 60, -87, 39, 65, -93, + 25, 2, 32, 25, 2, 24, 25, 4, 15, 26, 7, 5, + 26, 10, -5, 27, 14, -14, 28, 18, -24, 28, 23, -33, + 30, 28, -42, 31, 33, -50, 32, 38, -58, 33, 43, -65, + 35, 48, -72, 36, 53, -79, 38, 58, -86, 40, 63, -92, + 26, 0, 33, 26, 0, 26, 26, 1, 16, 27, 4, 7, + 27, 7, -3, 28, 11, -13, 29, 16, -22, 29, 20, -31, + 31, 25, -40, 32, 30, -48, 33, 36, -56, 34, 41, -64, + 36, 46, -71, 37, 51, -78, 39, 56, -85, 40, 61, -91, + 27, -3, 34, 28, -2, 27, 28, 0, 18, 28, 2, 8, + 28, 5, -1, 29, 9, -11, 30, 13, -21, 31, 18, -30, + 32, 23, -39, 33, 28, -47, 34, 33, -55, 35, 39, -62, + 36, 44, -70, 38, 49, -76, 39, 54, -83, 41, 59, -90, + 29, -5, 35, 29, -4, 29, 29, -3, 20, 29, -1, 10, + 30, 2, 0, 30, 6, -9, 31, 10, -19, 32, 15, -28, + 33, 20, -37, 34, 25, -45, 35, 31, -53, 36, 36, -61, + 37, 41, -68, 39, 47, -75, 40, 52, -82, 42, 57, -89, + 30, -8, 36, 30, -7, 30, 30, -5, 21, 31, -3, 12, + 31, 0, 2, 31, 3, -7, 32, 8, -17, 33, 12, -26, + 34, 18, -35, 35, 23, -43, 36, 28, -52, 37, 34, -59, + 38, 39, -67, 40, 44, -74, 41, 50, -81, 43, 55, -87, + 31, -10, 37, 31, -9, 32, 32, -8, 23, 32, -5, 13, + 32, -3, 3, 33, 1, -5, 33, 5, -15, 34, 10, -24, + 35, 15, -33, 36, 20, -42, 37, 26, -50, 38, 31, -57, + 39, 36, -65, 41, 42, -72, 42, 47, -79, 43, 53, -86, + 33, -13, 39, 33, -12, 33, 33, -10, 24, 33, -8, 15, + 33, -5, 5, 34, -2, -4, 34, 3, -13, 35, 7, -22, + 36, 12, -32, 37, 18, -40, 38, 23, -48, 39, 29, -56, + 40, 34, -64, 42, 40, -71, 43, 45, -78, 44, 51, -85, + 34, -15, 40, 34, -14, 34, 34, -12, 26, 34, -10, 17, + 35, -7, 7, 35, -4, -2, 36, 0, -12, 36, 5, -21, + 37, 10, -30, 38, 15, -38, 39, 21, -47, 40, 26, -54, + 41, 31, -62, 42, 37, -69, 44, 43, -77, 45, 48, -83, + 35, -17, 41, 35, -16, 35, 35, -15, 28, 36, -12, 18, + 36, -10, 9, 36, -6, 0, 37, -2, -10, 38, 2, -19, + 38, 7, -28, 39, 12, -36, 40, 18, -45, 41, 23, -53, + 42, 29, -60, 43, 35, -68, 45, 40, -75, 46, 46, -82, + 37, -19, 42, 37, -18, 37, 37, -17, 29, 37, -15, 20, + 37, -12, 11, 38, -9, 1, 38, -5, -8, 39, 0, -17, + 39, 5, -26, 40, 10, -34, 41, 15, -43, 42, 21, -51, + 43, 26, -59, 44, 32, -66, 46, 38, -74, 47, 43, -80, + 38, -21, 43, 38, -20, 38, 38, -19, 31, 38, -17, 22, + 39, -14, 12, 39, -11, 3, 39, -7, -6, 40, -2, -15, + 41, 2, -24, 41, 7, -33, 42, 13, -41, 43, 18, -49, + 44, 24, -57, 45, 30, -65, 47, 35, -72, 48, 41, -79, + 39, -23, 44, 39, -22, 39, 39, -21, 32, 40, -19, 23, + 40, -16, 14, 40, -13, 5, 41, -9, -4, 41, -5, -13, + 42, 0, -22, 43, 5, -31, 43, 10, -40, 44, 16, -47, + 45, 21, -55, 46, 27, -63, 48, 33, -70, 49, 39, -77, + 41, -25, 45, 41, -24, 40, 41, -23, 34, 41, -21, 25, + 41, -19, 16, 41, -16, 7, 42, -12, -2, 42, -7, -11, + 43, -2, -20, 44, 2, -29, 45, 8, -38, 45, 13, -46, + 46, 19, -54, 48, 25, -61, 49, 30, -69, 50, 36, -76, + 42, -27, 46, 42, -26, 41, 42, -25, 35, 42, -23, 27, + 42, -21, 18, 43, -18, 9, 43, -14, 0, 44, -10, -9, + 44, -5, -19, 45, 0, -27, 46, 5, -36, 47, 11, -44, + 48, 16, -52, 49, 22, -60, 50, 28, -67, 51, 34, -74, + 43, -29, 47, 43, -28, 43, 43, -27, 36, 43, -25, 28, + 44, -23, 19, 44, -20, 10, 44, -16, 1, 45, -12, -7, + 46, -7, -17, 46, -2, -25, 47, 3, -34, 48, 8, -42, + 49, 14, -50, 50, 20, -58, 51, 25, -65, 52, 31, -73, + 44, -31, 48, 45, -30, 44, 45, -29, 38, 45, -27, 30, + 45, -25, 21, 45, -22, 12, 46, -18, 3, 46, -14, -6, + 47, -9, -15, 47, -4, -23, 48, 1, -32, 49, 6, -40, + 50, 11, -48, 51, 17, -56, 52, 23, -64, 53, 29, -71, + 46, -33, 49, 46, -32, 45, 46, -31, 39, 46, -29, 31, + 46, -27, 23, 47, -24, 14, 47, -20, 5, 47, -16, -4, + 48, -12, -13, 49, -7, -22, 49, -1, -30, 50, 3, -39, + 51, 9, -46, 52, 15, -54, 53, 20, -62, 54, 26, -69, + 47, -35, 50, 47, -34, 46, 47, -33, 40, 47, -31, 33, + 48, -29, 25, 48, -26, 16, 48, -22, 6, 49, -19, -2, + 49, -14, -11, 50, -9, -20, 50, -4, -29, 51, 1, -37, + 52, 6, -45, 53, 12, -53, 54, 18, -60, 55, 24, -68, + 49, -37, 52, 49, -36, 48, 49, -35, 42, 49, -33, 35, + 49, -31, 27, 50, -28, 18, 50, -25, 9, 50, -21, 0, + 51, -17, -9, 51, -12, -17, 52, -7, -26, 53, -1, -34, + 53, 3, -42, 54, 9, -50, 55, 15, -58, 56, 21, -66, + 50, -38, 53, 50, -38, 49, 50, -37, 43, 50, -35, 36, + 51, -33, 28, 51, -30, 20, 51, -27, 11, 52, -23, 2, + 52, -19, -7, 53, -14, -16, 53, -9, -24, 54, -4, -33, + 55, 1, -41, 55, 7, -49, 56, 13, -56, 57, 18, -64, + 51, -40, 54, 51, -40, 50, 52, -38, 45, 52, -37, 38, + 52, -35, 30, 52, -32, 22, 52, -29, 12, 53, -25, 4, + 53, -21, -5, 54, -16, -14, 54, -11, -23, 55, -6, -31, + 56, 0, -39, 57, 4, -47, 57, 10, -55, 58, 16, -62, + 53, -42, 55, 53, -41, 52, 53, -40, 46, 53, -38, 40, + 53, -36, 32, 53, -34, 23, 54, -31, 14, 54, -27, 5, + 55, -23, -3, 55, -18, -12, 56, -13, -21, 56, -8, -29, + 57, -3, -37, 58, 2, -45, 59, 8, -53, 59, 13, -60, + 54, -43, 56, 54, -43, 53, 54, -42, 47, 54, -40, 41, + 54, -38, 33, 55, -36, 25, 55, -33, 16, 55, -29, 7, + 56, -25, -2, 56, -21, -10, 57, -16, -19, 57, -11, -27, + 58, -5, -35, 59, 0, -43, 60, 5, -51, 61, 11, -59, + 55, -45, 57, 55, -44, 54, 55, -43, 49, 56, -42, 42, + 56, -40, 35, 56, -37, 27, 56, -34, 18, 57, -31, 9, + 57, -27, 0, 57, -23, -8, 58, -18, -17, 59, -13, -25, + 59, -7, -33, 60, -2, -42, 61, 3, -49, 62, 9, -57, + 57, -47, 58, 57, -46, 55, 57, -45, 50, 57, -43, 44, + 57, -41, 36, 57, -39, 28, 58, -36, 19, 58, -33, 11, + 58, -29, 2, 59, -25, -6, 59, -20, -15, 60, -15, -23, + 60, -9, -31, 61, -4, -40, 62, 1, -48, 63, 6, -55, + 58, -48, 59, 58, -47, 56, 58, -47, 51, 58, -45, 45, + 58, -43, 38, 59, -41, 30, 59, -38, 21, 59, -35, 13, + 60, -31, 4, 60, -27, -5, 60, -22, -13, 61, -17, -22, + 62, -12, -30, 62, -6, -38, 63, -1, -46, 64, 4, -54, + 59, -50, 60, 59, -49, 58, 59, -48, 53, 59, -47, 47, + 60, -45, 39, 60, -43, 32, 60, -40, 23, 60, -37, 14, + 61, -33, 5, 61, -29, -3, 62, -24, -12, 62, -19, -20, + 63, -14, -28, 63, -9, -36, 64, -3, -44, 65, 2, -52, + 61, -51, 61, 61, -51, 59, 61, -50, 54, 61, -48, 48, + 61, -46, 41, 61, -44, 33, 61, -41, 24, 62, -38, 16, + 62, -35, 7, 62, -31, -1, 63, -26, -10, 63, -21, -18, + 64, -16, -26, 65, -11, -34, 65, -5, -42, 66, 0, -50, + 62, -52, 62, 62, -52, 60, 62, -51, 55, 62, -50, 49, + 62, -48, 42, 62, -46, 35, 63, -43, 26, 63, -40, 18, + 63, -37, 9, 64, -33, 1, 64, -28, -8, 65, -23, -16, + 65, -18, -24, 66, -13, -33, 66, -7, -40, 67, -2, -48, + 63, -54, 63, 63, -53, 61, 63, -53, 56, 63, -51, 51, + 63, -49, 44, 64, -47, 36, 64, -45, 28, 64, -42, 20, + 64, -38, 11, 65, -35, 2, 65, -30, -6, 66, -25, -14, + 66, -20, -22, 67, -15, -31, 68, -10, -39, 68, -4, -46, + 64, -55, 64, 64, -55, 62, 64, -54, 58, 65, -53, 52, + 65, -51, 45, 65, -49, 38, 65, -46, 30, 65, -44, 21, + 66, -40, 13, 66, -36, 4, 67, -32, -4, 67, -27, -13, + 68, -22, -21, 68, -17, -29, 69, -12, -37, 69, -7, -45, + 66, -57, 65, 66, -56, 63, 66, -55, 59, 66, -54, 53, + 66, -52, 47, 66, -50, 39, 66, -48, 31, 67, -45, 23, + 67, -42, 14, 67, -38, 6, 68, -34, -3, 68, -29, -11, + 69, -24, -19, 69, -19, -27, 70, -14, -35, 71, -9, -43, + 67, -58, 66, 67, -58, 64, 67, -57, 60, 67, -56, 55, + 67, -54, 48, 67, -52, 41, 68, -50, 33, 68, -47, 25, + 68, -44, 16, 69, -40, 8, 69, -36, -1, 69, -31, -9, + 70, -26, -17, 70, -22, -25, 71, -16, -33, 72, -11, -41, + 68, -59, 67, 68, -59, 65, 68, -58, 61, 68, -57, 56, + 69, -55, 49, 69, -53, 42, 69, -51, 34, 69, -48, 26, + 69, -45, 18, 70, -42, 10, 70, -38, 1, 71, -33, -7, + 71, -28, -15, 72, -24, -24, 72, -18, -31, 73, -13, -39, + 69, -61, 68, 70, -60, 66, 70, -60, 63, 70, -58, 57, + 70, -57, 51, 70, -55, 44, 70, -53, 36, 70, -50, 28, + 71, -47, 19, 71, -43, 11, 71, -39, 3, 72, -35, -5, + 72, -30, -13, 73, -26, -22, 73, -20, -30, 74, -15, -38, + 71, -62, 69, 71, -62, 67, 71, -61, 64, 71, -60, 58, + 71, -58, 52, 71, -56, 45, 71, -54, 37, 72, -52, 30, + 72, -48, 21, 72, -45, 13, 73, -41, 4, 73, -37, -4, + 73, -32, -12, 74, -28, -20, 75, -22, -28, 75, -17, -36, + 72, -63, 70, 72, -63, 68, 72, -62, 65, 72, -61, 60, + 72, -60, 53, 72, -58, 47, 73, -56, 39, 73, -53, 31, + 73, -50, 23, 73, -47, 15, 74, -43, 6, 74, -39, -2, + 75, -34, -10, 75, -30, -18, 76, -24, -26, 76, -19, -34, + 73, -65, 71, 73, -64, 69, 73, -64, 66, 73, -63, 61, + 74, -61, 55, 74, -59, 48, 74, -57, 41, 74, -55, 33, + 74, -52, 24, 75, -48, 16, 75, -45, 8, 75, -41, 0, + 76, -36, -8, 76, -32, -16, 77, -26, -24, 77, -21, -32, + 75, -66, 72, 75, -66, 70, 75, -65, 67, 75, -64, 62, + 75, -62, 56, 75, -61, 50, 75, -59, 42, 75, -56, 34, + 76, -53, 26, 76, -50, 18, 76, -46, 10, 77, -42, 2, + 77, -38, -6, 78, -33, -15, 78, -28, -23, 79, -24, -31, + 76, -68, 73, 76, -67, 72, 76, -67, 69, 76, -66, 64, + 76, -64, 58, 76, -62, 51, 77, -60, 44, 77, -58, 36, + 77, -55, 28, 77, -52, 20, 78, -48, 12, 78, -45, 4, + 79, -40, -4, 79, -36, -13, 79, -31, -20, 80, -26, -28, + 77, -69, 74, 77, -69, 73, 77, -68, 70, 77, -67, 65, + 78, -66, 59, 78, -64, 53, 78, -62, 45, 78, -59, 38, + 78, -57, 30, 79, -54, 22, 79, -50, 13, 79, -46, 5, + 80, -42, -2, 80, -38, -11, 81, -33, -19, 81, -28, -27, + 79, -70, 75, 79, -70, 74, 79, -69, 71, 79, -68, 66, + 79, -67, 60, 79, -65, 54, 79, -63, 47, 79, -61, 39, + 80, -58, 31, 80, -55, 24, 80, -52, 15, 80, -48, 7, + 81, -44, -1, 81, -40, -9, 82, -35, -17, 82, -30, -25, + 80, -71, 76, 80, -71, 75, 80, -70, 72, 80, -69, 67, + 80, -68, 62, 80, -67, 55, 80, -64, 48, 81, -62, 41, + 81, -60, 33, 81, -57, 25, 81, -53, 17, 82, -50, 9, + 82, -46, 1, 83, -41, -7, 83, -37, -15, 83, -32, -23, + 81, -73, 77, 81, -72, 76, 81, -72, 73, 81, -71, 68, + 81, -69, 63, 81, -68, 57, 82, -66, 50, 82, -64, 43, + 82, -61, 35, 82, -58, 27, 83, -55, 18, 83, -51, 11, + 83, -47, 2, 84, -43, -6, 84, -38, -13, 85, -34, -21, + 82, -74, 78, 82, -74, 77, 82, -73, 74, 82, -72, 70, + 83, -71, 64, 83, -69, 58, 83, -67, 51, 83, -65, 44, + 83, -62, 36, 83, -60, 28, 84, -56, 20, 84, -53, 12, + 84, -49, 4, 85, -45, -4, 85, -40, -12, 86, -36, -20, + 84, -75, 79, 84, -75, 78, 84, -74, 75, 84, -73, 71, + 84, -72, 66, 84, -71, 60, 84, -69, 53, 84, -66, 46, + 84, -64, 38, 85, -61, 30, 85, -58, 22, 85, -55, 14, + 86, -51, 6, 86, -47, -2, 86, -42, -10, 87, -38, -18, + 85, -76, 80, 85, -76, 79, 85, -75, 76, 85, -74, 72, + 85, -73, 67, 85, -72, 61, 85, -70, 54, 85, -68, 47, + 86, -65, 39, 86, -63, 32, 86, -59, 23, 86, -56, 16, + 87, -52, 7, 87, -48, 0, 88, -44, -8, 88, -39, -16, + 86, -77, 81, 86, -77, 80, 86, -77, 77, 86, -76, 73, + 86, -75, 68, 86, -73, 62, 86, -71, 55, 87, -69, 48, + 87, -67, 41, 87, -64, 33, 87, -61, 25, 88, -58, 17, + 88, -54, 9, 88, -50, 1, 89, -46, -7, 89, -41, -15, + 87, -79, 82, 87, -78, 81, 87, -78, 78, 87, -77, 74, + 87, -76, 69, 88, -74, 63, 88, -73, 57, 88, -71, 50, + 88, -68, 42, 88, -66, 35, 89, -62, 27, 89, -59, 19, + 89, -56, 11, 90, -52, 3, 90, -47, -5, 90, -43, -13, + 88, -80, 83, 88, -80, 82, 88, -79, 79, 89, -78, 76, + 89, -77, 71, 89, -76, 65, 89, -74, 58, 89, -72, 51, + 89, -69, 44, 89, -67, 36, 90, -64, 28, 90, -61, 21, + 90, -57, 12, 91, -53, 5, 91, -49, -3, 92, -45, -11, + 14, 35, 20, 15, 36, 13, 15, 37, 1, 16, 38, -9, + 16, 41, -19, 17, 44, -29, 19, 47, -38, 20, 50, -46, + 22, 54, -54, 23, 57, -61, 25, 61, -68, 27, 65, -75, + 29, 68, -82, 31, 72, -88, 33, 76, -94, 35, 80, -100, + 15, 34, 21, 15, 34, 13, 15, 35, 2, 16, 37, -9, + 17, 40, -19, 18, 42, -28, 19, 46, -37, 21, 49, -46, + 22, 53, -54, 24, 56, -61, 26, 60, -68, 27, 64, -74, + 29, 68, -81, 31, 72, -87, 33, 75, -94, 35, 79, -99, + 15, 32, 22, 16, 33, 14, 16, 34, 3, 16, 36, -8, + 17, 38, -18, 18, 41, -27, 20, 45, -37, 21, 48, -45, + 22, 52, -53, 24, 56, -60, 26, 59, -68, 28, 63, -74, + 29, 67, -81, 31, 71, -87, 33, 75, -93, 35, 79, -99, + 16, 31, 22, 16, 32, 15, 16, 33, 3, 17, 35, -7, + 18, 37, -17, 19, 40, -27, 20, 44, -36, 21, 47, -44, + 23, 51, -53, 24, 55, -60, 26, 59, -67, 28, 62, -74, + 30, 66, -80, 32, 70, -87, 34, 74, -93, 36, 78, -99, + 16, 30, 23, 17, 30, 15, 17, 31, 4, 17, 33, -7, + 18, 36, -17, 19, 39, -26, 20, 42, -36, 22, 46, -44, + 23, 50, -52, 25, 54, -59, 26, 58, -67, 28, 62, -73, + 30, 66, -80, 32, 70, -86, 34, 74, -93, 36, 77, -99, + 17, 28, 24, 17, 28, 16, 17, 30, 5, 18, 32, -6, + 19, 34, -16, 20, 37, -25, 21, 41, -35, 22, 45, -43, + 24, 49, -51, 25, 53, -59, 27, 57, -66, 28, 61, -73, + 30, 65, -80, 32, 69, -86, 34, 73, -92, 36, 77, -98, + 18, 26, 25, 18, 27, 17, 18, 28, 6, 19, 30, -5, + 19, 33, -15, 20, 36, -24, 21, 39, -34, 23, 43, -42, + 24, 47, -51, 26, 51, -58, 27, 55, -65, 29, 59, -72, + 31, 64, -79, 32, 68, -85, 34, 72, -92, 36, 76, -98, + 18, 24, 26, 19, 24, 18, 19, 26, 7, 19, 28, -4, + 20, 31, -14, 21, 34, -23, 22, 38, -33, 23, 41, -41, + 25, 46, -50, 26, 50, -57, 28, 54, -65, 29, 58, -71, + 31, 62, -78, 33, 67, -85, 35, 71, -91, 37, 75, -97, + 19, 22, 27, 19, 22, 19, 20, 23, 8, 20, 26, -2, + 21, 28, -12, 22, 32, -22, 23, 36, -32, 24, 40, -40, + 25, 44, -49, 27, 48, -56, 28, 52, -64, 30, 57, -71, + 31, 61, -78, 33, 65, -84, 35, 69, -90, 37, 74, -96, + 20, 19, 28, 20, 20, 20, 21, 21, 9, 21, 23, -1, + 22, 26, -11, 23, 30, -21, 24, 34, -30, 25, 38, -39, + 26, 42, -48, 27, 46, -55, 29, 51, -63, 30, 55, -70, + 32, 59, -77, 34, 64, -83, 36, 68, -90, 37, 72, -96, + 21, 17, 29, 21, 17, 21, 22, 19, 10, 22, 21, 0, + 23, 24, -10, 23, 27, -19, 24, 31, -29, 25, 35, -38, + 27, 40, -46, 28, 44, -54, 29, 49, -62, 31, 53, -69, + 33, 58, -76, 34, 62, -82, 36, 67, -89, 38, 71, -95, + 23, 14, 30, 23, 14, 22, 23, 16, 12, 23, 18, 2, + 24, 21, -8, 25, 24, -18, 25, 28, -27, 26, 33, -36, + 28, 37, -45, 29, 42, -52, 30, 46, -60, 32, 51, -67, + 33, 55, -75, 35, 60, -81, 37, 65, -88, 39, 69, -94, + 24, 11, 31, 24, 12, 23, 24, 13, 13, 24, 15, 3, + 25, 18, -6, 26, 22, -16, 26, 26, -26, 27, 30, -35, + 29, 35, -43, 30, 39, -51, 31, 44, -59, 33, 49, -66, + 34, 53, -73, 36, 58, -80, 37, 63, -87, 39, 68, -93, + 25, 8, 32, 25, 9, 25, 25, 11, 15, 25, 13, 5, + 26, 16, -5, 27, 19, -14, 27, 24, -24, 28, 28, -33, + 29, 32, -42, 31, 37, -50, 32, 42, -58, 33, 47, -65, + 35, 51, -72, 36, 56, -79, 38, 61, -86, 40, 66, -92, + 26, 6, 33, 26, 7, 26, 26, 8, 16, 27, 10, 6, + 27, 13, -3, 28, 17, -13, 28, 21, -23, 29, 25, -31, + 30, 30, -40, 31, 35, -48, 33, 40, -56, 34, 45, -64, + 36, 49, -71, 37, 54, -78, 39, 59, -85, 40, 64, -91, + 27, 3, 34, 27, 4, 27, 27, 5, 18, 28, 8, 8, + 28, 11, -1, 29, 14, -11, 29, 18, -21, 30, 23, -30, + 31, 27, -39, 32, 32, -47, 34, 37, -55, 35, 42, -62, + 36, 47, -70, 38, 52, -77, 39, 57, -84, 41, 62, -90, + 28, 1, 35, 28, 2, 29, 29, 3, 19, 29, 5, 10, + 29, 8, 0, 30, 12, -9, 31, 16, -19, 31, 20, -28, + 32, 25, -37, 33, 30, -45, 35, 35, -54, 36, 40, -61, + 37, 45, -69, 39, 50, -75, 40, 55, -82, 42, 60, -89, + 30, -1, 36, 30, 0, 30, 30, 0, 21, 30, 3, 11, + 31, 6, 1, 31, 9, -8, 32, 13, -18, 32, 18, -27, + 33, 22, -36, 34, 27, -44, 36, 32, -52, 37, 38, -60, + 38, 43, -67, 39, 48, -74, 41, 53, -81, 42, 58, -88, + 31, -4, 37, 31, -3, 31, 31, -1, 22, 31, 0, 13, + 32, 3, 3, 32, 6, -6, 33, 11, -16, 34, 15, -25, + 34, 20, -34, 35, 25, -42, 37, 30, -51, 38, 35, -58, + 39, 40, -66, 40, 46, -73, 42, 51, -80, 43, 56, -86, + 32, -6, 38, 32, -5, 33, 32, -4, 24, 33, -2, 14, + 33, 0, 5, 33, 4, -4, 34, 8, -14, 35, 12, -23, + 36, 17, -32, 36, 22, -41, 38, 27, -49, 39, 33, -57, + 40, 38, -64, 41, 43, -71, 42, 48, -79, 44, 54, -85, + 33, -9, 40, 33, -8, 34, 34, -6, 26, 34, -4, 16, + 34, -2, 6, 35, 1, -2, 35, 6, -12, 36, 10, -21, + 37, 15, -30, 38, 20, -39, 39, 25, -47, 40, 30, -55, + 41, 35, -63, 42, 41, -70, 43, 46, -77, 45, 51, -84, + 35, -11, 41, 35, -10, 35, 35, -9, 27, 35, -7, 18, + 35, -4, 8, 36, -1, -1, 36, 3, -10, 37, 7, -19, + 38, 12, -29, 39, 17, -37, 40, 22, -46, 41, 28, -53, + 42, 33, -61, 43, 38, -68, 44, 44, -76, 46, 49, -83, + 36, -13, 42, 36, -12, 36, 36, -11, 29, 36, -9, 19, + 37, -6, 10, 37, -3, 0, 38, 1, -9, 38, 5, -18, + 39, 10, -27, 40, 14, -35, 41, 20, -44, 42, 25, -52, + 43, 30, -59, 44, 36, -67, 45, 41, -74, 47, 47, -81, + 37, -15, 43, 37, -15, 37, 37, -13, 30, 38, -11, 21, + 38, -9, 12, 38, -6, 2, 39, -2, -7, 39, 2, -16, + 40, 7, -25, 41, 12, -34, 42, 17, -42, 43, 23, -50, + 44, 28, -58, 45, 34, -65, 46, 39, -73, 47, 44, -80, + 38, -18, 44, 39, -17, 39, 39, -15, 32, 39, -13, 22, + 39, -11, 13, 40, -8, 4, 40, -4, -5, 41, 0, -14, + 41, 5, -23, 42, 9, -32, 43, 15, -41, 44, 20, -48, + 45, 25, -56, 46, 31, -64, 47, 36, -71, 48, 42, -78, + 40, -20, 45, 40, -19, 40, 40, -18, 33, 40, -16, 24, + 40, -13, 15, 41, -10, 6, 41, -7, -3, 42, -2, -12, + 42, 2, -22, 43, 7, -30, 44, 12, -39, 45, 17, -47, + 46, 23, -54, 47, 29, -62, 48, 34, -70, 49, 40, -77, + 41, -22, 46, 41, -21, 41, 41, -20, 34, 41, -18, 26, + 42, -15, 17, 42, -13, 8, 42, -9, -1, 43, -5, -10, + 44, 0, -20, 44, 4, -28, 45, 10, -37, 46, 15, -45, + 47, 20, -53, 48, 26, -60, 49, 32, -68, 50, 37, -75, + 42, -24, 47, 42, -23, 42, 43, -22, 36, 43, -20, 27, + 43, -18, 18, 43, -15, 9, 44, -11, 0, 44, -7, -9, + 45, -2, -18, 45, 2, -26, 46, 7, -35, 47, 12, -43, + 48, 18, -51, 49, 24, -59, 50, 29, -66, 51, 35, -74, + 44, -26, 48, 44, -25, 43, 44, -24, 37, 44, -22, 29, + 44, -20, 20, 45, -17, 11, 45, -13, 2, 45, -10, -7, + 46, -5, -16, 47, 0, -25, 47, 5, -33, 48, 10, -41, + 49, 15, -49, 50, 21, -57, 51, 27, -65, 52, 32, -72, + 45, -28, 49, 45, -27, 45, 45, -26, 38, 45, -24, 30, + 46, -22, 22, 46, -19, 13, 46, -16, 4, 47, -12, -5, + 47, -7, -14, 48, -2, -23, 49, 2, -32, 49, 8, -40, + 50, 13, -48, 51, 19, -55, 52, 24, -63, 53, 30, -70, + 46, -30, 50, 46, -29, 46, 46, -28, 40, 47, -26, 32, + 47, -24, 24, 47, -21, 15, 47, -18, 5, 48, -14, -3, + 48, -9, -12, 49, -5, -21, 50, 0, -30, 50, 5, -38, + 51, 10, -46, 52, 16, -54, 53, 22, -61, 54, 27, -69, + 48, -32, 51, 48, -31, 47, 48, -30, 41, 48, -28, 34, + 48, -26, 25, 48, -23, 16, 49, -20, 7, 49, -16, -1, + 50, -12, -11, 50, -7, -19, 51, -2, -28, 52, 3, -36, + 52, 8, -44, 53, 14, -52, 54, 19, -60, 55, 25, -67, + 49, -34, 52, 49, -33, 49, 49, -32, 43, 49, -30, 36, + 50, -28, 27, 50, -26, 19, 50, -22, 9, 51, -19, 1, + 51, -14, -8, 52, -10, -17, 52, -5, -26, 53, 0, -34, + 54, 5, -42, 55, 11, -50, 56, 16, -58, 57, 22, -65, + 51, -36, 53, 51, -35, 50, 51, -34, 44, 51, -32, 37, + 51, -30, 29, 51, -28, 20, 52, -24, 11, 52, -21, 2, + 52, -17, -6, 53, -12, -15, 54, -7, -24, 54, -2, -32, + 55, 3, -40, 56, 8, -48, 57, 14, -56, 58, 19, -63, + 52, -37, 54, 52, -37, 51, 52, -36, 45, 52, -34, 39, + 52, -32, 31, 52, -30, 22, 53, -26, 13, 53, -23, 4, + 54, -19, -5, 54, -14, -13, 55, -9, -22, 55, -4, -30, + 56, 0, -38, 57, 6, -46, 58, 11, -54, 59, 17, -62, + 53, -39, 55, 53, -38, 52, 53, -37, 47, 53, -36, 40, + 54, -34, 32, 54, -31, 24, 54, -28, 15, 54, -25, 6, + 55, -21, -3, 55, -17, -11, 56, -12, -20, 57, -7, -28, + 57, -1, -36, 58, 3, -45, 59, 9, -52, 60, 15, -60, + 54, -41, 56, 54, -40, 53, 55, -39, 48, 55, -38, 42, + 55, -36, 34, 55, -33, 25, 55, -30, 16, 56, -27, 8, + 56, -23, -1, 57, -19, -9, 57, -14, -18, 58, -9, -27, + 58, -3, -35, 59, 1, -43, 60, 7, -51, 61, 12, -58, + 56, -42, 57, 56, -42, 54, 56, -41, 49, 56, -39, 43, + 56, -37, 35, 56, -35, 27, 57, -32, 18, 57, -29, 10, + 57, -25, 1, 58, -21, -8, 58, -16, -17, 59, -11, -25, + 60, -6, -33, 60, -1, -41, 61, 4, -49, 62, 10, -57, + 57, -44, 58, 57, -43, 56, 57, -42, 50, 57, -41, 44, + 57, -39, 37, 58, -37, 29, 58, -34, 20, 58, -31, 11, + 59, -27, 2, 59, -23, -6, 60, -18, -15, 60, -13, -23, + 61, -8, -31, 61, -3, -39, 62, 2, -47, 63, 8, -55, + 58, -46, 59, 58, -45, 57, 58, -44, 52, 58, -43, 46, + 59, -41, 38, 59, -39, 30, 59, -36, 22, 59, -33, 13, + 60, -29, 4, 60, -25, -4, 61, -20, -13, 61, -16, -21, + 62, -10, -29, 63, -5, -37, 63, 0, -45, 64, 5, -53, + 60, -47, 60, 60, -47, 58, 60, -46, 53, 60, -44, 47, + 60, -42, 40, 60, -40, 32, 60, -38, 23, 61, -35, 15, + 61, -31, 6, 61, -27, -2, 62, -22, -11, 62, -18, -19, + 63, -12, -27, 64, -7, -36, 64, -2, -44, 65, 3, -51, + 61, -49, 61, 61, -48, 59, 61, -47, 54, 61, -46, 48, + 61, -44, 41, 61, -42, 34, 62, -39, 25, 62, -36, 17, + 62, -33, 8, 63, -29, 0, 63, -24, -9, 64, -20, -18, + 64, -15, -26, 65, -10, -34, 66, -4, -42, 66, 1, -50, + 62, -50, 62, 62, -50, 60, 62, -49, 56, 62, -47, 50, + 62, -46, 43, 63, -44, 35, 63, -41, 27, 63, -38, 18, + 64, -35, 9, 64, -31, 1, 64, -26, -8, 65, -22, -16, + 65, -17, -24, 66, -12, -32, 67, -6, -40, 67, -1, -48, + 63, -52, 63, 63, -51, 61, 63, -50, 57, 64, -49, 51, + 64, -47, 44, 64, -45, 37, 64, -43, 28, 64, -40, 20, + 65, -36, 11, 65, -33, 3, 66, -28, -6, 66, -24, -14, + 67, -19, -22, 67, -14, -30, 68, -8, -38, 69, -3, -46, + 65, -53, 64, 65, -53, 62, 65, -52, 58, 65, -51, 52, + 65, -49, 45, 65, -47, 38, 65, -44, 30, 66, -42, 22, + 66, -38, 13, 66, -35, 5, 67, -30, -4, 67, -26, -12, + 68, -21, -20, 68, -16, -29, 69, -11, -36, 70, -5, -44, + 66, -55, 65, 66, -54, 63, 66, -53, 59, 66, -52, 54, + 66, -50, 47, 66, -48, 40, 67, -46, 32, 67, -43, 23, + 67, -40, 15, 68, -36, 6, 68, -32, -2, 68, -28, -10, + 69, -23, -18, 70, -18, -27, 70, -13, -35, 71, -8, -43, + 67, -56, 66, 67, -56, 64, 67, -55, 60, 67, -54, 55, + 68, -52, 48, 68, -50, 41, 68, -48, 33, 68, -45, 25, + 68, -42, 16, 69, -38, 8, 69, -34, 0, 70, -30, -9, + 70, -25, -17, 71, -20, -25, 71, -15, -33, 72, -10, -41, + 68, -57, 67, 69, -57, 65, 69, -56, 62, 69, -55, 56, + 69, -54, 50, 69, -52, 43, 69, -49, 35, 69, -47, 27, + 70, -43, 18, 70, -40, 10, 70, -36, 1, 71, -32, -7, + 71, -27, -15, 72, -22, -23, 72, -17, -31, 73, -12, -39, + 70, -59, 68, 70, -58, 66, 70, -58, 63, 70, -57, 57, + 70, -55, 51, 70, -53, 44, 70, -51, 36, 71, -48, 28, + 71, -45, 20, 71, -42, 12, 72, -38, 3, 72, -34, -5, + 73, -29, -13, 73, -24, -21, 74, -19, -29, 74, -14, -37, + 71, -60, 69, 71, -60, 67, 71, -59, 64, 71, -58, 59, + 71, -56, 52, 71, -55, 46, 72, -52, 38, 72, -50, 30, + 72, -47, 21, 72, -44, 13, 73, -40, 5, 73, -36, -3, + 74, -31, -11, 74, -26, -20, 75, -21, -28, 75, -16, -36, + 72, -62, 70, 72, -61, 69, 72, -60, 65, 72, -59, 60, + 73, -58, 54, 73, -56, 47, 73, -54, 39, 73, -51, 32, + 73, -48, 23, 74, -45, 15, 74, -41, 6, 74, -38, -2, + 75, -33, -10, 75, -28, -18, 76, -23, -26, 77, -18, -34, + 74, -63, 71, 74, -63, 70, 74, -62, 66, 74, -61, 61, + 74, -59, 55, 74, -58, 48, 74, -55, 41, 74, -53, 33, + 75, -50, 25, 75, -47, 17, 75, -43, 8, 76, -39, 0, + 76, -35, -8, 77, -30, -16, 77, -25, -24, 78, -20, -32, + 75, -64, 72, 75, -64, 71, 75, -63, 68, 75, -62, 62, + 75, -61, 56, 75, -59, 50, 75, -57, 42, 76, -55, 35, + 76, -52, 26, 76, -49, 18, 76, -45, 10, 77, -41, 2, + 77, -37, -6, 78, -32, -14, 78, -27, -22, 79, -22, -30, + 76, -66, 74, 76, -66, 72, 76, -65, 69, 76, -64, 64, + 77, -63, 58, 77, -61, 52, 77, -59, 44, 77, -56, 37, + 77, -54, 28, 78, -51, 21, 78, -47, 12, 78, -43, 4, + 79, -39, -4, 79, -35, -12, 80, -30, -20, 80, -25, -28, + 78, -67, 75, 78, -67, 73, 78, -66, 70, 78, -65, 65, + 78, -64, 59, 78, -62, 53, 78, -60, 46, 78, -58, 38, + 79, -55, 30, 79, -52, 22, 79, -49, 14, 79, -45, 6, + 80, -41, -2, 80, -37, -10, 81, -32, -18, 81, -27, -26, + 79, -69, 76, 79, -68, 74, 79, -68, 71, 79, -67, 66, + 79, -65, 61, 79, -64, 54, 79, -62, 47, 80, -59, 40, + 80, -57, 32, 80, -54, 24, 80, -50, 15, 81, -47, 7, + 81, -43, 0, 82, -38, -9, 82, -34, -17, 83, -29, -25, + 80, -70, 77, 80, -69, 75, 80, -69, 72, 80, -68, 68, + 80, -67, 62, 80, -65, 56, 81, -63, 49, 81, -61, 41, + 81, -58, 33, 81, -55, 25, 82, -52, 17, 82, -48, 9, + 82, -44, 1, 83, -40, -7, 83, -35, -15, 84, -31, -23, + 81, -71, 78, 81, -71, 76, 81, -70, 73, 81, -69, 69, + 81, -68, 63, 82, -66, 57, 82, -64, 50, 82, -62, 43, + 82, -60, 35, 82, -57, 27, 83, -54, 19, 83, -50, 11, + 83, -46, 3, 84, -42, -5, 84, -37, -13, 85, -33, -21, + 82, -72, 79, 83, -72, 77, 83, -71, 74, 83, -70, 70, + 83, -69, 64, 83, -68, 58, 83, -66, 51, 83, -64, 44, + 83, -61, 36, 84, -58, 29, 84, -55, 20, 84, -52, 13, + 85, -48, 4, 85, -44, -4, 85, -39, -11, 86, -35, -19, + 84, -74, 80, 84, -73, 78, 84, -73, 75, 84, -72, 71, + 84, -71, 66, 84, -69, 60, 84, -67, 53, 84, -65, 46, + 85, -63, 38, 85, -60, 30, 85, -57, 22, 85, -53, 14, + 86, -50, 6, 86, -46, -2, 87, -41, -10, 87, -37, -18, + 85, -75, 81, 85, -74, 79, 85, -74, 76, 85, -73, 72, + 85, -72, 67, 85, -70, 61, 85, -68, 54, 86, -66, 47, + 86, -64, 39, 86, -61, 32, 86, -58, 24, 87, -55, 16, + 87, -51, 8, 87, -47, 0, 88, -43, -8, 88, -38, -16, + 86, -76, 82, 86, -76, 80, 86, -75, 78, 86, -74, 73, + 86, -73, 68, 86, -72, 62, 87, -70, 56, 87, -68, 49, + 87, -65, 41, 87, -63, 33, 88, -60, 25, 88, -56, 18, + 88, -53, 9, 89, -49, 1, 89, -45, -6, 89, -40, -14, + 87, -77, 83, 87, -77, 81, 87, -76, 79, 87, -76, 75, + 88, -74, 70, 88, -73, 64, 88, -71, 57, 88, -69, 50, + 88, -67, 42, 88, -64, 35, 89, -61, 27, 89, -58, 19, + 89, -55, 11, 90, -51, 3, 90, -46, -5, 91, -42, -13, + 89, -78, 84, 89, -78, 82, 89, -78, 80, 89, -77, 76, + 89, -76, 71, 89, -74, 65, 89, -72, 58, 89, -71, 52, + 89, -68, 44, 90, -66, 37, 90, -63, 28, 90, -60, 21, + 90, -56, 13, 91, -52, 5, 91, -48, -3, 92, -44, -11, + 16, 37, 23, 17, 38, 16, 17, 39, 4, 17, 40, -6, + 18, 42, -16, 19, 45, -26, 20, 48, -35, 22, 51, -44, + 23, 55, -52, 25, 58, -59, 26, 62, -67, 28, 65, -73, + 30, 69, -80, 32, 73, -86, 34, 76, -93, 36, 80, -98, + 17, 36, 24, 17, 37, 16, 17, 38, 5, 18, 39, -6, + 19, 41, -16, 20, 44, -25, 21, 47, -35, 22, 50, -43, + 23, 54, -51, 25, 57, -59, 27, 61, -66, 28, 64, -73, + 30, 68, -80, 32, 72, -86, 34, 76, -92, 36, 79, -98, + 17, 35, 24, 17, 36, 17, 18, 37, 5, 18, 38, -5, + 19, 40, -15, 20, 43, -25, 21, 46, -34, 22, 49, -43, + 24, 53, -51, 25, 56, -58, 27, 60, -66, 29, 64, -72, + 30, 68, -79, 32, 71, -86, 34, 75, -92, 36, 79, -98, + 18, 34, 25, 18, 34, 17, 18, 35, 6, 19, 37, -5, + 19, 39, -15, 20, 42, -24, 21, 45, -34, 23, 48, -42, + 24, 52, -51, 25, 56, -58, 27, 59, -65, 29, 63, -72, + 31, 67, -79, 32, 71, -85, 34, 75, -92, 36, 78, -98, + 18, 32, 26, 18, 33, 18, 19, 34, 7, 19, 36, -4, + 20, 38, -14, 21, 41, -24, 22, 44, -33, 23, 47, -42, + 24, 51, -50, 26, 55, -57, 27, 59, -65, 29, 62, -72, + 31, 66, -79, 33, 70, -85, 35, 74, -91, 36, 78, -97, + 19, 31, 26, 19, 31, 18, 19, 33, 7, 20, 34, -3, + 20, 37, -13, 21, 39, -23, 22, 43, -32, 23, 46, -41, + 25, 50, -49, 26, 54, -57, 28, 58, -64, 29, 61, -71, + 31, 65, -78, 33, 69, -84, 35, 73, -91, 37, 77, -97, + 19, 29, 27, 19, 30, 19, 20, 31, 8, 20, 32, -2, + 21, 35, -12, 22, 38, -22, 23, 41, -32, 24, 45, -40, + 25, 49, -49, 27, 52, -56, 28, 56, -64, 30, 60, -71, + 31, 64, -78, 33, 68, -84, 35, 72, -90, 37, 76, -96, + 20, 27, 28, 20, 28, 20, 20, 29, 9, 21, 31, -1, + 22, 33, -11, 22, 36, -21, 23, 39, -31, 24, 43, -39, + 26, 47, -48, 27, 51, -55, 29, 55, -63, 30, 59, -70, + 32, 63, -77, 34, 67, -83, 35, 71, -90, 37, 75, -96, + 21, 25, 29, 21, 26, 21, 21, 27, 10, 22, 29, 0, + 22, 31, -10, 23, 34, -20, 24, 38, -29, 25, 41, -38, + 26, 45, -47, 28, 49, -54, 29, 54, -62, 31, 58, -69, + 32, 62, -76, 34, 66, -83, 36, 70, -89, 38, 74, -95, + 22, 23, 30, 22, 23, 22, 22, 25, 11, 23, 26, 1, + 23, 29, -9, 24, 32, -19, 25, 36, -28, 26, 39, -37, + 27, 44, -46, 28, 48, -53, 30, 52, -61, 31, 56, -68, + 33, 60, -75, 35, 65, -82, 36, 69, -89, 38, 73, -95, + 23, 21, 31, 23, 21, 23, 23, 22, 12, 23, 24, 2, + 24, 27, -8, 25, 30, -17, 26, 34, -27, 27, 37, -36, + 28, 42, -45, 29, 46, -52, 30, 50, -60, 32, 54, -67, + 33, 59, -74, 35, 63, -81, 37, 67, -88, 39, 72, -94, + 24, 18, 32, 24, 18, 24, 24, 19, 14, 25, 21, 4, + 25, 24, -6, 26, 27, -16, 27, 31, -25, 28, 35, -34, + 29, 39, -43, 30, 43, -51, 31, 48, -59, 33, 52, -66, + 34, 56, -73, 36, 61, -80, 37, 65, -87, 39, 70, -93, + 25, 15, 33, 25, 16, 25, 25, 17, 15, 26, 19, 5, + 26, 21, -4, 27, 25, -14, 27, 28, -24, 28, 32, -33, + 30, 37, -42, 31, 41, -50, 32, 46, -58, 33, 50, -65, + 35, 55, -72, 36, 59, -79, 38, 64, -86, 40, 68, -92, + 26, 13, 34, 26, 13, 26, 26, 14, 17, 27, 16, 7, + 27, 19, -3, 28, 22, -13, 28, 26, -23, 29, 30, -31, + 30, 34, -40, 31, 39, -48, 33, 44, -56, 34, 48, -64, + 36, 53, -71, 37, 57, -78, 39, 62, -85, 40, 67, -91, + 27, 10, 35, 27, 11, 27, 27, 12, 18, 28, 14, 8, + 28, 17, -1, 29, 20, -11, 29, 24, -21, 30, 28, -30, + 31, 32, -39, 32, 36, -47, 34, 41, -55, 35, 46, -62, + 36, 51, -70, 38, 55, -77, 39, 60, -84, 41, 65, -90, + 28, 8, 36, 28, 8, 29, 28, 9, 19, 29, 11, 9, + 29, 14, 0, 30, 17, -10, 30, 21, -19, 31, 25, -28, + 32, 30, -37, 33, 34, -46, 34, 39, -54, 36, 44, -61, + 37, 48, -69, 38, 53, -76, 40, 58, -83, 42, 63, -89, + 29, 5, 37, 29, 6, 30, 30, 7, 21, 30, 9, 11, + 30, 11, 1, 31, 15, -8, 31, 19, -18, 32, 23, -27, + 33, 27, -36, 34, 32, -44, 35, 37, -52, 37, 41, -60, + 38, 46, -67, 39, 51, -74, 41, 56, -81, 42, 61, -88, + 30, 3, 38, 31, 3, 31, 31, 4, 22, 31, 6, 13, + 31, 9, 2, 32, 12, -6, 33, 16, -16, 33, 20, -25, + 34, 25, -34, 35, 29, -43, 36, 34, -51, 37, 39, -58, + 39, 44, -66, 40, 49, -73, 41, 54, -80, 43, 59, -87, + 32, 0, 38, 32, 1, 32, 32, 2, 24, 32, 4, 14, + 33, 6, 4, 33, 10, -5, 34, 14, -15, 34, 18, -23, + 35, 22, -33, 36, 27, -41, 37, 32, -49, 38, 37, -57, + 40, 42, -65, 41, 47, -72, 42, 52, -79, 44, 57, -86, + 33, -2, 39, 33, -1, 34, 33, 0, 25, 33, 1, 16, + 34, 4, 6, 34, 7, -3, 35, 11, -13, 35, 15, -22, + 36, 20, -31, 37, 24, -39, 38, 29, -48, 39, 34, -55, + 40, 39, -63, 42, 44, -70, 43, 49, -78, 45, 55, -84, + 34, -5, 40, 34, -4, 35, 34, -2, 27, 35, 0, 17, + 35, 1, 7, 35, 5, -1, 36, 8, -11, 37, 13, -20, + 37, 17, -29, 38, 22, -38, 39, 27, -46, 40, 32, -54, + 41, 37, -62, 43, 42, -69, 44, 47, -76, 45, 52, -83, + 35, -7, 42, 35, -6, 36, 36, -5, 28, 36, -3, 19, + 36, 0, 9, 37, 2, 0, 37, 6, -9, 38, 10, -18, + 38, 15, -28, 39, 19, -36, 40, 24, -45, 41, 29, -52, + 42, 34, -60, 44, 40, -67, 45, 45, -75, 46, 50, -82, + 37, -9, 43, 37, -8, 37, 37, -7, 30, 37, -5, 20, + 37, -3, 11, 38, 0, 2, 38, 3, -8, 39, 7, -17, + 40, 12, -26, 40, 17, -34, 41, 22, -43, 42, 27, -51, + 43, 32, -59, 44, 37, -66, 46, 42, -73, 47, 48, -80, + 38, -12, 44, 38, -11, 38, 38, -9, 31, 38, -8, 22, + 39, -5, 13, 39, -2, 3, 39, 1, -6, 40, 5, -15, + 41, 10, -24, 41, 14, -33, 42, 19, -41, 43, 24, -49, + 44, 29, -57, 45, 35, -64, 47, 40, -72, 48, 46, -79, + 39, -14, 45, 39, -13, 39, 39, -12, 32, 39, -10, 23, + 40, -8, 14, 40, -5, 5, 41, -1, -4, 41, 3, -13, + 42, 7, -22, 43, 12, -31, 43, 17, -40, 44, 22, -47, + 45, 27, -55, 46, 32, -63, 48, 38, -70, 49, 43, -77, + 40, -16, 46, 40, -15, 41, 41, -14, 34, 41, -12, 25, + 41, -10, 16, 41, -7, 7, 42, -4, -2, 42, 0, -11, + 43, 5, -21, 44, 9, -29, 45, 14, -38, 45, 19, -46, + 46, 24, -54, 47, 30, -61, 49, 35, -69, 50, 41, -76, + 42, -18, 47, 42, -17, 42, 42, -16, 35, 42, -14, 27, + 42, -12, 18, 43, -9, 9, 43, -6, -1, 44, -2, -9, + 44, 2, -19, 45, 7, -27, 46, 12, -36, 46, 17, -44, + 47, 22, -52, 48, 28, -60, 49, 33, -67, 51, 38, -74, + 43, -20, 48, 43, -20, 43, 43, -18, 37, 43, -17, 28, + 44, -14, 19, 44, -12, 10, 44, -8, 1, 45, -5, -8, + 45, 0, -17, 46, 4, -26, 47, 9, -34, 48, 14, -42, + 48, 19, -50, 49, 25, -58, 50, 30, -66, 52, 36, -73, + 44, -22, 49, 44, -22, 44, 44, -20, 38, 45, -19, 30, + 45, -17, 21, 45, -14, 12, 45, -11, 3, 46, -7, -6, + 47, -2, -15, 47, 2, -24, 48, 7, -33, 49, 12, -41, + 50, 17, -49, 50, 23, -56, 51, 28, -64, 53, 34, -71, + 45, -24, 50, 46, -24, 45, 46, -22, 39, 46, -21, 31, + 46, -19, 23, 46, -16, 14, 47, -13, 4, 47, -9, -4, + 48, -5, -13, 48, 0, -22, 49, 4, -31, 50, 9, -39, + 51, 15, -47, 52, 20, -55, 53, 25, -62, 54, 31, -70, + 47, -26, 51, 47, -26, 46, 47, -25, 40, 47, -23, 33, + 47, -21, 24, 48, -18, 15, 48, -15, 6, 48, -11, -2, + 49, -7, -12, 49, -3, -20, 50, 2, -29, 51, 7, -37, + 52, 12, -45, 53, 18, -53, 54, 23, -61, 55, 29, -68, + 48, -28, 52, 48, -28, 48, 48, -27, 42, 48, -25, 34, + 49, -23, 26, 49, -20, 17, 49, -17, 8, 50, -14, -1, + 50, -9, -10, 51, -5, -18, 51, 0, -27, 52, 5, -35, + 53, 10, -43, 54, 15, -51, 55, 21, -59, 56, 26, -67, + 50, -31, 53, 50, -30, 49, 50, -29, 43, 50, -27, 36, + 50, -25, 28, 50, -23, 19, 51, -20, 10, 51, -16, 1, + 52, -12, -8, 52, -8, -16, 53, -3, -25, 53, 2, -33, + 54, 7, -41, 55, 12, -49, 56, 17, -57, 57, 23, -64, + 51, -32, 54, 51, -32, 50, 51, -31, 45, 51, -29, 38, + 51, -27, 30, 52, -25, 21, 52, -22, 12, 52, -19, 3, + 53, -14, -6, 53, -10, -14, 54, -5, -23, 55, 0, -31, + 55, 4, -39, 56, 10, -47, 57, 15, -55, 58, 21, -63, + 52, -34, 55, 52, -34, 51, 52, -33, 46, 52, -31, 39, + 53, -29, 31, 53, -27, 23, 53, -24, 14, 54, -21, 5, + 54, -17, -4, 55, -12, -12, 55, -7, -21, 56, -3, -30, + 56, 2, -38, 57, 7, -46, 58, 13, -54, 59, 18, -61, + 54, -36, 56, 54, -35, 53, 54, -35, 47, 54, -33, 41, + 54, -31, 33, 54, -29, 24, 54, -26, 15, 55, -23, 7, + 55, -19, -2, 56, -15, -11, 56, -10, -20, 57, -5, -28, + 58, 0, -36, 58, 5, -44, 59, 10, -52, 60, 16, -59, + 55, -38, 57, 55, -37, 54, 55, -36, 48, 55, -35, 42, + 55, -33, 34, 55, -31, 26, 56, -28, 17, 56, -25, 8, + 56, -21, 0, 57, -17, -9, 58, -12, -18, 58, -7, -26, + 59, -2, -34, 60, 3, -42, 60, 8, -50, 61, 14, -58, + 56, -40, 58, 56, -39, 55, 56, -38, 50, 56, -37, 43, + 56, -35, 36, 57, -33, 28, 57, -30, 19, 57, -27, 10, + 58, -23, 1, 58, -19, -7, 59, -14, -16, 59, -9, -24, + 60, -4, -32, 61, 0, -40, 61, 6, -48, 62, 11, -56, + 57, -41, 59, 57, -41, 56, 57, -40, 51, 58, -38, 45, + 58, -36, 37, 58, -34, 29, 58, -32, 20, 59, -29, 12, + 59, -25, 3, 59, -21, -5, 60, -16, -14, 60, -12, -22, + 61, -6, -30, 62, -1, -39, 63, 3, -47, 63, 9, -54, + 59, -43, 60, 59, -42, 57, 59, -41, 52, 59, -40, 46, + 59, -38, 39, 59, -36, 31, 59, -33, 22, 60, -30, 14, + 60, -27, 5, 61, -23, -4, 61, -18, -12, 62, -14, -21, + 62, -9, -29, 63, -4, -37, 64, 1, -45, 64, 7, -53, + 60, -45, 61, 60, -44, 58, 60, -43, 54, 60, -42, 48, + 60, -40, 40, 60, -38, 33, 61, -35, 24, 61, -32, 15, + 61, -29, 6, 62, -25, -2, 62, -21, -11, 63, -16, -19, + 63, -11, -27, 64, -6, -35, 65, 0, -43, 66, 4, -51, + 61, -46, 62, 61, -46, 59, 61, -45, 55, 61, -43, 49, + 62, -42, 42, 62, -40, 34, 62, -37, 25, 62, -34, 17, + 63, -31, 8, 63, -27, 0, 63, -23, -9, 64, -18, -17, + 65, -13, -25, 65, -8, -33, 66, -3, -41, 67, 2, -49, + 62, -48, 63, 62, -47, 60, 63, -46, 56, 63, -45, 50, + 63, -43, 43, 63, -41, 36, 63, -39, 27, 63, -36, 19, + 64, -33, 10, 64, -29, 2, 65, -25, -7, 65, -20, -15, + 66, -15, -23, 66, -10, -32, 67, -5, -40, 68, 0, -47, + 64, -49, 64, 64, -49, 62, 64, -48, 57, 64, -47, 51, + 64, -45, 44, 64, -43, 37, 64, -41, 29, 65, -38, 20, + 65, -34, 12, 65, -31, 3, 66, -27, -5, 66, -22, -13, + 67, -17, -22, 68, -12, -30, 68, -7, -38, 69, -2, -46, + 65, -51, 65, 65, -50, 63, 65, -50, 58, 65, -48, 53, + 65, -47, 46, 65, -45, 39, 66, -42, 30, 66, -40, 22, + 66, -36, 13, 67, -33, 5, 67, -29, -4, 68, -24, -12, + 68, -19, -20, 69, -15, -28, 69, -9, -36, 70, -4, -44, + 66, -52, 66, 66, -52, 64, 66, -51, 60, 66, -50, 54, + 67, -48, 47, 67, -46, 40, 67, -44, 32, 67, -41, 24, + 67, -38, 15, 68, -35, 7, 68, -31, -2, 69, -26, -10, + 69, -22, -18, 70, -17, -26, 70, -12, -34, 71, -6, -42, + 67, -54, 67, 68, -53, 65, 68, -53, 61, 68, -51, 55, + 68, -50, 49, 68, -48, 42, 68, -46, 34, 68, -43, 26, + 69, -40, 17, 69, -36, 9, 69, -33, 0, 70, -28, -8, + 70, -24, -16, 71, -19, -25, 72, -14, -32, 72, -9, -40, + 69, -55, 68, 69, -55, 66, 69, -54, 62, 69, -53, 56, + 69, -51, 50, 69, -50, 43, 69, -47, 35, 70, -45, 27, + 70, -42, 19, 70, -38, 10, 71, -34, 2, 71, -30, -6, + 72, -26, -14, 72, -21, -23, 73, -16, -31, 73, -11, -39, + 70, -57, 69, 70, -56, 67, 70, -56, 63, 70, -54, 58, + 70, -53, 51, 70, -51, 45, 71, -49, 37, 71, -46, 29, + 71, -43, 20, 71, -40, 12, 72, -36, 3, 72, -32, -5, + 73, -28, -13, 73, -23, -21, 74, -18, -29, 74, -13, -37, + 71, -58, 70, 71, -58, 68, 71, -57, 64, 71, -56, 59, + 72, -54, 53, 72, -53, 46, 72, -50, 38, 72, -48, 30, + 72, -45, 22, 73, -42, 14, 73, -38, 5, 73, -34, -3, + 74, -30, -11, 74, -25, -19, 75, -20, -27, 76, -15, -35, + 73, -60, 71, 73, -59, 69, 73, -58, 66, 73, -57, 60, + 73, -56, 54, 73, -54, 47, 73, -52, 40, 73, -50, 32, + 74, -47, 24, 74, -44, 15, 74, -40, 7, 75, -36, -1, + 75, -31, -9, 76, -27, -18, 76, -22, -25, 77, -17, -33, + 74, -61, 72, 74, -61, 70, 74, -60, 67, 74, -59, 61, + 74, -57, 55, 74, -56, 49, 74, -54, 41, 75, -51, 34, + 75, -48, 25, 75, -45, 17, 75, -42, 9, 76, -38, 1, + 76, -33, -7, 77, -29, -16, 77, -24, -24, 78, -19, -32, + 75, -62, 73, 75, -62, 71, 75, -61, 68, 75, -60, 63, + 75, -59, 57, 75, -57, 50, 76, -55, 43, 76, -53, 35, + 76, -50, 27, 76, -47, 19, 77, -43, 10, 77, -40, 2, + 77, -35, -6, 78, -31, -14, 78, -26, -22, 79, -21, -30, + 77, -64, 74, 77, -64, 72, 77, -63, 69, 77, -62, 64, + 77, -61, 58, 77, -59, 52, 77, -57, 45, 77, -55, 37, + 78, -52, 29, 78, -49, 21, 78, -45, 12, 79, -42, 4, + 79, -38, -4, 79, -33, -12, 80, -29, -20, 80, -24, -28, + 78, -65, 75, 78, -65, 73, 78, -64, 70, 78, -63, 65, + 78, -62, 60, 78, -60, 53, 78, -58, 46, 79, -56, 39, + 79, -53, 30, 79, -51, 23, 79, -47, 14, 80, -44, 6, + 80, -39, -2, 81, -35, -10, 81, -30, -18, 82, -26, -26, + 79, -67, 76, 79, -66, 74, 79, -66, 71, 79, -65, 67, + 79, -64, 61, 79, -62, 55, 80, -60, 47, 80, -58, 40, + 80, -55, 32, 80, -52, 24, 81, -49, 16, 81, -45, 8, + 81, -41, 0, 82, -37, -8, 82, -32, -16, 83, -28, -24, + 80, -68, 77, 80, -68, 75, 80, -67, 72, 80, -66, 68, + 80, -65, 62, 81, -63, 56, 81, -61, 49, 81, -59, 42, + 81, -57, 34, 81, -54, 26, 82, -50, 17, 82, -47, 10, + 82, -43, 1, 83, -39, -7, 83, -34, -15, 84, -30, -23, + 81, -69, 78, 81, -69, 76, 82, -68, 74, 82, -67, 69, + 82, -66, 63, 82, -65, 57, 82, -63, 50, 82, -61, 43, + 82, -58, 35, 83, -55, 27, 83, -52, 19, 83, -49, 11, + 84, -45, 3, 84, -41, -5, 85, -36, -13, 85, -32, -21, + 83, -71, 79, 83, -70, 77, 83, -70, 75, 83, -69, 70, + 83, -68, 65, 83, -66, 59, 83, -64, 52, 83, -62, 45, + 84, -60, 37, 84, -57, 29, 84, -54, 21, 84, -50, 13, + 85, -47, 5, 85, -43, -3, 86, -38, -11, 86, -34, -19, + 84, -72, 80, 84, -72, 78, 84, -71, 76, 84, -70, 71, + 84, -69, 66, 84, -67, 60, 84, -66, 53, 85, -64, 46, + 85, -61, 38, 85, -58, 31, 85, -55, 22, 86, -52, 15, + 86, -48, 6, 86, -44, -2, 87, -40, -9, 87, -35, -17, + 85, -73, 81, 85, -73, 79, 85, -72, 77, 85, -71, 73, + 85, -70, 67, 85, -69, 61, 86, -67, 54, 86, -65, 48, + 86, -63, 40, 86, -60, 32, 86, -57, 24, 87, -54, 16, + 87, -50, 8, 88, -46, 0, 88, -42, -8, 88, -37, -16, + 86, -74, 82, 86, -74, 80, 86, -74, 78, 86, -73, 74, + 87, -72, 69, 87, -70, 63, 87, -68, 56, 87, -66, 49, + 87, -64, 41, 87, -61, 34, 88, -58, 26, 88, -55, 18, + 88, -52, 10, 89, -48, 2, 89, -43, -6, 90, -39, -14, + 88, -76, 83, 88, -75, 81, 88, -75, 79, 88, -74, 75, + 88, -73, 70, 88, -72, 64, 88, -70, 57, 88, -68, 50, + 88, -65, 43, 89, -63, 35, 89, -60, 27, 89, -57, 19, + 89, -53, 11, 90, -49, 3, 90, -45, -4, 91, -41, -12, + 89, -77, 84, 89, -77, 82, 89, -76, 80, 89, -75, 76, + 89, -74, 71, 89, -73, 65, 89, -71, 59, 89, -69, 52, + 90, -67, 44, 90, -64, 37, 90, -61, 29, 90, -58, 21, + 91, -55, 13, 91, -51, 5, 91, -47, -3, 92, -43, -11, + 19, 40, 27, 19, 40, 19, 19, 41, 8, 20, 43, -3, + 20, 44, -13, 21, 47, -23, 22, 49, -32, 23, 52, -41, + 25, 56, -49, 26, 59, -57, 28, 62, -64, 29, 66, -71, + 31, 70, -78, 33, 73, -84, 35, 77, -91, 37, 80, -97, + 19, 39, 27, 19, 39, 19, 19, 40, 8, 20, 42, -3, + 21, 43, -13, 21, 46, -22, 22, 49, -32, 24, 52, -40, + 25, 55, -49, 26, 58, -56, 28, 62, -64, 30, 65, -71, + 31, 69, -78, 33, 73, -84, 35, 76, -91, 37, 80, -97, + 19, 38, 27, 19, 38, 20, 20, 39, 9, 20, 41, -2, + 21, 42, -12, 22, 45, -22, 23, 48, -31, 24, 51, -40, + 25, 54, -49, 27, 57, -56, 28, 61, -64, 30, 65, -70, + 31, 68, -77, 33, 72, -84, 35, 76, -90, 37, 79, -96, + 20, 37, 28, 20, 37, 20, 20, 38, 9, 21, 40, -2, + 21, 41, -12, 22, 44, -21, 23, 47, -31, 24, 50, -40, + 26, 53, -48, 27, 57, -56, 28, 60, -63, 30, 64, -70, + 32, 68, -77, 34, 71, -84, 35, 75, -90, 37, 79, -96, + 20, 36, 28, 20, 36, 21, 21, 37, 10, 21, 38, -1, + 22, 40, -11, 22, 43, -21, 23, 46, -30, 25, 49, -39, + 26, 52, -48, 27, 56, -55, 29, 60, -63, 30, 63, -70, + 32, 67, -77, 34, 71, -83, 35, 75, -90, 37, 78, -96, + 21, 34, 29, 21, 35, 21, 21, 36, 10, 21, 37, 0, + 22, 39, -10, 23, 42, -20, 24, 45, -30, 25, 48, -38, + 26, 51, -47, 28, 55, -55, 29, 59, -62, 31, 62, -69, + 32, 66, -76, 34, 70, -83, 36, 74, -89, 38, 78, -95, + 21, 33, 30, 21, 33, 22, 22, 34, 11, 22, 35, 0, + 23, 38, -9, 23, 40, -19, 24, 43, -29, 25, 46, -38, + 27, 50, -46, 28, 54, -54, 29, 58, -62, 31, 61, -69, + 33, 65, -76, 34, 69, -82, 36, 73, -89, 38, 77, -95, + 22, 31, 30, 22, 31, 22, 22, 32, 12, 23, 34, 1, + 23, 36, -9, 24, 38, -18, 25, 42, -28, 26, 45, -37, + 27, 49, -45, 28, 52, -53, 30, 56, -61, 31, 60, -68, + 33, 64, -75, 35, 68, -82, 36, 72, -88, 38, 76, -94, + 23, 29, 31, 23, 29, 23, 23, 30, 13, 23, 32, 2, + 24, 34, -7, 25, 37, -17, 26, 40, -27, 27, 43, -36, + 28, 47, -45, 29, 51, -52, 30, 55, -60, 32, 59, -67, + 33, 63, -74, 35, 67, -81, 37, 71, -88, 39, 75, -94, + 23, 27, 32, 24, 27, 24, 24, 28, 14, 24, 30, 3, + 25, 32, -6, 25, 35, -16, 26, 38, -26, 27, 41, -35, + 28, 45, -44, 30, 49, -51, 31, 53, -59, 32, 57, -66, + 34, 61, -74, 36, 66, -80, 37, 70, -87, 39, 74, -93, + 24, 25, 33, 24, 25, 25, 25, 26, 15, 25, 28, 5, + 25, 30, -5, 26, 33, -15, 27, 36, -25, 28, 40, -34, + 29, 44, -43, 30, 47, -50, 32, 52, -58, 33, 56, -66, + 34, 60, -73, 36, 64, -79, 38, 68, -86, 39, 72, -93, + 25, 22, 34, 25, 22, 26, 26, 23, 16, 26, 25, 6, + 27, 27, -4, 27, 30, -13, 28, 33, -23, 29, 37, -32, + 30, 41, -41, 31, 45, -49, 32, 49, -57, 34, 53, -64, + 35, 58, -72, 37, 62, -78, 38, 66, -85, 40, 71, -92, + 26, 19, 35, 26, 20, 27, 27, 21, 17, 27, 23, 7, + 27, 25, -2, 28, 28, -12, 29, 31, -22, 30, 35, -31, + 31, 39, -40, 32, 43, -48, 33, 47, -56, 34, 52, -63, + 36, 56, -71, 37, 60, -77, 39, 65, -84, 41, 69, -91, + 27, 17, 36, 27, 18, 28, 28, 19, 19, 28, 20, 9, + 28, 23, -1, 29, 25, -11, 30, 29, -21, 30, 33, -29, + 31, 37, -39, 33, 41, -47, 34, 45, -55, 35, 50, -62, + 36, 54, -70, 38, 59, -76, 39, 63, -83, 41, 67, -90, + 28, 15, 36, 28, 15, 29, 29, 16, 20, 29, 18, 10, + 29, 20, 0, 30, 23, -9, 31, 27, -19, 31, 30, -28, + 32, 35, -37, 33, 39, -45, 35, 43, -54, 36, 48, -61, + 37, 52, -68, 39, 57, -75, 40, 61, -82, 42, 66, -89, + 29, 12, 37, 29, 13, 30, 30, 14, 21, 30, 15, 11, + 30, 18, 1, 31, 21, -8, 32, 24, -18, 32, 28, -27, + 33, 32, -36, 34, 36, -44, 35, 41, -52, 37, 45, -60, + 38, 50, -67, 39, 55, -74, 41, 59, -81, 42, 64, -88, + 30, 10, 38, 31, 10, 32, 31, 11, 23, 31, 13, 13, + 31, 15, 3, 32, 18, -6, 33, 22, -16, 33, 26, -25, + 34, 30, -34, 35, 34, -43, 36, 39, -51, 37, 43, -58, + 39, 48, -66, 40, 53, -73, 41, 57, -80, 43, 62, -87, + 32, 7, 39, 32, 8, 33, 32, 9, 24, 32, 10, 14, + 32, 13, 4, 33, 16, -5, 34, 19, -15, 34, 23, -24, + 35, 27, -33, 36, 32, -41, 37, 36, -49, 38, 41, -57, + 39, 46, -65, 41, 50, -72, 42, 55, -79, 44, 60, -86, + 33, 5, 40, 33, 5, 34, 33, 6, 25, 33, 8, 16, + 34, 10, 6, 34, 13, -3, 35, 17, -13, 35, 21, -22, + 36, 25, -31, 37, 29, -40, 38, 34, -48, 39, 39, -56, + 40, 43, -63, 42, 48, -70, 43, 53, -78, 44, 58, -84, + 34, 2, 41, 34, 3, 35, 34, 4, 27, 34, 5, 17, + 35, 8, 7, 35, 11, -1, 36, 14, -11, 36, 18, -20, + 37, 22, -30, 38, 27, -38, 39, 32, -46, 40, 36, -54, + 41, 41, -62, 42, 46, -69, 44, 51, -76, 45, 56, -83, + 35, 0, 42, 35, 0, 36, 35, 1, 28, 36, 3, 19, + 36, 5, 9, 36, 8, 0, 37, 12, -10, 37, 16, -19, + 38, 20, -28, 39, 24, -36, 40, 29, -45, 41, 34, -53, + 42, 39, -60, 43, 44, -68, 45, 49, -75, 46, 54, -82, + 36, -2, 43, 36, -2, 37, 36, 0, 29, 37, 0, 20, + 37, 3, 11, 37, 6, 1, 38, 9, -8, 39, 13, -17, + 39, 17, -26, 40, 22, -35, 41, 27, -43, 42, 31, -51, + 43, 36, -59, 44, 41, -66, 45, 46, -74, 47, 51, -81, + 37, -5, 44, 38, -4, 38, 38, -3, 31, 38, -1, 22, + 38, 0, 12, 39, 3, 3, 39, 7, -6, 40, 11, -15, + 40, 15, -25, 41, 19, -33, 42, 24, -42, 43, 29, -50, + 44, 34, -57, 45, 39, -65, 46, 44, -72, 48, 49, -79, + 39, -7, 45, 39, -6, 39, 39, -5, 32, 39, -3, 23, + 39, -1, 14, 40, 1, 5, 40, 4, -5, 41, 8, -14, + 41, 12, -23, 42, 17, -31, 43, 22, -40, 44, 26, -48, + 45, 31, -56, 46, 37, -63, 47, 42, -71, 49, 47, -78, + 40, -9, 46, 40, -9, 40, 40, -8, 34, 40, -6, 25, + 41, -4, 15, 41, -1, 6, 41, 2, -3, 42, 6, -12, + 43, 10, -21, 43, 14, -30, 44, 19, -38, 45, 24, -46, + 46, 29, -54, 47, 34, -62, 48, 39, -69, 49, 45, -76, + 41, -12, 47, 41, -11, 41, 41, -10, 35, 42, -8, 26, + 42, -6, 17, 42, -3, 8, 43, 0, -1, 43, 3, -10, + 44, 8, -19, 44, 12, -28, 45, 17, -37, 46, 21, -45, + 47, 26, -53, 48, 32, -60, 49, 37, -68, 50, 42, -75, + 42, -14, 48, 42, -13, 43, 43, -12, 36, 43, -10, 28, + 43, -8, 19, 43, -6, 10, 44, -3, 0, 44, 1, -8, + 45, 5, -18, 45, 9, -26, 46, 14, -35, 47, 19, -43, + 48, 24, -51, 49, 29, -59, 50, 34, -66, 51, 40, -74, + 44, -16, 49, 44, -15, 44, 44, -14, 37, 44, -13, 29, + 44, -11, 20, 45, -8, 11, 45, -5, 2, 45, -1, -7, + 46, 3, -16, 47, 7, -24, 47, 12, -33, 48, 17, -41, + 49, 21, -49, 50, 27, -57, 51, 32, -65, 52, 37, -72, + 45, -18, 49, 45, -18, 45, 45, -16, 39, 45, -15, 31, + 45, -13, 22, 46, -10, 13, 46, -7, 4, 47, -4, -5, + 47, 0, -14, 48, 5, -23, 48, 9, -32, 49, 14, -40, + 50, 19, -48, 51, 24, -56, 52, 30, -63, 53, 35, -70, + 46, -20, 50, 46, -20, 46, 46, -19, 40, 46, -17, 32, + 47, -15, 24, 47, -13, 15, 47, -9, 5, 48, -6, -3, + 48, -2, -12, 49, 2, -21, 50, 7, -30, 50, 12, -38, + 51, 17, -46, 52, 22, -54, 53, 27, -62, 54, 33, -69, + 47, -22, 51, 47, -22, 47, 48, -21, 41, 48, -19, 34, + 48, -17, 25, 48, -15, 16, 49, -12, 7, 49, -8, -1, + 49, -4, -11, 50, 0, -19, 51, 5, -28, 51, 9, -36, + 52, 14, -44, 53, 20, -52, 54, 25, -60, 55, 30, -67, + 49, -24, 52, 49, -24, 48, 49, -23, 43, 49, -21, 35, + 49, -19, 27, 49, -17, 18, 50, -14, 9, 50, -11, 0, + 51, -7, -9, 51, -2, -17, 52, 2, -26, 53, 7, -35, + 53, 12, -42, 54, 17, -51, 55, 22, -58, 56, 28, -66, + 50, -27, 53, 50, -26, 50, 50, -25, 44, 50, -24, 37, + 51, -22, 29, 51, -19, 20, 51, -17, 11, 52, -13, 2, + 52, -9, -7, 53, -5, -15, 53, 0, -24, 54, 4, -32, + 55, 9, -40, 56, 14, -48, 56, 19, -56, 57, 25, -64, + 51, -29, 54, 52, -28, 51, 52, -27, 45, 52, -26, 38, + 52, -24, 30, 52, -22, 22, 53, -19, 13, 53, -16, 4, + 53, -12, -5, 54, -8, -13, 54, -3, -22, 55, 2, -31, + 56, 6, -39, 57, 12, -47, 57, 17, -54, 58, 22, -62, + 53, -31, 55, 53, -30, 52, 53, -29, 47, 53, -28, 40, + 53, -26, 32, 53, -24, 23, 54, -21, 14, 54, -18, 6, + 55, -14, -3, 55, -10, -12, 56, -5, -21, 56, 0, -29, + 57, 4, -37, 58, 9, -45, 59, 14, -53, 59, 20, -60, + 54, -33, 56, 54, -32, 53, 54, -31, 48, 54, -30, 41, + 54, -28, 33, 55, -26, 25, 55, -23, 16, 55, -20, 7, + 56, -16, -1, 56, -12, -10, 57, -7, -19, 57, -3, -27, + 58, 2, -35, 59, 7, -43, 60, 12, -51, 61, 18, -59, + 55, -34, 57, 55, -34, 54, 55, -33, 49, 56, -31, 43, + 56, -30, 35, 56, -28, 27, 56, -25, 18, 57, -22, 9, + 57, -18, 0, 57, -14, -8, 58, -10, -17, 59, -5, -25, + 59, 0, -33, 60, 5, -42, 61, 10, -49, 62, 15, -57, + 57, -36, 58, 57, -36, 56, 57, -35, 50, 57, -33, 44, + 57, -32, 36, 57, -29, 28, 57, -27, 19, 58, -24, 11, + 58, -20, 2, 59, -16, -6, 59, -12, -15, 60, -7, -23, + 60, -2, -32, 61, 2, -40, 62, 7, -48, 63, 13, -55, + 58, -38, 59, 58, -37, 57, 58, -37, 52, 58, -35, 45, + 58, -33, 38, 58, -31, 30, 59, -29, 21, 59, -26, 13, + 59, -22, 4, 60, -19, -5, 60, -14, -13, 61, -10, -22, + 61, -4, -30, 62, 0, -38, 63, 5, -46, 64, 10, -54, + 59, -40, 60, 59, -39, 58, 59, -38, 53, 59, -37, 47, + 59, -35, 39, 60, -33, 32, 60, -31, 23, 60, -28, 14, + 61, -24, 5, 61, -21, -3, 62, -16, -12, 62, -12, -20, + 63, -7, -28, 63, -2, -36, 64, 3, -44, 65, 8, -52, + 60, -41, 61, 60, -41, 59, 60, -40, 54, 61, -39, 48, + 61, -37, 41, 61, -35, 33, 61, -32, 24, 61, -30, 16, + 62, -26, 7, 62, -23, -1, 63, -18, -10, 63, -14, -18, + 64, -9, -26, 64, -4, -35, 65, 0, -42, 66, 6, -50, + 62, -43, 62, 62, -43, 60, 62, -42, 55, 62, -40, 49, + 62, -39, 42, 62, -37, 35, 62, -34, 26, 63, -32, 18, + 63, -28, 9, 63, -25, 0, 64, -20, -8, 64, -16, -16, + 65, -11, -24, 66, -6, -33, 66, -1, -41, 67, 4, -49, + 63, -45, 63, 63, -44, 61, 63, -43, 57, 63, -42, 51, + 63, -41, 44, 63, -39, 36, 64, -36, 28, 64, -33, 19, + 64, -30, 11, 65, -27, 2, 65, -22, -6, 66, -18, -15, + 66, -13, -23, 67, -9, -31, 67, -3, -39, 68, 1, -47, + 64, -46, 64, 64, -46, 62, 64, -45, 58, 64, -44, 52, + 64, -42, 45, 65, -40, 38, 65, -38, 29, 65, -35, 21, + 65, -32, 12, 66, -29, 4, 66, -25, -5, 67, -20, -13, + 67, -15, -21, 68, -11, -29, 68, -5, -37, 69, 0, -45, + 65, -48, 65, 65, -47, 63, 65, -47, 59, 66, -45, 53, + 66, -44, 46, 66, -42, 39, 66, -40, 31, 66, -37, 23, + 67, -34, 14, 67, -30, 6, 67, -27, -3, 68, -22, -11, + 68, -18, -19, 69, -13, -28, 70, -8, -35, 70, -3, -43, + 67, -49, 66, 67, -49, 64, 67, -48, 60, 67, -47, 54, + 67, -46, 48, 67, -44, 41, 67, -41, 32, 68, -39, 24, + 68, -36, 16, 68, -32, 7, 69, -28, -1, 69, -24, -9, + 70, -20, -17, 70, -15, -26, 71, -10, -34, 71, -5, -42, + 68, -51, 67, 68, -51, 65, 68, -50, 61, 68, -49, 56, + 68, -47, 49, 68, -45, 42, 69, -43, 34, 69, -41, 26, + 69, -38, 17, 69, -34, 9, 70, -30, 0, 70, -26, -8, + 71, -22, -16, 71, -17, -24, 72, -12, -32, 73, -7, -40, + 69, -53, 68, 69, -52, 66, 69, -51, 62, 69, -50, 57, + 69, -49, 51, 70, -47, 44, 70, -45, 36, 70, -42, 28, + 70, -39, 19, 71, -36, 11, 71, -32, 2, 71, -28, -6, + 72, -24, -14, 72, -19, -22, 73, -14, -30, 74, -9, -38, + 70, -54, 69, 70, -54, 67, 70, -53, 64, 70, -52, 58, + 71, -50, 52, 71, -49, 45, 71, -46, 37, 71, -44, 29, + 71, -41, 21, 72, -38, 13, 72, -34, 4, 73, -30, -4, + 73, -26, -12, 74, -21, -21, 74, -16, -28, 75, -11, -36, + 72, -56, 70, 72, -55, 68, 72, -54, 65, 72, -53, 59, + 72, -52, 53, 72, -50, 46, 72, -48, 39, 72, -46, 31, + 73, -43, 22, 73, -40, 14, 73, -36, 6, 74, -32, -2, + 74, -28, -10, 75, -23, -19, 75, -18, -27, 76, -14, -35, + 73, -57, 71, 73, -57, 69, 73, -56, 66, 73, -55, 61, + 73, -54, 54, 73, -52, 48, 73, -50, 40, 74, -47, 32, + 74, -44, 24, 74, -41, 16, 75, -38, 7, 75, -34, -1, + 75, -30, -9, 76, -25, -17, 76, -20, -25, 77, -16, -33, + 74, -58, 72, 74, -58, 70, 74, -57, 67, 74, -56, 62, + 74, -55, 56, 74, -53, 49, 75, -51, 42, 75, -49, 34, + 75, -46, 26, 75, -43, 18, 76, -40, 9, 76, -36, 1, + 77, -32, -7, 77, -27, -15, 78, -22, -23, 78, -18, -31, + 75, -60, 73, 75, -60, 71, 75, -59, 68, 75, -58, 63, + 76, -57, 57, 76, -55, 51, 76, -53, 43, 76, -51, 36, + 76, -48, 27, 77, -45, 19, 77, -41, 11, 77, -38, 3, + 78, -34, -5, 78, -29, -14, 79, -24, -21, 79, -20, -29, + 77, -62, 74, 77, -61, 73, 77, -61, 70, 77, -60, 65, + 77, -58, 59, 77, -57, 52, 77, -55, 45, 78, -53, 37, + 78, -50, 29, 78, -47, 21, 78, -44, 13, 79, -40, 5, + 79, -36, -3, 80, -32, -11, 80, -27, -19, 81, -22, -27, + 78, -63, 75, 78, -63, 74, 78, -62, 71, 78, -61, 66, + 78, -60, 60, 78, -58, 54, 79, -56, 46, 79, -54, 39, + 79, -51, 31, 79, -49, 23, 80, -45, 15, 80, -42, 7, + 80, -38, -1, 81, -34, -10, 81, -29, -18, 82, -24, -26, + 79, -64, 76, 79, -64, 75, 79, -64, 72, 79, -63, 67, + 80, -61, 61, 80, -60, 55, 80, -58, 48, 80, -56, 40, + 80, -53, 32, 80, -50, 25, 81, -47, 16, 81, -44, 8, + 82, -40, 0, 82, -35, -8, 82, -31, -16, 83, -26, -24, + 81, -66, 77, 81, -65, 76, 81, -65, 73, 81, -64, 68, + 81, -63, 63, 81, -61, 56, 81, -59, 49, 81, -57, 42, + 81, -55, 34, 82, -52, 26, 82, -49, 18, 82, -45, 10, + 83, -41, 2, 83, -37, -6, 84, -33, -14, 84, -28, -22, + 82, -67, 78, 82, -67, 77, 82, -66, 74, 82, -65, 69, + 82, -64, 64, 82, -63, 58, 82, -61, 51, 82, -59, 43, + 83, -56, 36, 83, -53, 28, 83, -50, 19, 84, -47, 12, + 84, -43, 3, 84, -39, -5, 85, -35, -12, 85, -30, -20, + 83, -69, 79, 83, -68, 78, 83, -68, 75, 83, -67, 71, + 83, -66, 65, 83, -64, 59, 83, -62, 52, 84, -60, 45, + 84, -58, 37, 84, -55, 29, 84, -52, 21, 85, -49, 13, + 85, -45, 5, 85, -41, -3, 86, -37, -11, 86, -32, -19, + 84, -70, 80, 84, -70, 79, 84, -69, 76, 84, -68, 72, + 84, -67, 66, 84, -66, 60, 85, -64, 53, 85, -62, 46, + 85, -59, 39, 85, -57, 31, 86, -53, 23, 86, -50, 15, + 86, -47, 7, 87, -43, -1, 87, -38, -9, 88, -34, -17, + 85, -71, 81, 85, -71, 80, 85, -70, 77, 85, -69, 73, + 86, -68, 68, 86, -67, 62, 86, -65, 55, 86, -63, 48, + 86, -61, 40, 86, -58, 33, 87, -55, 24, 87, -52, 17, + 87, -48, 8, 88, -45, 0, 88, -40, -7, 89, -36, -15, + 87, -72, 82, 87, -72, 81, 87, -72, 78, 87, -71, 74, + 87, -70, 69, 87, -68, 63, 87, -66, 56, 87, -64, 49, + 87, -62, 42, 88, -60, 34, 88, -57, 26, 88, -54, 18, + 89, -50, 10, 89, -46, 2, 89, -42, -6, 90, -38, -14, + 88, -74, 83, 88, -73, 81, 88, -73, 79, 88, -72, 75, + 88, -71, 70, 88, -70, 64, 88, -68, 58, 88, -66, 51, + 89, -64, 43, 89, -61, 36, 89, -58, 28, 89, -55, 20, + 90, -52, 12, 90, -48, 4, 90, -44, -4, 91, -40, -12, + 89, -75, 84, 89, -75, 82, 89, -74, 80, 89, -73, 76, + 89, -72, 71, 89, -71, 66, 89, -69, 59, 90, -67, 52, + 90, -65, 45, 90, -63, 37, 90, -60, 29, 91, -57, 21, + 91, -53, 13, 91, -50, 5, 92, -46, -2, 92, -41, -10, + 21, 42, 29, 21, 43, 22, 21, 43, 10, 21, 45, 0, + 22, 46, -10, 23, 48, -20, 24, 51, -30, 25, 54, -38, + 26, 57, -47, 27, 60, -55, 29, 63, -62, 31, 67, -69, + 32, 70, -76, 34, 74, -83, 36, 77, -89, 38, 81, -95, + 21, 41, 30, 21, 42, 22, 21, 42, 11, 22, 44, 0, + 22, 45, -10, 23, 47, -19, 24, 50, -29, 25, 53, -38, + 26, 56, -47, 28, 59, -54, 29, 63, -62, 31, 66, -69, + 32, 70, -76, 34, 73, -83, 36, 77, -89, 38, 80, -95, + 21, 40, 30, 21, 41, 22, 22, 42, 11, 22, 43, 1, + 23, 44, -9, 23, 47, -19, 24, 49, -29, 25, 52, -38, + 27, 55, -46, 28, 58, -54, 29, 62, -62, 31, 65, -69, + 33, 69, -76, 34, 73, -82, 36, 76, -89, 38, 80, -95, + 22, 39, 30, 22, 40, 23, 22, 41, 12, 22, 42, 1, + 23, 44, -9, 24, 46, -19, 25, 48, -28, 26, 51, -37, + 27, 55, -46, 28, 58, -54, 30, 61, -61, 31, 65, -68, + 33, 69, -75, 34, 72, -82, 36, 76, -89, 38, 79, -95, + 22, 38, 31, 22, 39, 23, 22, 40, 12, 23, 41, 2, + 23, 42, -8, 24, 45, -18, 25, 47, -28, 26, 50, -37, + 27, 54, -45, 28, 57, -53, 30, 61, -61, 31, 64, -68, + 33, 68, -75, 35, 71, -82, 36, 75, -88, 38, 79, -94, + 22, 37, 31, 23, 37, 24, 23, 38, 13, 23, 39, 2, + 24, 41, -8, 24, 44, -17, 25, 46, -27, 26, 49, -36, + 28, 53, -45, 29, 56, -53, 30, 60, -60, 32, 63, -67, + 33, 67, -75, 35, 71, -81, 37, 74, -88, 38, 78, -94, + 23, 36, 32, 23, 36, 24, 23, 37, 13, 24, 38, 3, + 24, 40, -7, 25, 42, -17, 26, 45, -26, 27, 48, -35, + 28, 51, -44, 29, 55, -52, 31, 59, -60, 32, 62, -67, + 34, 66, -74, 35, 70, -81, 37, 74, -87, 39, 77, -94, + 24, 34, 33, 24, 34, 25, 24, 35, 14, 24, 36, 4, + 25, 38, -6, 25, 41, -16, 26, 44, -26, 27, 47, -34, + 28, 50, -43, 30, 54, -51, 31, 57, -59, 32, 61, -66, + 34, 65, -73, 36, 69, -80, 37, 73, -87, 39, 76, -93, + 24, 32, 33, 24, 33, 25, 25, 33, 15, 25, 35, 5, + 25, 37, -5, 26, 39, -15, 27, 42, -25, 28, 45, -34, + 29, 49, -43, 30, 52, -50, 32, 56, -58, 33, 60, -66, + 34, 64, -73, 36, 68, -79, 38, 72, -86, 39, 75, -93, + 25, 30, 34, 25, 31, 26, 25, 31, 16, 26, 33, 6, + 26, 35, -4, 27, 37, -14, 28, 40, -24, 28, 43, -33, + 30, 47, -42, 31, 51, -50, 32, 55, -58, 33, 58, -65, + 35, 62, -72, 36, 66, -79, 38, 70, -86, 40, 74, -92, + 26, 28, 35, 26, 29, 27, 26, 29, 17, 26, 31, 7, + 27, 33, -3, 27, 35, -13, 28, 38, -23, 29, 42, -32, + 30, 45, -41, 31, 49, -49, 33, 53, -57, 34, 57, -64, + 35, 61, -71, 37, 65, -78, 38, 69, -85, 40, 73, -91, + 27, 25, 36, 27, 26, 28, 27, 27, 18, 27, 28, 8, + 28, 30, -2, 28, 33, -11, 29, 36, -21, 30, 39, -30, + 31, 43, -39, 32, 47, -47, 33, 51, -55, 35, 55, -63, + 36, 59, -70, 38, 63, -77, 39, 67, -84, 41, 71, -90, + 28, 23, 36, 28, 24, 29, 28, 24, 19, 28, 26, 9, + 29, 28, 0, 29, 31, -10, 30, 34, -20, 31, 37, -29, + 32, 41, -38, 33, 45, -46, 34, 49, -54, 35, 53, -62, + 37, 57, -69, 38, 61, -76, 40, 66, -83, 41, 70, -89, + 29, 21, 37, 29, 21, 30, 29, 22, 20, 29, 24, 10, + 30, 26, 0, 30, 28, -9, 31, 32, -19, 32, 35, -28, + 33, 39, -37, 34, 43, -45, 35, 47, -53, 36, 51, -61, + 37, 55, -68, 39, 60, -75, 40, 64, -82, 42, 68, -89, + 30, 18, 38, 30, 19, 31, 30, 20, 22, 30, 21, 12, + 31, 23, 1, 31, 26, -7, 32, 29, -17, 32, 33, -26, + 33, 37, -35, 34, 41, -44, 36, 45, -52, 37, 49, -59, + 38, 53, -67, 39, 58, -74, 41, 62, -81, 42, 67, -88, + 31, 16, 39, 31, 16, 32, 31, 17, 23, 31, 19, 13, + 31, 21, 3, 32, 24, -6, 33, 27, -16, 33, 30, -25, + 34, 34, -34, 35, 38, -42, 36, 43, -51, 37, 47, -58, + 39, 51, -66, 40, 56, -73, 41, 60, -80, 43, 65, -87, + 32, 14, 39, 32, 14, 33, 32, 15, 24, 32, 16, 14, + 32, 19, 4, 33, 21, -5, 34, 25, -14, 34, 28, -23, + 35, 32, -33, 36, 36, -41, 37, 41, -49, 38, 45, -57, + 39, 49, -65, 41, 54, -72, 42, 58, -79, 44, 63, -86, + 33, 11, 40, 33, 12, 34, 33, 13, 25, 33, 14, 16, + 34, 16, 6, 34, 19, -3, 35, 22, -13, 35, 26, -22, + 36, 30, -31, 37, 34, -40, 38, 38, -48, 39, 43, -56, + 40, 47, -63, 42, 52, -71, 43, 56, -78, 44, 61, -84, + 34, 9, 41, 34, 9, 35, 34, 10, 27, 34, 11, 17, + 35, 14, 7, 35, 16, -1, 36, 20, -11, 36, 23, -20, + 37, 27, -30, 38, 31, -38, 39, 36, -47, 40, 40, -54, + 41, 45, -62, 42, 50, -69, 44, 54, -77, 45, 59, -83, + 35, 6, 42, 35, 7, 36, 35, 8, 28, 35, 9, 19, + 36, 11, 9, 36, 14, 0, 37, 17, -10, 37, 21, -19, + 38, 25, -28, 39, 29, -37, 40, 34, -45, 41, 38, -53, + 42, 43, -61, 43, 47, -68, 44, 52, -75, 46, 57, -82, + 36, 4, 43, 36, 4, 37, 36, 5, 29, 36, 7, 20, + 37, 9, 10, 37, 11, 1, 38, 15, -8, 38, 18, -17, + 39, 23, -27, 40, 27, -35, 41, 31, -44, 42, 36, -51, + 43, 40, -59, 44, 45, -67, 45, 50, -74, 47, 55, -81, + 37, 1, 44, 37, 2, 38, 37, 3, 31, 38, 4, 21, + 38, 6, 12, 38, 9, 3, 39, 12, -7, 39, 16, -16, + 40, 20, -25, 41, 24, -33, 42, 29, -42, 43, 33, -50, + 44, 38, -58, 45, 43, -65, 46, 48, -73, 47, 53, -80, + 38, -1, 45, 38, 0, 39, 39, 0, 32, 39, 2, 23, + 39, 4, 13, 39, 7, 4, 40, 10, -5, 40, 13, -14, + 41, 18, -23, 42, 22, -32, 43, 26, -41, 44, 31, -48, + 45, 35, -56, 46, 41, -64, 47, 45, -71, 48, 50, -78, + 40, -3, 46, 40, -3, 40, 40, -1, 33, 40, 0, 24, + 40, 1, 15, 41, 4, 6, 41, 7, -3, 42, 11, -12, + 42, 15, -22, 43, 19, -30, 44, 24, -39, 45, 28, -47, + 46, 33, -55, 47, 38, -62, 48, 43, -70, 49, 48, -77, + 41, -5, 47, 41, -5, 41, 41, -4, 35, 41, -2, 26, + 41, 0, 17, 42, 2, 7, 42, 5, -2, 43, 9, -11, + 43, 13, -20, 44, 17, -29, 45, 21, -37, 46, 26, -45, + 47, 31, -53, 48, 36, -61, 49, 41, -68, 50, 46, -76, + 42, -8, 48, 42, -7, 42, 42, -6, 36, 42, -4, 27, + 43, -2, 18, 43, 0, 9, 43, 3, 0, 44, 6, -9, + 44, 10, -18, 45, 14, -27, 46, 19, -36, 47, 24, -44, + 48, 28, -52, 49, 33, -59, 50, 38, -67, 51, 44, -74, + 43, -10, 48, 43, -9, 43, 43, -8, 37, 43, -7, 29, + 44, -5, 20, 44, -2, 11, 44, 0, 1, 45, 4, -7, + 45, 8, -17, 46, 12, -25, 47, 17, -34, 48, 21, -42, + 49, 26, -50, 50, 31, -58, 51, 36, -65, 52, 41, -73, + 44, -12, 49, 44, -12, 45, 44, -11, 38, 45, -9, 30, + 45, -7, 21, 45, -5, 12, 46, -2, 3, 46, 1, -6, + 47, 5, -15, 47, 9, -23, 48, 14, -32, 49, 19, -40, + 50, 23, -48, 51, 29, -56, 52, 33, -64, 53, 39, -71, + 46, -14, 50, 46, -14, 46, 46, -13, 40, 46, -11, 32, + 46, -9, 23, 46, -7, 14, 47, -4, 5, 47, -1, -4, + 48, 3, -13, 48, 7, -22, 49, 12, -31, 50, 16, -39, + 51, 21, -47, 52, 26, -55, 53, 31, -62, 54, 36, -70, + 47, -17, 51, 47, -16, 47, 47, -15, 41, 47, -13, 33, + 47, -12, 25, 48, -9, 16, 48, -6, 6, 48, -3, -2, + 49, 1, -11, 49, 5, -20, 50, 9, -29, 51, 14, -37, + 52, 19, -45, 53, 24, -53, 54, 29, -61, 55, 34, -68, + 48, -19, 52, 48, -18, 48, 48, -17, 42, 48, -16, 35, + 49, -14, 26, 49, -11, 17, 49, -9, 8, 50, -5, 0, + 50, -2, -10, 51, 2, -18, 51, 7, -27, 52, 11, -35, + 53, 16, -43, 54, 21, -51, 55, 26, -59, 56, 32, -67, + 49, -21, 53, 49, -20, 49, 49, -19, 43, 50, -18, 36, + 50, -16, 28, 50, -14, 19, 50, -11, 10, 51, -8, 1, + 51, -4, -8, 52, 0, -17, 52, 4, -25, 53, 9, -34, + 54, 14, -42, 55, 19, -50, 56, 24, -57, 57, 29, -65, + 51, -23, 54, 51, -23, 51, 51, -22, 45, 51, -20, 38, + 51, -18, 30, 51, -16, 21, 52, -14, 12, 52, -11, 3, + 53, -7, -6, 53, -3, -14, 54, 2, -23, 54, 6, -31, + 55, 11, -39, 56, 16, -48, 57, 21, -55, 58, 26, -63, + 52, -25, 55, 52, -25, 52, 52, -24, 46, 52, -22, 39, + 52, -21, 31, 53, -18, 23, 53, -16, 13, 53, -13, 5, + 54, -9, -4, 54, -5, -13, 55, -1, -22, 56, 4, -30, + 56, 8, -38, 57, 13, -46, 58, 18, -54, 59, 24, -61, + 53, -27, 56, 53, -27, 53, 53, -26, 47, 54, -24, 41, + 54, -23, 33, 54, -21, 24, 54, -18, 15, 55, -15, 6, + 55, -11, -2, 56, -7, -11, 56, -3, -20, 57, 1, -28, + 57, 6, -36, 58, 11, -44, 59, 16, -52, 60, 21, -60, + 55, -29, 57, 55, -29, 54, 55, -28, 48, 55, -26, 42, + 55, -25, 34, 55, -23, 26, 55, -20, 17, 56, -17, 8, + 56, -13, -1, 57, -10, -9, 57, -5, -18, 58, -1, -26, + 59, 4, -34, 59, 9, -43, 60, 14, -50, 61, 19, -58, + 56, -31, 58, 56, -31, 55, 56, -30, 50, 56, -28, 43, + 56, -27, 36, 56, -25, 27, 57, -22, 18, 57, -19, 10, + 57, -16, 1, 58, -12, -7, 58, -7, -16, 59, -3, -25, + 60, 1, -33, 60, 6, -41, 61, 11, -49, 62, 17, -56, + 57, -33, 59, 57, -33, 56, 57, -32, 51, 57, -30, 45, + 57, -29, 37, 58, -27, 29, 58, -24, 20, 58, -21, 12, + 59, -18, 3, 59, -14, -6, 60, -10, -15, 60, -5, -23, + 61, 0, -31, 61, 4, -39, 62, 9, -47, 63, 14, -55, + 58, -35, 60, 58, -34, 57, 58, -34, 52, 58, -32, 46, + 59, -30, 39, 59, -28, 31, 59, -26, 22, 59, -23, 13, + 60, -20, 4, 60, -16, -4, 61, -12, -13, 61, -7, -21, + 62, -3, -29, 63, 2, -37, 63, 7, -45, 64, 12, -53, + 60, -37, 61, 60, -36, 58, 60, -35, 53, 60, -34, 47, + 60, -32, 40, 60, -30, 32, 60, -28, 23, 61, -25, 15, + 61, -22, 6, 61, -18, -2, 62, -14, -11, 62, -10, -19, + 63, -5, -27, 64, 0, -36, 64, 4, -44, 65, 10, -51, + 61, -38, 62, 61, -38, 59, 61, -37, 55, 61, -36, 49, + 61, -34, 41, 61, -32, 34, 62, -30, 25, 62, -27, 17, + 62, -24, 8, 63, -20, 0, 63, -16, -9, 64, -12, -18, + 64, -7, -26, 65, -2, -34, 65, 2, -42, 66, 7, -50, + 62, -40, 63, 62, -40, 60, 62, -39, 56, 62, -38, 50, + 62, -36, 43, 63, -34, 35, 63, -32, 27, 63, -29, 18, + 63, -26, 9, 64, -22, 1, 64, -18, -8, 65, -14, -16, + 65, -9, -24, 66, -5, -32, 67, 0, -40, 67, 5, -48, + 63, -42, 64, 63, -41, 61, 63, -41, 57, 63, -39, 51, + 64, -38, 44, 64, -36, 37, 64, -34, 28, 64, -31, 20, + 65, -28, 11, 65, -24, 3, 65, -20, -6, 66, -16, -14, + 66, -11, -22, 67, -7, -30, 68, -2, -38, 68, 3, -46, + 64, -44, 65, 65, -43, 62, 65, -42, 58, 65, -41, 52, + 65, -40, 46, 65, -38, 38, 65, -35, 30, 65, -33, 22, + 66, -30, 13, 66, -26, 5, 67, -22, -4, 67, -18, -12, + 68, -14, -20, 68, -9, -29, 69, -4, -37, 70, 1, -45, + 66, -45, 66, 66, -45, 63, 66, -44, 59, 66, -43, 54, + 66, -41, 47, 66, -39, 40, 66, -37, 31, 67, -35, 23, + 67, -32, 15, 67, -28, 6, 68, -24, -2, 68, -20, -11, + 69, -16, -19, 69, -11, -27, 70, -6, -35, 71, -1, -43, + 67, -47, 67, 67, -46, 65, 67, -46, 61, 67, -45, 55, + 67, -43, 48, 67, -41, 41, 68, -39, 33, 68, -36, 25, + 68, -33, 16, 69, -30, 8, 69, -26, -1, 69, -22, -9, + 70, -18, -17, 70, -13, -25, 71, -8, -33, 72, -3, -41, + 68, -48, 68, 68, -48, 66, 68, -47, 62, 68, -46, 56, + 68, -45, 50, 69, -43, 43, 69, -41, 35, 69, -38, 27, + 69, -35, 18, 70, -32, 10, 70, -28, 1, 71, -24, -7, + 71, -20, -15, 72, -15, -24, 72, -10, -31, 73, -6, -39, + 69, -50, 69, 69, -50, 67, 70, -49, 63, 70, -48, 57, + 70, -46, 51, 70, -45, 44, 70, -42, 36, 70, -40, 28, + 71, -37, 20, 71, -34, 11, 71, -30, 3, 72, -26, -5, + 72, -22, -13, 73, -18, -22, 73, -13, -30, 74, -8, -38, + 71, -52, 70, 71, -51, 68, 71, -51, 64, 71, -49, 59, + 71, -48, 52, 71, -46, 45, 71, -44, 38, 72, -42, 30, + 72, -39, 21, 72, -36, 13, 73, -32, 4, 73, -28, -4, + 73, -24, -12, 74, -20, -20, 74, -15, -28, 75, -10, -36, + 72, -53, 71, 72, -53, 69, 72, -52, 65, 72, -51, 60, + 72, -50, 54, 72, -48, 47, 73, -46, 39, 73, -43, 31, + 73, -41, 23, 73, -38, 15, 74, -34, 6, 74, -30, -2, + 75, -26, -10, 75, -22, -18, 76, -17, -26, 76, -12, -34, + 73, -55, 72, 73, -54, 70, 73, -54, 66, 73, -53, 61, + 73, -51, 55, 74, -50, 48, 74, -47, 41, 74, -45, 33, + 74, -42, 24, 75, -39, 16, 75, -36, 8, 75, -32, 0, + 76, -28, -8, 76, -24, -17, 77, -19, -24, 77, -14, -33, + 74, -56, 72, 74, -56, 71, 74, -55, 67, 75, -54, 62, + 75, -53, 56, 75, -51, 50, 75, -49, 42, 75, -47, 34, + 75, -44, 26, 76, -41, 18, 76, -38, 10, 76, -34, 1, + 77, -30, -7, 77, -26, -15, 78, -21, -23, 78, -16, -31, + 76, -58, 73, 76, -57, 72, 76, -57, 69, 76, -56, 63, + 76, -54, 57, 76, -53, 51, 76, -51, 44, 76, -48, 36, + 77, -46, 28, 77, -43, 20, 77, -39, 11, 78, -36, 3, + 78, -32, -5, 79, -28, -13, 79, -23, -21, 80, -18, -29, + 77, -59, 75, 77, -59, 73, 77, -58, 70, 77, -58, 65, + 77, -56, 59, 77, -55, 53, 78, -53, 45, 78, -51, 38, + 78, -48, 30, 78, -45, 22, 79, -42, 13, 79, -38, 5, + 79, -34, -3, 80, -30, -11, 80, -25, -19, 81, -21, -27, + 78, -61, 76, 78, -61, 74, 78, -60, 71, 78, -59, 66, + 79, -58, 60, 79, -56, 54, 79, -54, 47, 79, -52, 39, + 79, -50, 31, 80, -47, 23, 80, -43, 15, 80, -40, 7, + 81, -36, -1, 81, -32, -9, 82, -27, -17, 82, -23, -25, + 80, -62, 77, 80, -62, 75, 80, -61, 72, 80, -60, 67, + 80, -59, 62, 80, -58, 55, 80, -56, 48, 80, -54, 41, + 80, -51, 33, 81, -48, 25, 81, -45, 17, 81, -42, 9, + 82, -38, 0, 82, -34, -8, 83, -29, -15, 83, -25, -23, + 81, -64, 78, 81, -63, 76, 81, -63, 73, 81, -62, 69, + 81, -61, 63, 81, -59, 57, 81, -57, 50, 81, -55, 42, + 82, -53, 34, 82, -50, 27, 82, -47, 18, 83, -44, 10, + 83, -40, 2, 83, -36, -6, 84, -31, -14, 84, -27, -22, + 82, -65, 79, 82, -65, 77, 82, -64, 74, 82, -63, 70, + 82, -62, 64, 82, -61, 58, 82, -59, 51, 83, -57, 44, + 83, -54, 36, 83, -52, 28, 83, -49, 20, 84, -45, 12, + 84, -42, 4, 85, -38, -4, 85, -33, -12, 86, -29, -20, + 83, -66, 80, 83, -66, 78, 83, -66, 75, 83, -65, 71, + 83, -64, 65, 84, -62, 59, 84, -60, 52, 84, -58, 45, + 84, -56, 37, 84, -53, 30, 85, -50, 21, 85, -47, 14, + 85, -43, 5, 86, -39, -2, 86, -35, -10, 87, -31, -18, + 84, -68, 81, 84, -68, 79, 84, -67, 76, 85, -66, 72, + 85, -65, 67, 85, -64, 61, 85, -62, 54, 85, -60, 47, + 85, -57, 39, 86, -55, 31, 86, -52, 23, 86, -49, 15, + 86, -45, 7, 87, -41, -1, 87, -37, -9, 88, -33, -17, + 86, -69, 82, 86, -69, 80, 86, -68, 77, 86, -68, 73, + 86, -66, 68, 86, -65, 62, 86, -63, 55, 86, -61, 48, + 86, -59, 40, 87, -56, 33, 87, -53, 25, 87, -50, 17, + 88, -47, 9, 88, -43, 1, 88, -39, -7, 89, -35, -15, + 87, -71, 82, 87, -70, 81, 87, -70, 78, 87, -69, 74, + 87, -68, 69, 87, -66, 63, 87, -65, 57, 87, -63, 50, + 88, -60, 42, 88, -58, 34, 88, -55, 26, 88, -52, 19, + 89, -49, 10, 89, -45, 2, 90, -41, -5, 90, -36, -13, + 88, -72, 83, 88, -72, 82, 88, -71, 79, 88, -70, 76, + 88, -69, 70, 88, -68, 65, 88, -66, 58, 89, -64, 51, + 89, -62, 43, 89, -59, 36, 89, -57, 28, 90, -54, 20, + 90, -50, 12, 90, -46, 4, 91, -42, -4, 91, -38, -12, + 89, -73, 84, 89, -73, 83, 89, -72, 80, 89, -72, 77, + 89, -71, 72, 90, -69, 66, 90, -68, 59, 90, -66, 52, + 90, -63, 45, 90, -61, 37, 90, -58, 29, 91, -55, 22, + 91, -52, 14, 91, -48, 6, 92, -44, -2, 92, -40, -10, + 22, 45, 32, 23, 45, 24, 23, 46, 13, 23, 47, 3, + 24, 48, -7, 24, 50, -17, 25, 52, -27, 26, 55, -36, + 28, 58, -45, 29, 61, -52, 30, 64, -60, 32, 67, -67, + 33, 71, -75, 35, 74, -81, 37, 78, -88, 38, 81, -94, + 23, 44, 32, 23, 44, 24, 23, 45, 14, 24, 46, 3, + 24, 47, -7, 25, 49, -17, 26, 52, -27, 27, 54, -35, + 28, 57, -44, 29, 60, -52, 31, 64, -60, 32, 67, -67, + 33, 70, -74, 35, 74, -81, 37, 77, -88, 39, 81, -94, + 23, 43, 33, 23, 43, 25, 23, 44, 14, 24, 45, 3, + 24, 46, -7, 25, 48, -16, 26, 51, -26, 27, 53, -35, + 28, 57, -44, 29, 60, -52, 31, 63, -60, 32, 66, -67, + 34, 70, -74, 35, 73, -81, 37, 77, -87, 39, 80, -93, + 23, 42, 33, 23, 42, 25, 24, 43, 14, 24, 44, 4, + 25, 46, -6, 25, 48, -16, 26, 50, -26, 27, 53, -35, + 28, 56, -44, 30, 59, -51, 31, 62, -59, 32, 66, -66, + 34, 69, -74, 36, 73, -80, 37, 76, -87, 39, 80, -93, + 24, 41, 33, 24, 41, 25, 24, 42, 15, 24, 43, 4, + 25, 45, -6, 26, 47, -15, 26, 49, -25, 27, 52, -34, + 29, 55, -43, 30, 58, -51, 31, 62, -59, 33, 65, -66, + 34, 69, -73, 36, 72, -80, 37, 76, -87, 39, 79, -93, + 24, 40, 34, 24, 40, 26, 24, 41, 15, 25, 42, 5, + 25, 44, -5, 26, 46, -15, 27, 48, -25, 28, 51, -34, + 29, 54, -43, 30, 57, -50, 31, 61, -58, 33, 64, -66, + 34, 68, -73, 36, 71, -79, 38, 75, -86, 39, 79, -93, + 25, 38, 34, 25, 39, 26, 25, 39, 16, 25, 41, 6, + 26, 42, -4, 26, 44, -14, 27, 47, -24, 28, 50, -33, + 29, 53, -42, 30, 56, -50, 32, 60, -58, 33, 63, -65, + 35, 67, -72, 36, 71, -79, 38, 74, -86, 40, 78, -92, + 25, 37, 35, 25, 37, 27, 26, 38, 17, 26, 39, 6, + 26, 41, -4, 27, 43, -13, 28, 46, -23, 29, 48, -32, + 30, 52, -41, 31, 55, -49, 32, 59, -57, 34, 62, -64, + 35, 66, -72, 37, 70, -78, 38, 73, -85, 40, 77, -92, + 26, 35, 35, 26, 36, 27, 26, 36, 17, 26, 37, 7, + 27, 39, -3, 28, 41, -12, 28, 44, -22, 29, 47, -31, + 30, 50, -40, 31, 54, -48, 33, 57, -56, 34, 61, -64, + 35, 65, -71, 37, 68, -78, 39, 72, -85, 40, 76, -91, + 26, 33, 36, 27, 34, 28, 27, 34, 18, 27, 36, 8, + 28, 37, -2, 28, 40, -12, 29, 42, -21, 30, 45, -30, + 31, 49, -39, 32, 52, -48, 33, 56, -56, 34, 60, -63, + 36, 63, -70, 37, 67, -77, 39, 71, -84, 41, 75, -91, + 27, 31, 37, 27, 32, 29, 27, 33, 19, 28, 34, 9, + 28, 36, -1, 29, 38, -11, 30, 41, -20, 30, 44, -29, + 31, 47, -39, 32, 51, -47, 34, 54, -55, 35, 58, -62, + 36, 62, -70, 38, 66, -76, 39, 70, -83, 41, 74, -90, + 28, 29, 37, 28, 29, 30, 28, 30, 20, 29, 31, 10, + 29, 33, 0, 30, 35, -9, 30, 38, -19, 31, 41, -28, + 32, 45, -37, 33, 48, -45, 34, 52, -54, 36, 56, -61, + 37, 60, -69, 39, 64, -75, 40, 68, -83, 42, 72, -89, + 29, 27, 38, 29, 27, 31, 29, 28, 21, 30, 29, 11, + 30, 31, 1, 31, 33, -8, 31, 36, -18, 32, 39, -27, + 33, 43, -36, 34, 47, -44, 35, 51, -53, 36, 54, -60, + 38, 58, -68, 39, 63, -75, 40, 67, -82, 42, 71, -88, + 30, 24, 39, 30, 25, 32, 30, 26, 22, 30, 27, 12, + 31, 29, 2, 31, 31, -7, 32, 34, -17, 33, 37, -26, + 34, 41, -35, 35, 45, -43, 36, 49, -51, 37, 53, -59, + 38, 57, -67, 40, 61, -74, 41, 65, -81, 43, 69, -87, + 31, 22, 39, 31, 23, 33, 31, 23, 23, 31, 25, 14, + 32, 27, 3, 32, 29, -5, 33, 32, -15, 34, 35, -24, + 34, 39, -34, 35, 43, -42, 37, 47, -50, 38, 51, -58, + 39, 55, -66, 40, 59, -73, 42, 63, -80, 43, 68, -86, + 32, 20, 40, 32, 20, 34, 32, 21, 25, 32, 22, 15, + 33, 24, 5, 33, 27, -4, 34, 30, -14, 34, 33, -23, + 35, 37, -32, 36, 40, -41, 37, 45, -49, 38, 49, -57, + 40, 53, -64, 41, 57, -72, 42, 61, -79, 44, 66, -85, + 33, 17, 41, 33, 18, 35, 33, 19, 26, 33, 20, 16, + 34, 22, 6, 34, 24, -3, 35, 27, -13, 35, 31, -22, + 36, 34, -31, 37, 38, -39, 38, 42, -48, 39, 47, -55, + 40, 51, -63, 42, 55, -70, 43, 59, -78, 44, 64, -84, + 34, 15, 42, 34, 15, 36, 34, 16, 27, 34, 18, 17, + 35, 19, 7, 35, 22, -1, 36, 25, -11, 36, 28, -20, + 37, 32, -30, 38, 36, -38, 39, 40, -47, 40, 44, -54, + 41, 49, -62, 42, 53, -69, 44, 58, -77, 45, 62, -83, + 35, 13, 42, 35, 13, 37, 35, 14, 28, 35, 15, 19, + 36, 17, 9, 36, 20, 0, 37, 23, -10, 37, 26, -19, + 38, 30, -28, 39, 34, -37, 40, 38, -45, 41, 42, -53, + 42, 46, -61, 43, 51, -68, 44, 55, -75, 46, 60, -82, + 36, 10, 43, 36, 11, 38, 36, 11, 29, 36, 13, 20, + 37, 15, 10, 37, 17, 1, 38, 20, -8, 38, 24, -17, + 39, 27, -27, 40, 31, -35, 41, 36, -44, 42, 40, -52, + 43, 44, -59, 44, 49, -67, 45, 53, -74, 47, 58, -81, + 37, 8, 44, 37, 8, 38, 37, 9, 31, 37, 10, 21, + 38, 12, 12, 38, 15, 2, 39, 18, -7, 39, 21, -16, + 40, 25, -25, 41, 29, -34, 42, 33, -42, 43, 38, -50, + 44, 42, -58, 45, 47, -65, 46, 51, -73, 47, 56, -80, + 38, 5, 45, 38, 6, 39, 38, 7, 32, 38, 8, 23, + 39, 10, 13, 39, 12, 4, 40, 15, -5, 40, 19, -14, + 41, 23, -24, 42, 27, -32, 43, 31, -41, 43, 35, -49, + 44, 40, -57, 46, 44, -64, 47, 49, -72, 48, 54, -79, + 39, 3, 46, 39, 3, 40, 39, 4, 33, 40, 5, 24, + 40, 7, 15, 40, 10, 6, 41, 13, -4, 41, 16, -13, + 42, 20, -22, 43, 24, -31, 44, 29, -39, 44, 33, -47, + 45, 37, -55, 47, 42, -63, 48, 47, -70, 49, 52, -77, + 40, 1, 47, 40, 1, 41, 41, 2, 35, 41, 3, 25, + 41, 5, 16, 41, 7, 7, 42, 11, -2, 42, 14, -11, + 43, 18, -20, 44, 22, -29, 45, 26, -38, 45, 30, -46, + 46, 35, -54, 47, 40, -61, 48, 45, -69, 50, 49, -76, + 42, -2, 48, 42, -1, 42, 42, 0, 36, 42, 1, 27, + 42, 3, 18, 42, 5, 9, 43, 8, 0, 43, 11, -9, + 44, 15, -19, 45, 19, -27, 46, 24, -36, 46, 28, -44, + 47, 33, -52, 48, 37, -60, 49, 42, -67, 51, 47, -75, + 43, -4, 48, 43, -3, 43, 43, -2, 37, 43, -1, 28, + 43, 0, 19, 44, 3, 10, 44, 6, 1, 45, 9, -8, + 45, 13, -17, 46, 17, -26, 47, 21, -35, 47, 26, -43, + 48, 30, -50, 49, 35, -58, 50, 40, -66, 51, 45, -73, + 44, -6, 49, 44, -6, 44, 44, -5, 38, 44, -3, 30, + 44, -1, 21, 45, 0, 12, 45, 3, 2, 46, 7, -6, + 46, 11, -15, 47, 14, -24, 48, 19, -33, 48, 23, -41, + 49, 28, -49, 50, 33, -57, 51, 37, -64, 52, 43, -72, + 45, -8, 50, 45, -8, 46, 45, -7, 39, 45, -5, 31, + 46, -3, 22, 46, -1, 13, 46, 1, 4, 47, 4, -5, + 47, 8, -14, 48, 12, -22, 49, 16, -31, 49, 21, -39, + 50, 25, -47, 51, 30, -55, 52, 35, -63, 53, 40, -70, + 46, -11, 51, 46, -10, 47, 46, -9, 41, 47, -8, 33, + 47, -6, 24, 47, -4, 15, 47, -1, 6, 48, 2, -3, + 48, 6, -12, 49, 10, -21, 50, 14, -30, 50, 18, -38, + 51, 23, -46, 52, 28, -54, 53, 33, -61, 54, 38, -69, + 47, -13, 52, 48, -12, 48, 48, -11, 42, 48, -10, 34, + 48, -8, 25, 48, -6, 17, 49, -3, 7, 49, 0, -1, + 50, 3, -10, 50, 7, -19, 51, 12, -28, 52, 16, -36, + 52, 21, -44, 53, 26, -52, 54, 30, -60, 55, 36, -67, + 49, -15, 53, 49, -15, 49, 49, -14, 43, 49, -12, 35, + 49, -10, 27, 49, -8, 18, 50, -5, 9, 50, -2, 0, + 51, 1, -9, 51, 5, -17, 52, 9, -26, 53, 14, -34, + 53, 18, -42, 54, 23, -50, 55, 28, -58, 56, 33, -66, + 50, -17, 54, 50, -17, 50, 50, -16, 44, 50, -14, 37, + 50, -12, 29, 51, -10, 20, 51, -8, 11, 51, -5, 2, + 52, -1, -7, 52, 3, -16, 53, 7, -25, 54, 11, -33, + 54, 16, -41, 55, 21, -49, 56, 26, -57, 57, 31, -64, + 51, -20, 55, 51, -19, 51, 52, -18, 46, 52, -17, 39, + 52, -15, 30, 52, -13, 22, 52, -10, 13, 53, -8, 4, + 53, -4, -5, 54, 0, -13, 54, 4, -22, 55, 8, -31, + 56, 13, -39, 57, 18, -47, 57, 23, -55, 58, 28, -62, + 53, -22, 56, 53, -21, 52, 53, -20, 47, 53, -19, 40, + 53, -17, 32, 53, -15, 23, 54, -13, 14, 54, -10, 6, + 54, -6, -3, 55, -3, -12, 55, 2, -21, 56, 6, -29, + 57, 10, -37, 58, 15, -45, 58, 20, -53, 59, 25, -61, + 54, -24, 57, 54, -23, 54, 54, -23, 48, 54, -21, 41, + 54, -19, 33, 54, -17, 25, 55, -15, 16, 55, -12, 7, + 56, -9, -2, 56, -5, -10, 57, -1, -19, 57, 4, -27, + 58, 8, -35, 59, 13, -43, 59, 18, -51, 60, 23, -59, + 55, -26, 58, 55, -25, 55, 55, -25, 49, 55, -23, 43, + 55, -21, 35, 56, -19, 27, 56, -17, 18, 56, -14, 9, + 57, -11, 0, 57, -7, -8, 58, -3, -17, 58, 1, -25, + 59, 6, -34, 60, 11, -42, 61, 15, -50, 61, 21, -57, + 56, -28, 59, 56, -27, 56, 56, -26, 50, 57, -25, 44, + 57, -23, 36, 57, -21, 28, 57, -19, 19, 58, -16, 11, + 58, -13, 2, 58, -9, -7, 59, -5, -16, 59, -1, -24, + 60, 3, -32, 61, 8, -40, 62, 13, -48, 62, 18, -56, + 58, -30, 59, 58, -29, 57, 58, -28, 52, 58, -27, 45, + 58, -25, 38, 58, -24, 30, 58, -21, 21, 59, -18, 12, + 59, -15, 3, 60, -11, -5, 60, -7, -14, 61, -3, -22, + 61, 1, -30, 62, 6, -38, 63, 11, -46, 63, 16, -54, + 59, -32, 60, 59, -31, 58, 59, -30, 53, 59, -29, 47, + 59, -27, 39, 59, -25, 31, 60, -23, 22, 60, -20, 14, + 60, -17, 5, 61, -14, -3, 61, -10, -12, 62, -5, -20, + 62, -1, -28, 63, 4, -37, 64, 8, -45, 65, 14, -52, + 60, -33, 61, 60, -33, 59, 60, -32, 54, 60, -31, 48, + 60, -29, 41, 61, -27, 33, 61, -25, 24, 61, -22, 16, + 61, -19, 7, 62, -16, -1, 62, -12, -10, 63, -8, -19, + 63, -3, -27, 64, 1, -35, 65, 6, -43, 66, 11, -51, + 61, -35, 62, 61, -35, 60, 61, -34, 55, 61, -33, 49, + 62, -31, 42, 62, -29, 34, 62, -27, 26, 62, -24, 17, + 63, -21, 8, 63, -18, 0, 64, -14, -9, 64, -10, -17, + 65, -5, -25, 65, -1, -33, 66, 4, -41, 67, 9, -49, + 62, -37, 63, 62, -37, 61, 63, -36, 56, 63, -35, 51, + 63, -33, 43, 63, -31, 36, 63, -29, 27, 63, -26, 19, + 64, -23, 10, 64, -20, 2, 65, -16, -7, 65, -12, -15, + 66, -7, -23, 66, -3, -32, 67, 2, -39, 68, 7, -47, + 64, -39, 64, 64, -38, 62, 64, -38, 58, 64, -37, 52, + 64, -35, 45, 64, -33, 37, 64, -31, 29, 65, -28, 21, + 65, -25, 12, 65, -22, 3, 66, -18, -5, 66, -14, -13, + 67, -10, -21, 67, -5, -30, 68, 0, -38, 69, 4, -46, + 65, -41, 65, 65, -40, 63, 65, -39, 59, 65, -38, 53, + 65, -37, 46, 65, -35, 39, 66, -33, 30, 66, -30, 22, + 66, -27, 13, 67, -24, 5, 67, -20, -3, 67, -16, -12, + 68, -12, -20, 69, -7, -28, 69, -2, -36, 70, 2, -44, + 66, -42, 66, 66, -42, 64, 66, -41, 60, 66, -40, 54, + 66, -39, 47, 67, -37, 40, 67, -35, 32, 67, -32, 24, + 67, -29, 15, 68, -26, 7, 68, -22, -2, 69, -18, -10, + 69, -14, -18, 70, -9, -26, 70, -4, -34, 71, 0, -42, + 67, -44, 67, 67, -44, 65, 67, -43, 61, 68, -42, 55, + 68, -40, 49, 68, -39, 42, 68, -36, 34, 68, -34, 26, + 69, -31, 17, 69, -28, 9, 69, -24, 0, 70, -20, -8, + 70, -16, -16, 71, -12, -25, 71, -7, -33, 72, -2, -41, + 69, -46, 68, 69, -45, 66, 69, -45, 62, 69, -43, 57, + 69, -42, 50, 69, -40, 43, 69, -38, 35, 69, -36, 27, + 70, -33, 18, 70, -30, 10, 71, -26, 2, 71, -22, -7, + 71, -18, -15, 72, -14, -23, 73, -9, -31, 73, -4, -39, + 70, -47, 69, 70, -47, 67, 70, -46, 63, 70, -45, 58, + 70, -44, 51, 70, -42, 45, 70, -40, 37, 71, -38, 29, + 71, -35, 20, 71, -32, 12, 72, -28, 3, 72, -24, -5, + 73, -20, -13, 73, -16, -21, 74, -11, -29, 74, -6, -37, + 71, -49, 70, 71, -49, 68, 71, -48, 65, 71, -47, 59, + 71, -45, 53, 71, -44, 46, 72, -42, 38, 72, -39, 30, + 72, -37, 22, 72, -34, 14, 73, -30, 5, 73, -26, -3, + 74, -22, -11, 74, -18, -20, 75, -13, -27, 75, -8, -35, + 72, -51, 71, 72, -50, 69, 72, -49, 66, 72, -48, 60, + 73, -47, 54, 73, -45, 47, 73, -43, 40, 73, -41, 32, + 73, -38, 23, 74, -35, 15, 74, -32, 7, 74, -28, -1, + 75, -24, -9, 75, -20, -18, 76, -15, -26, 77, -11, -34, + 73, -52, 72, 73, -52, 70, 74, -51, 67, 74, -50, 61, + 74, -49, 55, 74, -47, 49, 74, -45, 41, 74, -43, 33, + 75, -40, 25, 75, -37, 17, 75, -34, 8, 76, -30, 0, + 76, -26, -8, 77, -22, -16, 77, -17, -24, 78, -13, -32, + 75, -54, 73, 75, -53, 71, 75, -53, 68, 75, -52, 63, + 75, -50, 57, 75, -49, 50, 75, -47, 43, 75, -45, 35, + 76, -42, 27, 76, -39, 19, 76, -36, 10, 77, -32, 2, + 77, -28, -6, 78, -24, -14, 78, -19, -22, 79, -15, -30, + 76, -55, 74, 76, -55, 72, 76, -54, 69, 76, -53, 64, + 76, -52, 58, 76, -50, 51, 76, -48, 44, 77, -46, 36, + 77, -44, 28, 77, -41, 20, 78, -37, 12, 78, -34, 4, + 78, -30, -4, 79, -26, -13, 79, -21, -21, 80, -17, -29, + 77, -57, 75, 77, -57, 73, 77, -56, 70, 78, -55, 65, + 78, -54, 59, 78, -52, 53, 78, -50, 46, 78, -48, 38, + 78, -46, 30, 79, -43, 22, 79, -40, 14, 79, -36, 6, + 80, -32, -2, 80, -28, -11, 81, -24, -18, 81, -19, -26, + 79, -59, 76, 79, -58, 74, 79, -58, 71, 79, -57, 67, + 79, -55, 61, 79, -54, 55, 79, -52, 47, 79, -50, 40, + 80, -47, 32, 80, -45, 24, 80, -41, 15, 81, -38, 7, + 81, -34, -1, 81, -30, -9, 82, -26, -17, 82, -21, -25, + 80, -60, 77, 80, -60, 75, 80, -59, 72, 80, -58, 68, + 80, -57, 62, 80, -56, 56, 80, -54, 49, 81, -52, 41, + 81, -49, 33, 81, -46, 25, 81, -43, 17, 82, -40, 9, + 82, -36, 1, 83, -32, -7, 83, -28, -15, 84, -23, -23, + 81, -61, 78, 81, -61, 76, 81, -61, 73, 81, -60, 69, + 81, -59, 63, 81, -57, 57, 82, -55, 50, 82, -53, 43, + 82, -51, 35, 82, -48, 27, 83, -45, 19, 83, -42, 11, + 83, -38, 2, 84, -34, -5, 84, -30, -13, 85, -25, -21, + 82, -63, 79, 82, -63, 77, 82, -62, 75, 82, -61, 70, + 82, -60, 65, 83, -59, 58, 83, -57, 51, 83, -55, 44, + 83, -52, 36, 83, -50, 29, 84, -47, 20, 84, -43, 12, + 84, -40, 4, 85, -36, -4, 85, -32, -12, 86, -27, -20, + 83, -64, 80, 83, -64, 78, 84, -63, 76, 84, -63, 71, + 84, -61, 66, 84, -60, 60, 84, -58, 53, 84, -56, 46, + 84, -54, 38, 85, -51, 30, 85, -48, 22, 85, -45, 14, + 86, -42, 6, 86, -38, -2, 86, -34, -10, 87, -29, -18, + 85, -66, 81, 85, -65, 79, 85, -65, 77, 85, -64, 72, + 85, -63, 67, 85, -62, 61, 85, -60, 54, 85, -58, 47, + 86, -55, 39, 86, -53, 32, 86, -50, 23, 86, -47, 16, + 87, -43, 7, 87, -40, 0, 88, -35, -8, 88, -31, -16, + 86, -67, 82, 86, -67, 80, 86, -66, 78, 86, -65, 74, + 86, -64, 68, 86, -63, 62, 86, -61, 56, 86, -59, 49, + 87, -57, 41, 87, -55, 33, 87, -52, 25, 88, -49, 17, + 88, -45, 9, 88, -41, 1, 89, -37, -7, 89, -33, -15, + 87, -68, 83, 87, -68, 81, 87, -68, 79, 87, -67, 75, + 87, -66, 70, 87, -64, 64, 88, -63, 57, 88, -61, 50, + 88, -59, 42, 88, -56, 35, 88, -53, 27, 89, -50, 19, + 89, -47, 11, 89, -43, 3, 90, -39, -5, 90, -35, -13, + 88, -70, 84, 88, -70, 82, 88, -69, 80, 88, -68, 76, + 88, -67, 71, 89, -66, 65, 89, -64, 58, 89, -62, 51, + 89, -60, 44, 89, -58, 36, 90, -55, 28, 90, -52, 20, + 90, -49, 12, 91, -45, 4, 91, -41, -3, 91, -37, -11, + 89, -71, 85, 89, -71, 83, 90, -70, 81, 90, -70, 77, + 90, -69, 72, 90, -67, 66, 90, -66, 60, 90, -64, 53, + 90, -62, 45, 90, -59, 38, 91, -56, 30, 91, -53, 22, + 91, -50, 14, 92, -47, 6, 92, -43, -2, 93, -39, -10, + 24, 47, 35, 24, 47, 27, 25, 48, 16, 25, 49, 5, + 26, 50, -5, 26, 52, -14, 27, 54, -24, 28, 56, -33, + 29, 59, -42, 30, 62, -50, 32, 65, -58, 33, 68, -65, + 34, 72, -73, 36, 75, -79, 38, 78, -86, 39, 82, -92, + 25, 46, 35, 25, 46, 27, 25, 47, 16, 25, 48, 6, + 26, 49, -4, 26, 51, -14, 27, 53, -24, 28, 56, -33, + 29, 58, -42, 30, 61, -50, 32, 65, -58, 33, 68, -65, + 35, 71, -72, 36, 74, -79, 38, 78, -86, 40, 81, -92, + 25, 45, 35, 25, 45, 27, 25, 46, 17, 26, 47, 6, + 26, 48, -4, 27, 50, -14, 28, 52, -24, 28, 55, -32, + 30, 58, -41, 31, 61, -49, 32, 64, -57, 33, 67, -65, + 35, 71, -72, 36, 74, -79, 38, 78, -86, 40, 81, -92, + 25, 44, 35, 25, 45, 27, 26, 45, 17, 26, 46, 7, + 26, 48, -3, 27, 49, -13, 28, 52, -23, 29, 54, -32, + 30, 57, -41, 31, 60, -49, 32, 63, -57, 34, 67, -64, + 35, 70, -72, 37, 73, -78, 38, 77, -85, 40, 81, -92, + 26, 43, 36, 26, 44, 28, 26, 44, 17, 26, 45, 7, + 27, 47, -3, 27, 49, -13, 28, 51, -23, 29, 53, -32, + 30, 56, -41, 31, 59, -49, 32, 63, -57, 34, 66, -64, + 35, 69, -71, 37, 73, -78, 38, 77, -85, 40, 80, -91, + 26, 42, 36, 26, 43, 28, 26, 43, 18, 27, 44, 7, + 27, 46, -2, 28, 48, -12, 28, 50, -22, 29, 53, -31, + 30, 56, -40, 31, 59, -48, 33, 62, -56, 34, 65, -64, + 35, 69, -71, 37, 72, -78, 39, 76, -85, 40, 79, -91, + 26, 41, 36, 26, 41, 29, 27, 42, 18, 27, 43, 8, + 27, 45, -2, 28, 46, -12, 29, 49, -22, 30, 51, -31, + 31, 54, -40, 32, 58, -48, 33, 61, -56, 34, 64, -63, + 36, 68, -71, 37, 71, -77, 39, 75, -84, 41, 79, -91, + 27, 40, 37, 27, 40, 29, 27, 41, 19, 27, 42, 9, + 28, 43, -1, 29, 45, -11, 29, 48, -21, 30, 50, -30, + 31, 53, -39, 32, 56, -47, 33, 60, -55, 35, 63, -62, + 36, 67, -70, 38, 70, -77, 39, 74, -84, 41, 78, -90, + 27, 38, 37, 28, 38, 30, 28, 39, 20, 28, 40, 9, + 28, 42, 0, 29, 44, -10, 30, 46, -20, 31, 49, -29, + 32, 52, -38, 33, 55, -46, 34, 59, -54, 35, 62, -62, + 37, 66, -69, 38, 69, -76, 40, 73, -83, 41, 77, -90, + 28, 36, 38, 28, 37, 30, 28, 37, 20, 29, 38, 10, + 29, 40, 0, 30, 42, -9, 30, 45, -19, 31, 47, -28, + 32, 50, -37, 33, 54, -46, 34, 57, -54, 36, 61, -61, + 37, 65, -69, 38, 68, -76, 40, 72, -83, 42, 76, -89, + 29, 35, 38, 29, 35, 31, 29, 36, 21, 29, 37, 11, + 30, 38, 1, 30, 40, -8, 31, 43, -18, 32, 46, -27, + 33, 49, -36, 34, 52, -45, 35, 56, -53, 36, 59, -60, + 37, 63, -68, 39, 67, -75, 40, 71, -82, 42, 75, -88, + 30, 32, 39, 30, 32, 32, 30, 33, 22, 30, 34, 12, + 31, 36, 2, 31, 38, -7, 32, 41, -17, 33, 43, -26, + 33, 47, -35, 34, 50, -43, 36, 54, -52, 37, 57, -59, + 38, 61, -67, 39, 65, -74, 41, 69, -81, 42, 73, -87, + 30, 30, 40, 31, 30, 33, 31, 31, 23, 31, 32, 13, + 31, 34, 3, 32, 36, -6, 32, 39, -16, 33, 42, -25, + 34, 45, -34, 35, 48, -42, 36, 52, -51, 37, 56, -58, + 39, 60, -66, 40, 64, -73, 41, 68, -80, 43, 72, -87, + 31, 28, 40, 31, 28, 33, 32, 29, 24, 32, 30, 14, + 32, 32, 4, 33, 34, -5, 33, 37, -15, 34, 40, -24, + 35, 43, -33, 36, 46, -41, 37, 50, -50, 38, 54, -57, + 39, 58, -65, 41, 62, -72, 42, 66, -79, 43, 70, -86, + 32, 26, 41, 32, 26, 34, 32, 27, 25, 33, 28, 16, + 33, 30, 5, 33, 32, -4, 34, 35, -13, 35, 38, -23, + 36, 41, -32, 37, 45, -40, 38, 48, -49, 39, 52, -56, + 40, 56, -64, 41, 60, -71, 43, 64, -78, 44, 69, -85, + 33, 23, 42, 33, 24, 35, 33, 25, 26, 33, 26, 17, + 34, 27, 7, 34, 30, -2, 35, 32, -12, 36, 35, -21, + 36, 39, -31, 37, 42, -39, 38, 46, -47, 39, 50, -55, + 41, 54, -63, 42, 59, -70, 43, 63, -77, 45, 67, -84, + 34, 21, 42, 34, 22, 36, 34, 22, 28, 34, 23, 18, + 35, 25, 8, 35, 27, -1, 36, 30, -11, 36, 33, -20, + 37, 37, -29, 38, 40, -38, 39, 44, -46, 40, 48, -54, + 41, 52, -62, 43, 57, -69, 44, 61, -76, 45, 65, -83, + 35, 19, 43, 35, 19, 37, 35, 20, 29, 35, 21, 19, + 36, 23, 9, 36, 25, 0, 37, 28, -10, 37, 31, -19, + 38, 35, -28, 39, 38, -36, 40, 42, -45, 41, 46, -53, + 42, 50, -60, 43, 55, -68, 44, 59, -75, 46, 63, -82, + 36, 16, 44, 36, 17, 38, 36, 18, 30, 36, 19, 20, + 37, 20, 11, 37, 23, 1, 38, 26, -8, 38, 29, -17, + 39, 32, -27, 40, 36, -35, 41, 40, -44, 42, 44, -51, + 43, 48, -59, 44, 53, -67, 45, 57, -74, 47, 61, -81, + 37, 14, 44, 37, 14, 39, 37, 15, 31, 37, 16, 22, + 38, 18, 12, 38, 20, 3, 39, 23, -7, 39, 26, -16, + 40, 30, -25, 41, 34, -34, 42, 38, -42, 43, 42, -50, + 44, 46, -58, 45, 50, -65, 46, 55, -73, 47, 59, -80, + 38, 12, 45, 38, 12, 40, 38, 13, 32, 38, 14, 23, + 39, 16, 13, 39, 18, 4, 40, 21, -5, 40, 24, -14, + 41, 28, -24, 42, 31, -32, 42, 36, -41, 43, 40, -49, + 44, 44, -57, 46, 48, -64, 47, 53, -72, 48, 57, -79, + 39, 9, 46, 39, 10, 41, 39, 10, 33, 39, 12, 24, + 40, 13, 15, 40, 16, 5, 41, 18, -4, 41, 22, -13, + 42, 25, -22, 43, 29, -31, 43, 33, -39, 44, 37, -47, + 45, 42, -55, 46, 46, -63, 48, 51, -70, 49, 55, -77, + 40, 7, 47, 40, 7, 42, 40, 8, 35, 41, 9, 25, + 41, 11, 16, 41, 13, 7, 42, 16, -2, 42, 19, -11, + 43, 23, -21, 43, 27, -29, 44, 31, -38, 45, 35, -46, + 46, 39, -54, 47, 44, -61, 48, 48, -69, 50, 53, -76, + 41, 5, 48, 41, 5, 43, 41, 6, 36, 42, 7, 27, + 42, 8, 18, 42, 11, 8, 43, 14, -1, 43, 17, -10, + 44, 20, -19, 44, 24, -28, 45, 28, -36, 46, 33, -44, + 47, 37, -52, 48, 42, -60, 49, 46, -68, 50, 51, -75, + 42, 2, 49, 42, 3, 44, 43, 3, 37, 43, 4, 28, + 43, 6, 19, 43, 8, 10, 44, 11, 0, 44, 14, -8, + 45, 18, -17, 45, 22, -26, 46, 26, -35, 47, 30, -43, + 48, 35, -51, 49, 39, -59, 50, 44, -66, 51, 49, -73, + 44, 0, 49, 44, 0, 44, 44, 1, 38, 44, 2, 29, + 44, 4, 21, 44, 6, 12, 45, 9, 2, 45, 12, -7, + 46, 16, -16, 47, 19, -24, 47, 24, -33, 48, 28, -41, + 49, 32, -49, 50, 37, -57, 51, 42, -65, 52, 46, -72, + 45, -2, 50, 45, -2, 45, 45, -1, 39, 45, 0, 31, + 45, 1, 22, 46, 4, 13, 46, 6, 4, 46, 10, -5, + 47, 13, -14, 48, 17, -23, 48, 21, -32, 49, 25, -40, + 50, 30, -48, 51, 35, -56, 52, 39, -63, 53, 44, -71, + 46, -5, 51, 46, -4, 47, 46, -3, 40, 46, -2, 32, + 46, 0, 24, 47, 1, 15, 47, 4, 5, 47, 7, -3, + 48, 11, -13, 49, 15, -21, 49, 19, -30, 50, 23, -38, + 51, 27, -46, 52, 32, -54, 53, 37, -62, 54, 42, -69, + 47, -7, 52, 47, -6, 48, 47, -5, 42, 47, -4, 34, + 48, -2, 25, 48, 0, 16, 48, 2, 7, 49, 5, -2, + 49, 8, -11, 50, 12, -20, 50, 17, -29, 51, 21, -37, + 52, 25, -45, 53, 30, -53, 54, 34, -60, 55, 39, -68, + 48, -9, 53, 48, -9, 49, 48, -8, 43, 48, -6, 35, + 49, -5, 27, 49, -3, 18, 49, 0, 8, 50, 2, 0, + 50, 6, -9, 51, 10, -18, 51, 14, -27, 52, 18, -35, + 53, 23, -43, 54, 27, -51, 55, 32, -59, 56, 37, -66, + 49, -11, 54, 49, -11, 50, 49, -10, 44, 50, -9, 36, + 50, -7, 28, 50, -5, 19, 50, -2, 10, 51, 0, 1, + 51, 4, -8, 52, 8, -16, 53, 12, -25, 53, 16, -33, + 54, 20, -41, 55, 25, -49, 56, 30, -57, 57, 35, -65, + 51, -13, 55, 51, -13, 51, 51, -12, 45, 51, -11, 38, + 51, -9, 30, 51, -7, 21, 52, -5, 12, 52, -2, 3, + 52, 1, -6, 53, 5, -15, 54, 9, -24, 54, 14, -32, + 55, 18, -40, 56, 23, -48, 57, 27, -56, 58, 32, -63, + 52, -16, 56, 52, -16, 52, 52, -15, 46, 52, -13, 40, + 52, -12, 31, 53, -10, 23, 53, -7, 14, 53, -5, 5, + 54, -1, -4, 54, 2, -13, 55, 6, -21, 56, 11, -30, + 56, 15, -38, 57, 20, -46, 58, 24, -54, 59, 29, -61, + 53, -18, 56, 53, -18, 53, 53, -17, 48, 53, -16, 41, + 54, -14, 33, 54, -12, 24, 54, -10, 15, 55, -7, 7, + 55, -3, -2, 55, 0, -11, 56, 4, -20, 57, 8, -28, + 57, 13, -36, 58, 17, -44, 59, 22, -52, 60, 27, -60, + 54, -20, 57, 54, -20, 54, 55, -19, 49, 55, -18, 42, + 55, -16, 34, 55, -14, 26, 55, -12, 17, 56, -9, 8, + 56, -6, -1, 57, -2, -9, 57, 2, -18, 58, 6, -26, + 58, 10, -34, 59, 15, -43, 60, 20, -50, 61, 25, -58, + 56, -22, 58, 56, -22, 55, 56, -21, 50, 56, -20, 44, + 56, -18, 36, 56, -16, 28, 57, -14, 18, 57, -11, 10, + 57, -8, 1, 58, -4, -7, 58, 0, -16, 59, 4, -25, + 60, 8, -33, 60, 13, -41, 61, 17, -49, 62, 22, -56, + 57, -24, 59, 57, -24, 56, 57, -23, 51, 57, -22, 45, + 57, -20, 37, 57, -18, 29, 58, -16, 20, 58, -13, 11, + 58, -10, 2, 59, -7, -6, 59, -3, -15, 60, 1, -23, + 61, 6, -31, 61, 10, -39, 62, 15, -47, 63, 20, -55, + 58, -26, 60, 58, -26, 57, 58, -25, 52, 58, -24, 46, + 58, -22, 39, 59, -20, 31, 59, -18, 22, 59, -15, 13, + 60, -12, 4, 60, -9, -4, 61, -5, -13, 61, -1, -21, + 62, 3, -29, 62, 8, -38, 63, 13, -45, 64, 18, -53, + 59, -28, 61, 59, -28, 59, 59, -27, 54, 59, -26, 47, + 60, -24, 40, 60, -22, 32, 60, -20, 23, 60, -18, 15, + 61, -14, 6, 61, -11, -2, 62, -7, -11, 62, -3, -20, + 63, 1, -28, 63, 6, -36, 64, 10, -44, 65, 15, -52, + 60, -30, 62, 61, -30, 60, 61, -29, 55, 61, -28, 49, + 61, -26, 41, 61, -24, 34, 61, -22, 25, 62, -20, 16, + 62, -16, 7, 62, -13, -1, 63, -9, -10, 63, -5, -18, + 64, -1, -26, 65, 3, -34, 65, 8, -42, 66, 13, -50, + 62, -32, 63, 62, -32, 61, 62, -31, 56, 62, -30, 50, + 62, -28, 43, 62, -26, 35, 62, -24, 26, 63, -22, 18, + 63, -19, 9, 63, -15, 1, 64, -11, -8, 64, -8, -16, + 65, -3, -24, 66, 1, -33, 66, 6, -40, 67, 11, -48, + 63, -34, 64, 63, -33, 62, 63, -33, 57, 63, -32, 51, + 63, -30, 44, 63, -28, 37, 64, -26, 28, 64, -24, 20, + 64, -21, 11, 65, -17, 2, 65, -14, -6, 66, -10, -14, + 66, -5, -22, 67, -1, -31, 67, 3, -39, 68, 8, -47, + 64, -36, 65, 64, -35, 63, 64, -35, 58, 64, -33, 52, + 64, -32, 45, 65, -30, 38, 65, -28, 30, 65, -26, 21, + 65, -23, 12, 66, -19, 4, 66, -16, -5, 67, -12, -13, + 67, -7, -21, 68, -3, -29, 69, 1, -37, 69, 6, -45, + 65, -38, 66, 65, -37, 64, 65, -36, 59, 66, -35, 54, + 66, -34, 47, 66, -32, 39, 66, -30, 31, 66, -28, 23, + 67, -25, 14, 67, -21, 6, 67, -18, -3, 68, -14, -11, + 68, -10, -19, 69, -5, -27, 70, 0, -35, 70, 4, -43, + 67, -39, 67, 67, -39, 65, 67, -38, 61, 67, -37, 55, + 67, -36, 48, 67, -34, 41, 67, -32, 33, 67, -29, 25, + 68, -27, 16, 68, -23, 8, 69, -20, -1, 69, -16, -9, + 70, -12, -17, 70, -7, -26, 71, -3, -34, 71, 2, -42, + 68, -41, 68, 68, -41, 66, 68, -40, 62, 68, -39, 56, + 68, -38, 49, 68, -36, 42, 68, -34, 34, 69, -31, 26, + 69, -28, 17, 69, -25, 9, 70, -22, 0, 70, -18, -8, + 71, -14, -16, 71, -10, -24, 72, -5, -32, 72, 0, -40, + 69, -43, 69, 69, -42, 67, 69, -42, 63, 69, -41, 57, + 69, -39, 51, 69, -38, 44, 70, -35, 36, 70, -33, 28, + 70, -30, 19, 71, -27, 11, 71, -24, 2, 71, -20, -6, + 72, -16, -14, 72, -12, -22, 73, -7, -30, 74, -2, -38, + 70, -44, 69, 70, -44, 68, 70, -43, 64, 70, -42, 58, + 70, -41, 52, 71, -39, 45, 71, -37, 37, 71, -35, 29, + 71, -32, 21, 72, -29, 13, 72, -26, 4, 72, -22, -4, + 73, -18, -12, 73, -14, -21, 74, -9, -29, 75, -5, -37, + 71, -46, 70, 71, -46, 68, 71, -45, 65, 72, -44, 60, + 72, -43, 53, 72, -41, 47, 72, -39, 39, 72, -37, 31, + 73, -34, 22, 73, -31, 14, 73, -28, 6, 74, -24, -3, + 74, -20, -11, 75, -16, -19, 75, -11, -27, 76, -7, -35, + 73, -48, 71, 73, -47, 69, 73, -47, 66, 73, -46, 61, + 73, -45, 55, 73, -43, 48, 73, -41, 40, 73, -39, 32, + 74, -36, 24, 74, -33, 16, 74, -30, 7, 75, -26, -1, + 75, -22, -9, 76, -18, -17, 76, -13, -25, 77, -9, -33, + 74, -49, 72, 74, -49, 70, 74, -48, 67, 74, -47, 62, + 74, -46, 56, 74, -45, 49, 74, -43, 42, 75, -40, 34, + 75, -38, 26, 75, -35, 17, 76, -32, 9, 76, -28, 1, + 76, -24, -7, 77, -20, -16, 77, -16, -23, 78, -11, -31, + 75, -51, 73, 75, -51, 71, 75, -50, 68, 75, -49, 63, + 75, -48, 57, 75, -46, 51, 76, -44, 43, 76, -42, 35, + 76, -40, 27, 76, -37, 19, 77, -33, 11, 77, -30, 3, + 78, -26, -5, 78, -22, -14, 79, -18, -22, 79, -13, -30, + 76, -53, 74, 76, -52, 72, 76, -52, 69, 76, -51, 64, + 76, -49, 58, 77, -48, 52, 77, -46, 44, 77, -44, 37, + 77, -41, 29, 78, -39, 21, 78, -35, 12, 78, -32, 4, + 79, -28, -4, 79, -24, -12, 80, -20, -20, 80, -15, -28, + 78, -55, 75, 78, -54, 74, 78, -54, 71, 78, -53, 66, + 78, -52, 60, 78, -50, 54, 78, -48, 46, 78, -46, 39, + 79, -43, 31, 79, -41, 23, 79, -38, 14, 80, -34, 6, + 80, -31, -2, 81, -27, -10, 81, -22, -18, 82, -18, -26, + 79, -56, 76, 79, -56, 75, 79, -55, 72, 79, -54, 67, + 79, -53, 61, 79, -52, 55, 79, -50, 48, 80, -48, 40, + 80, -45, 32, 80, -43, 24, 81, -39, 16, 81, -36, 8, + 81, -32, 0, 82, -29, -8, 82, -24, -16, 83, -20, -24, + 80, -58, 77, 80, -57, 76, 80, -57, 73, 80, -56, 68, + 80, -55, 62, 81, -53, 56, 81, -51, 49, 81, -49, 42, + 81, -47, 34, 81, -44, 26, 82, -41, 17, 82, -38, 10, + 82, -34, 1, 83, -30, -7, 83, -26, -15, 84, -22, -23, + 81, -59, 78, 81, -59, 77, 81, -58, 74, 81, -57, 69, + 82, -56, 64, 82, -55, 58, 82, -53, 50, 82, -51, 43, + 82, -49, 35, 83, -46, 27, 83, -43, 19, 83, -40, 11, + 84, -36, 3, 84, -32, -5, 84, -28, -13, 85, -24, -21, + 83, -61, 79, 83, -60, 78, 83, -60, 75, 83, -59, 70, + 83, -58, 65, 83, -56, 59, 83, -55, 52, 83, -53, 45, + 83, -50, 37, 84, -48, 29, 84, -45, 21, 84, -42, 13, + 85, -38, 5, 85, -34, -3, 86, -30, -11, 86, -26, -19, + 84, -62, 80, 84, -62, 79, 84, -61, 76, 84, -60, 72, + 84, -59, 66, 84, -58, 60, 84, -56, 53, 84, -54, 46, + 85, -52, 38, 85, -49, 31, 85, -46, 22, 85, -43, 14, + 86, -40, 6, 86, -36, -2, 87, -32, -9, 87, -28, -18, + 85, -63, 81, 85, -63, 80, 85, -63, 77, 85, -62, 73, + 85, -61, 67, 85, -59, 61, 85, -58, 55, 86, -56, 48, + 86, -53, 40, 86, -51, 32, 86, -48, 24, 87, -45, 16, + 87, -42, 8, 87, -38, 0, 88, -34, -8, 88, -30, -16, + 86, -65, 82, 86, -65, 80, 86, -64, 78, 86, -63, 74, + 86, -62, 69, 86, -61, 63, 87, -59, 56, 87, -57, 49, + 87, -55, 41, 87, -53, 34, 87, -50, 25, 88, -47, 18, + 88, -43, 9, 89, -40, 2, 89, -36, -6, 89, -32, -14, + 87, -66, 83, 87, -66, 81, 87, -66, 79, 87, -65, 75, + 88, -64, 70, 88, -62, 64, 88, -61, 57, 88, -59, 50, + 88, -57, 43, 88, -54, 35, 89, -51, 27, 89, -48, 19, + 89, -45, 11, 90, -41, 3, 90, -38, -4, 91, -33, -12, + 89, -68, 84, 89, -67, 82, 89, -67, 80, 89, -66, 76, + 89, -65, 71, 89, -64, 65, 89, -62, 59, 89, -60, 52, + 89, -58, 44, 90, -56, 37, 90, -53, 29, 90, -50, 21, + 90, -47, 13, 91, -43, 5, 91, -39, -3, 92, -35, -11, + 90, -69, 85, 90, -69, 83, 90, -68, 81, 90, -68, 77, + 90, -67, 72, 90, -65, 66, 90, -64, 60, 90, -62, 53, + 91, -60, 46, 91, -57, 38, 91, -55, 30, 91, -52, 22, + 92, -49, 14, 92, -45, 7, 92, -41, -1, 93, -37, -9, + 26, 49, 37, 27, 49, 29, 27, 50, 19, 27, 51, 9, + 28, 52, -1, 28, 54, -11, 29, 56, -21, 30, 58, -30, + 31, 61, -39, 32, 63, -47, 33, 66, -56, 34, 69, -63, + 36, 73, -70, 37, 76, -77, 39, 79, -84, 41, 82, -90, + 27, 48, 37, 27, 49, 30, 27, 49, 19, 27, 50, 9, + 28, 51, -1, 28, 53, -11, 29, 55, -21, 30, 57, -30, + 31, 60, -39, 32, 63, -47, 33, 66, -55, 35, 69, -63, + 36, 72, -70, 38, 75, -77, 39, 79, -84, 41, 82, -90, + 27, 48, 38, 27, 48, 30, 27, 49, 20, 28, 49, 9, + 28, 51, -1, 29, 52, -11, 29, 54, -21, 30, 57, -30, + 31, 59, -39, 32, 62, -47, 34, 65, -55, 35, 68, -62, + 36, 72, -70, 38, 75, -77, 39, 78, -84, 41, 82, -90, + 27, 47, 38, 27, 47, 30, 28, 48, 20, 28, 49, 10, + 28, 50, 0, 29, 52, -10, 30, 54, -20, 30, 56, -29, + 31, 59, -38, 32, 62, -46, 34, 65, -55, 35, 68, -62, + 36, 71, -70, 38, 74, -76, 39, 78, -83, 41, 81, -90, + 28, 46, 38, 28, 46, 30, 28, 47, 20, 28, 48, 10, + 29, 49, 0, 29, 51, -10, 30, 53, -20, 31, 55, -29, + 32, 58, -38, 33, 61, -46, 34, 64, -54, 35, 67, -62, + 37, 71, -69, 38, 74, -76, 40, 77, -83, 41, 81, -89, + 28, 45, 38, 28, 45, 31, 28, 46, 21, 28, 47, 10, + 29, 48, 0, 29, 50, -9, 30, 52, -19, 31, 54, -28, + 32, 57, -37, 33, 60, -46, 34, 63, -54, 35, 66, -61, + 37, 70, -69, 38, 73, -76, 40, 77, -83, 41, 80, -89, + 28, 44, 39, 28, 44, 31, 29, 45, 21, 29, 46, 11, + 29, 47, 0, 30, 49, -9, 31, 51, -19, 31, 53, -28, + 32, 56, -37, 33, 59, -45, 35, 62, -53, 36, 66, -61, + 37, 69, -68, 39, 72, -75, 40, 76, -82, 42, 79, -89, + 29, 43, 39, 29, 43, 31, 29, 44, 22, 29, 45, 12, + 30, 46, 1, 30, 48, -8, 31, 50, -18, 32, 52, -27, + 33, 55, -36, 34, 58, -45, 35, 61, -53, 36, 65, -60, + 37, 68, -68, 39, 71, -75, 40, 75, -82, 42, 79, -88, + 29, 41, 40, 29, 42, 32, 30, 42, 22, 30, 43, 12, + 30, 44, 2, 31, 46, -7, 31, 48, -17, 32, 51, -26, + 33, 54, -36, 34, 57, -44, 35, 60, -52, 37, 63, -60, + 38, 67, -67, 39, 70, -74, 41, 74, -81, 42, 78, -88, + 30, 40, 40, 30, 40, 33, 30, 41, 23, 30, 42, 13, + 31, 43, 2, 31, 45, -7, 32, 47, -17, 33, 50, -26, + 34, 52, -35, 35, 55, -43, 36, 59, -51, 37, 62, -59, + 38, 66, -67, 40, 69, -74, 41, 73, -81, 43, 77, -87, + 30, 38, 41, 31, 38, 33, 31, 39, 24, 31, 40, 14, + 31, 41, 3, 32, 43, -6, 33, 45, -16, 33, 48, -25, + 34, 51, -34, 35, 54, -42, 36, 57, -51, 37, 61, -58, + 39, 65, -66, 40, 68, -73, 41, 72, -80, 43, 76, -87, + 31, 36, 41, 31, 36, 34, 32, 37, 25, 32, 38, 15, + 32, 39, 4, 33, 41, -4, 33, 43, -14, 34, 46, -24, + 35, 49, -33, 36, 52, -41, 37, 56, -50, 38, 59, -57, + 39, 63, -65, 41, 66, -72, 42, 70, -79, 44, 74, -86, + 32, 34, 42, 32, 34, 35, 32, 35, 26, 33, 36, 16, + 33, 37, 5, 33, 39, -3, 34, 41, -13, 35, 44, -23, + 36, 47, -32, 36, 50, -40, 38, 54, -49, 39, 57, -56, + 40, 61, -64, 41, 65, -71, 42, 69, -78, 44, 73, -85, + 33, 32, 42, 33, 32, 36, 33, 33, 27, 33, 34, 17, + 34, 35, 6, 34, 37, -2, 35, 40, -12, 35, 42, -21, + 36, 45, -31, 37, 49, -39, 38, 52, -48, 39, 56, -55, + 40, 60, -63, 42, 63, -70, 43, 67, -78, 44, 71, -84, + 34, 30, 43, 34, 30, 36, 34, 31, 27, 34, 32, 18, + 34, 33, 8, 35, 35, -1, 35, 38, -11, 36, 40, -20, + 37, 43, -30, 38, 47, -38, 39, 50, -47, 40, 54, -54, + 41, 58, -62, 42, 62, -69, 44, 66, -77, 45, 70, -83, + 34, 27, 43, 35, 28, 37, 35, 28, 28, 35, 29, 19, + 35, 31, 9, 36, 33, 0, 36, 35, -10, 37, 38, -19, + 38, 41, -28, 39, 45, -37, 40, 49, -46, 41, 52, -53, + 42, 56, -61, 43, 60, -68, 44, 64, -76, 46, 68, -82, + 35, 25, 44, 35, 26, 38, 36, 26, 30, 36, 27, 20, + 36, 29, 10, 37, 31, 0, 37, 33, -9, 38, 36, -18, + 38, 39, -27, 39, 43, -36, 40, 47, -44, 41, 50, -52, + 42, 54, -60, 44, 58, -67, 45, 62, -75, 46, 66, -81, + 36, 23, 45, 36, 23, 39, 37, 24, 31, 37, 25, 21, + 37, 27, 11, 37, 29, 2, 38, 31, -7, 39, 34, -17, + 39, 37, -26, 40, 41, -34, 41, 44, -43, 42, 48, -51, + 43, 52, -59, 44, 56, -66, 45, 60, -74, 47, 65, -80, + 37, 21, 45, 37, 21, 40, 37, 22, 32, 38, 23, 22, + 38, 24, 12, 38, 26, 3, 39, 29, -6, 39, 32, -15, + 40, 35, -25, 41, 38, -33, 42, 42, -42, 43, 46, -50, + 44, 50, -58, 45, 54, -65, 46, 58, -73, 47, 63, -79, + 38, 18, 46, 38, 19, 40, 38, 19, 33, 39, 20, 23, + 39, 22, 14, 39, 24, 4, 40, 27, -5, 40, 29, -14, + 41, 33, -23, 42, 36, -32, 43, 40, -41, 44, 44, -48, + 45, 48, -56, 46, 52, -64, 47, 56, -71, 48, 61, -78, + 39, 16, 47, 39, 16, 41, 39, 17, 34, 40, 18, 25, + 40, 20, 15, 40, 22, 6, 41, 24, -3, 41, 27, -12, + 42, 31, -22, 43, 34, -30, 44, 38, -39, 44, 42, -47, + 45, 46, -55, 47, 50, -63, 48, 54, -70, 49, 59, -77, + 40, 14, 47, 40, 14, 42, 40, 15, 35, 41, 16, 26, + 41, 17, 16, 41, 19, 7, 42, 22, -2, 42, 25, -11, + 43, 28, -20, 44, 32, -29, 44, 36, -38, 45, 40, -46, + 46, 44, -54, 47, 48, -61, 48, 52, -69, 50, 57, -76, + 41, 11, 48, 41, 12, 43, 41, 12, 36, 42, 13, 27, + 42, 15, 18, 42, 17, 9, 43, 20, -1, 43, 22, -10, + 44, 26, -19, 45, 29, -28, 45, 33, -36, 46, 37, -44, + 47, 41, -52, 48, 46, -60, 49, 50, -68, 50, 55, -75, + 42, 9, 49, 42, 9, 44, 43, 10, 37, 43, 11, 28, + 43, 13, 19, 43, 15, 10, 44, 17, 0, 44, 20, -8, + 45, 24, -17, 45, 27, -26, 46, 31, -35, 47, 35, -43, + 48, 39, -51, 49, 44, -59, 50, 48, -66, 51, 53, -73, + 43, 7, 50, 43, 7, 45, 44, 8, 38, 44, 9, 30, + 44, 10, 21, 44, 12, 12, 45, 15, 2, 45, 18, -7, + 46, 21, -16, 46, 25, -25, 47, 29, -33, 48, 33, -41, + 49, 37, -49, 50, 41, -57, 51, 46, -65, 52, 50, -72, + 45, 4, 51, 45, 5, 46, 45, 5, 39, 45, 6, 31, + 45, 8, 22, 45, 10, 13, 46, 12, 3, 46, 15, -5, + 47, 19, -14, 47, 22, -23, 48, 26, -32, 49, 30, -40, + 50, 35, -48, 51, 39, -56, 52, 43, -64, 53, 48, -71, + 46, 2, 51, 46, 2, 47, 46, 3, 41, 46, 4, 32, + 46, 5, 24, 46, 7, 15, 47, 10, 5, 47, 13, -3, + 48, 16, -13, 48, 20, -21, 49, 24, -30, 50, 28, -38, + 51, 32, -46, 52, 37, -54, 53, 41, -62, 54, 46, -69, + 47, 0, 52, 47, 0, 48, 47, 1, 42, 47, 2, 34, + 47, 3, 25, 48, 5, 16, 48, 8, 7, 48, 11, -2, + 49, 14, -11, 49, 18, -20, 50, 22, -29, 51, 26, -37, + 52, 30, -45, 53, 34, -53, 54, 39, -61, 55, 44, -68, + 48, -3, 53, 48, -2, 49, 48, -1, 43, 48, 0, 35, + 48, 1, 26, 49, 3, 18, 49, 5, 8, 49, 8, 0, + 50, 12, -10, 51, 15, -18, 51, 19, -27, 52, 23, -35, + 53, 27, -43, 54, 32, -51, 54, 37, -59, 56, 41, -67, + 49, -5, 54, 49, -4, 50, 49, -4, 44, 49, -2, 36, + 50, -1, 28, 50, 0, 19, 50, 3, 10, 51, 6, 1, + 51, 9, -8, 52, 13, -17, 52, 17, -26, 53, 21, -34, + 54, 25, -42, 55, 30, -50, 55, 34, -58, 56, 39, -65, + 50, -7, 55, 50, -7, 51, 50, -6, 45, 50, -5, 38, + 51, -3, 29, 51, -1, 21, 51, 1, 11, 52, 4, 2, + 52, 7, -6, 53, 11, -15, 53, 15, -24, 54, 19, -32, + 55, 23, -40, 56, 27, -48, 56, 32, -56, 57, 37, -64, + 51, -9, 55, 51, -9, 52, 51, -8, 46, 52, -7, 39, + 52, -5, 31, 52, -3, 22, 52, -1, 13, 53, 1, 4, + 53, 5, -5, 54, 8, -13, 54, 12, -22, 55, 16, -31, + 56, 20, -39, 57, 25, -47, 57, 29, -55, 58, 34, -62, + 53, -12, 57, 53, -12, 53, 53, -11, 47, 53, -10, 41, + 53, -8, 33, 53, -6, 24, 54, -4, 15, 54, -1, 6, + 55, 2, -3, 55, 5, -11, 56, 9, -20, 56, 13, -29, + 57, 17, -37, 58, 22, -45, 59, 27, -53, 60, 31, -60, + 54, -14, 57, 54, -14, 54, 54, -13, 49, 54, -12, 42, + 54, -10, 34, 55, -8, 25, 55, -6, 16, 55, -3, 8, + 56, 0, -1, 56, 3, -10, 57, 7, -19, 57, 11, -27, + 58, 15, -35, 59, 20, -43, 60, 24, -51, 60, 29, -59, + 55, -16, 58, 55, -16, 55, 55, -15, 50, 55, -14, 43, + 56, -12, 35, 56, -10, 27, 56, -8, 18, 56, -6, 9, + 57, -2, 0, 57, 1, -8, 58, 5, -17, 58, 9, -25, + 59, 13, -33, 60, 17, -42, 61, 22, -49, 61, 27, -57, + 56, -18, 59, 56, -18, 56, 56, -17, 51, 57, -16, 45, + 57, -14, 37, 57, -13, 29, 57, -10, 19, 58, -8, 11, + 58, -5, 2, 58, -1, -6, 59, 2, -15, 60, 6, -24, + 60, 10, -32, 61, 15, -40, 62, 20, -48, 62, 24, -56, + 58, -20, 60, 58, -20, 57, 58, -19, 52, 58, -18, 46, + 58, -17, 38, 58, -15, 30, 58, -12, 21, 59, -10, 13, + 59, -7, 3, 60, -4, -5, 60, 0, -14, 61, 4, -22, + 61, 8, -30, 62, 13, -38, 63, 17, -46, 63, 22, -54, + 59, -22, 61, 59, -22, 58, 59, -21, 53, 59, -20, 47, + 59, -19, 39, 59, -17, 32, 60, -15, 23, 60, -12, 14, + 60, -9, 5, 61, -6, -3, 61, -2, -12, 62, 2, -20, + 62, 6, -28, 63, 10, -37, 64, 15, -45, 65, 20, -52, + 60, -24, 62, 60, -24, 59, 60, -23, 54, 60, -22, 48, + 60, -21, 41, 60, -19, 33, 61, -17, 24, 61, -14, 16, + 61, -11, 7, 62, -8, -1, 62, -4, -10, 63, 0, -19, + 63, 4, -27, 64, 8, -35, 65, 13, -43, 66, 17, -51, + 61, -26, 63, 61, -26, 60, 61, -25, 55, 61, -24, 49, + 61, -23, 42, 62, -21, 35, 62, -19, 26, 62, -16, 17, + 63, -13, 8, 63, -10, 0, 63, -6, -9, 64, -3, -17, + 64, 1, -25, 65, 6, -33, 66, 10, -41, 67, 15, -49, + 62, -28, 64, 62, -28, 61, 62, -27, 57, 62, -26, 51, + 63, -25, 44, 63, -23, 36, 63, -21, 27, 63, -18, 19, + 64, -15, 10, 64, -12, 2, 65, -9, -7, 65, -5, -15, + 66, -1, -23, 66, 4, -32, 67, 8, -40, 68, 13, -47, + 63, -30, 64, 64, -30, 62, 64, -29, 58, 64, -28, 52, + 64, -27, 45, 64, -25, 37, 64, -23, 29, 64, -20, 21, + 65, -17, 12, 65, -14, 3, 66, -11, -5, 66, -7, -14, + 67, -3, -22, 67, 1, -30, 68, 6, -38, 69, 11, -46, + 65, -32, 65, 65, -32, 63, 65, -31, 59, 65, -30, 53, + 65, -29, 46, 65, -27, 39, 65, -25, 30, 66, -22, 22, + 66, -20, 13, 66, -16, 5, 67, -13, -4, 67, -9, -12, + 68, -5, -20, 68, -1, -28, 69, 3, -36, 70, 8, -44, + 66, -34, 66, 66, -34, 64, 66, -33, 60, 66, -32, 54, + 66, -30, 48, 66, -29, 40, 67, -27, 32, 67, -24, 24, + 67, -22, 15, 68, -19, 7, 68, -15, -2, 68, -11, -10, + 69, -7, -18, 69, -3, -27, 70, 1, -35, 71, 6, -43, + 67, -36, 67, 67, -35, 65, 67, -35, 61, 67, -34, 55, + 67, -32, 49, 68, -31, 42, 68, -29, 33, 68, -26, 25, + 68, -24, 17, 69, -21, 8, 69, -17, 0, 70, -13, -9, + 70, -9, -17, 71, -5, -25, 71, 0, -33, 72, 4, -41, + 68, -38, 68, 68, -37, 66, 68, -37, 62, 68, -36, 57, + 69, -34, 50, 69, -33, 43, 69, -30, 35, 69, -28, 27, + 69, -26, 18, 70, -23, 10, 70, -19, 1, 71, -16, -7, + 71, -12, -15, 72, -7, -23, 72, -3, -31, 73, 2, -39, + 69, -39, 69, 70, -39, 67, 70, -38, 63, 70, -37, 58, + 70, -36, 51, 70, -34, 44, 70, -32, 36, 70, -30, 28, + 71, -27, 20, 71, -25, 12, 71, -21, 3, 72, -18, -5, + 72, -14, -13, 73, -9, -22, 73, -5, -30, 74, 0, -38, + 71, -41, 70, 71, -41, 68, 71, -40, 65, 71, -39, 59, + 71, -38, 53, 71, -36, 46, 71, -34, 38, 72, -32, 30, + 72, -29, 21, 72, -27, 13, 73, -23, 5, 73, -20, -3, + 73, -16, -12, 74, -12, -20, 74, -7, -28, 75, -3, -36, + 72, -43, 71, 72, -42, 69, 72, -42, 66, 72, -41, 60, + 72, -40, 54, 72, -38, 47, 72, -36, 39, 73, -34, 32, + 73, -31, 23, 73, -28, 15, 74, -25, 6, 74, -22, -2, + 75, -18, -10, 75, -14, -18, 76, -9, -26, 76, -5, -34, + 73, -45, 72, 73, -44, 70, 73, -44, 67, 73, -43, 61, + 73, -41, 55, 73, -40, 49, 74, -38, 41, 74, -36, 33, + 74, -33, 25, 74, -30, 16, 75, -27, 8, 75, -24, 0, + 76, -20, -8, 76, -16, -17, 77, -11, -24, 77, -7, -32, + 74, -46, 73, 74, -46, 71, 74, -45, 68, 74, -44, 62, + 75, -43, 56, 75, -42, 50, 75, -40, 42, 75, -38, 35, + 75, -35, 26, 76, -32, 18, 76, -29, 10, 76, -26, 2, + 77, -22, -6, 77, -18, -15, 78, -13, -23, 78, -9, -31, + 75, -48, 74, 76, -48, 72, 76, -47, 69, 76, -46, 64, + 76, -45, 58, 76, -43, 51, 76, -41, 44, 76, -39, 36, + 77, -37, 28, 77, -34, 20, 77, -31, 11, 78, -28, 3, + 78, -24, -5, 78, -20, -13, 79, -15, -21, 79, -11, -29, + 77, -50, 75, 77, -49, 73, 77, -49, 70, 77, -48, 65, + 77, -47, 59, 77, -45, 53, 77, -43, 45, 77, -41, 38, + 78, -39, 29, 78, -36, 21, 78, -33, 13, 79, -30, 5, + 79, -26, -3, 80, -22, -11, 80, -18, -19, 81, -13, -27, + 78, -52, 76, 78, -51, 74, 78, -51, 71, 78, -50, 66, + 78, -49, 60, 79, -47, 54, 79, -45, 47, 79, -43, 39, + 79, -41, 31, 79, -38, 23, 80, -35, 15, 80, -32, 7, + 80, -28, -1, 81, -24, -9, 81, -20, -17, 82, -16, -25, + 79, -53, 77, 79, -53, 75, 79, -52, 72, 79, -51, 67, + 80, -50, 62, 80, -49, 56, 80, -47, 48, 80, -45, 41, + 80, -43, 33, 81, -40, 25, 81, -37, 16, 81, -34, 9, + 82, -30, 0, 82, -26, -8, 83, -22, -16, 83, -18, -24, + 81, -55, 78, 81, -54, 76, 81, -54, 73, 81, -53, 69, + 81, -52, 63, 81, -50, 57, 81, -49, 50, 81, -47, 42, + 81, -44, 34, 82, -42, 26, 82, -39, 18, 82, -36, 10, + 83, -32, 2, 83, -28, -6, 84, -24, -14, 84, -20, -22, + 82, -56, 79, 82, -56, 77, 82, -55, 74, 82, -55, 70, + 82, -53, 64, 82, -52, 58, 82, -50, 51, 82, -48, 44, + 83, -46, 36, 83, -43, 28, 83, -40, 20, 84, -37, 12, + 84, -34, 3, 84, -30, -4, 85, -26, -12, 85, -22, -20, + 83, -58, 80, 83, -57, 78, 83, -57, 75, 83, -56, 71, + 83, -55, 65, 83, -54, 59, 83, -52, 52, 84, -50, 45, + 84, -48, 37, 84, -45, 30, 84, -42, 21, 85, -39, 13, + 85, -36, 5, 85, -32, -3, 86, -28, -11, 86, -24, -19, + 84, -59, 81, 84, -59, 79, 84, -58, 76, 84, -58, 72, + 84, -57, 67, 84, -55, 61, 85, -54, 54, 85, -52, 47, + 85, -49, 39, 85, -47, 31, 86, -44, 23, 86, -41, 15, + 86, -38, 7, 87, -34, -1, 87, -30, -9, 88, -26, -17, + 85, -61, 82, 85, -60, 80, 85, -60, 77, 85, -59, 73, + 86, -58, 68, 86, -57, 62, 86, -55, 55, 86, -53, 48, + 86, -51, 40, 86, -49, 33, 87, -46, 24, 87, -43, 17, + 87, -40, 8, 88, -36, 0, 88, -32, -7, 89, -28, -15, + 87, -62, 83, 87, -62, 81, 87, -61, 78, 87, -61, 74, + 87, -60, 69, 87, -58, 63, 87, -57, 56, 87, -55, 49, + 87, -53, 42, 88, -50, 34, 88, -47, 26, 88, -44, 18, + 88, -41, 10, 89, -38, 2, 89, -34, -6, 90, -30, -14, + 88, -64, 84, 88, -63, 82, 88, -63, 79, 88, -62, 75, + 88, -61, 70, 88, -60, 64, 88, -58, 58, 88, -56, 51, + 88, -54, 43, 89, -52, 36, 89, -49, 28, 89, -46, 20, + 90, -43, 12, 90, -39, 4, 90, -36, -4, 91, -32, -12, + 89, -65, 84, 89, -65, 83, 89, -64, 80, 89, -64, 77, + 89, -63, 72, 89, -61, 66, 89, -60, 59, 89, -58, 52, + 90, -56, 45, 90, -53, 37, 90, -51, 29, 90, -48, 21, + 91, -45, 13, 91, -41, 5, 92, -37, -2, 92, -34, -10, + 90, -67, 85, 90, -66, 84, 90, -66, 81, 90, -65, 78, + 90, -64, 73, 90, -63, 67, 90, -61, 60, 91, -59, 54, + 91, -57, 46, 91, -55, 39, 91, -52, 31, 92, -50, 23, + 92, -46, 15, 92, -43, 7, 93, -39, -1, 93, -35, -9, + 28, 51, 40, 28, 52, 32, 29, 52, 22, 29, 53, 11, + 29, 54, 0, 30, 55, -8, 31, 57, -19, 31, 59, -28, + 32, 62, -37, 33, 64, -45, 35, 67, -53, 36, 70, -61, + 37, 73, -68, 39, 77, -75, 40, 80, -82, 42, 83, -89, + 29, 51, 40, 29, 51, 32, 29, 51, 22, 29, 52, 12, + 30, 53, 1, 30, 55, -8, 31, 57, -18, 32, 59, -27, + 33, 61, -36, 34, 64, -45, 35, 67, -53, 36, 70, -60, + 37, 73, -68, 39, 76, -75, 40, 79, -82, 42, 83, -88, + 29, 50, 40, 29, 50, 32, 29, 51, 22, 29, 52, 12, + 30, 53, 1, 30, 54, -8, 31, 56, -18, 32, 58, -27, + 33, 61, -36, 34, 63, -44, 35, 66, -53, 36, 69, -60, + 37, 73, -68, 39, 76, -75, 40, 79, -82, 42, 82, -88, + 29, 49, 40, 29, 50, 32, 29, 50, 22, 30, 51, 12, + 30, 52, 1, 31, 54, -8, 31, 55, -18, 32, 58, -27, + 33, 60, -36, 34, 63, -44, 35, 66, -52, 36, 69, -60, + 38, 72, -68, 39, 75, -74, 40, 79, -82, 42, 82, -88, + 29, 49, 40, 29, 49, 33, 30, 49, 23, 30, 50, 13, + 30, 51, 2, 31, 53, -7, 31, 55, -17, 32, 57, -26, + 33, 60, -35, 34, 62, -44, 35, 65, -52, 37, 68, -60, + 38, 71, -67, 39, 75, -74, 41, 78, -81, 42, 81, -88, + 30, 48, 41, 30, 48, 33, 30, 48, 23, 30, 49, 13, + 31, 50, 2, 31, 52, -7, 32, 54, -17, 33, 56, -26, + 33, 59, -35, 34, 61, -43, 36, 64, -52, 37, 68, -59, + 38, 71, -67, 39, 74, -74, 41, 78, -81, 42, 81, -87, + 30, 47, 41, 30, 47, 33, 30, 47, 23, 31, 48, 13, + 31, 49, 3, 31, 51, -6, 32, 53, -16, 33, 55, -25, + 34, 58, -35, 35, 61, -43, 36, 64, -51, 37, 67, -59, + 38, 70, -66, 40, 73, -73, 41, 77, -81, 43, 80, -87, + 30, 45, 41, 31, 46, 34, 31, 46, 24, 31, 47, 14, + 31, 48, 3, 32, 50, -6, 32, 52, -16, 33, 54, -25, + 34, 57, -34, 35, 60, -42, 36, 63, -51, 37, 66, -58, + 39, 69, -66, 40, 72, -73, 41, 76, -80, 43, 79, -87, + 31, 44, 42, 31, 44, 34, 31, 45, 25, 31, 46, 15, + 32, 47, 4, 32, 48, -5, 33, 51, -15, 34, 53, -24, + 35, 56, -33, 35, 58, -42, 37, 62, -50, 38, 65, -58, + 39, 68, -65, 40, 71, -72, 42, 75, -80, 43, 79, -86, + 31, 42, 42, 32, 43, 35, 32, 43, 25, 32, 44, 15, + 32, 45, 5, 33, 47, -4, 33, 49, -14, 34, 52, -23, + 35, 54, -33, 36, 57, -41, 37, 60, -49, 38, 64, -57, + 39, 67, -65, 41, 70, -72, 42, 74, -79, 44, 78, -86, + 32, 41, 42, 32, 41, 35, 32, 42, 26, 33, 43, 16, + 33, 44, 6, 33, 46, -3, 34, 48, -13, 35, 50, -22, + 36, 53, -32, 36, 56, -40, 37, 59, -49, 39, 62, -56, + 40, 66, -64, 41, 69, -71, 42, 73, -78, 44, 76, -85, + 33, 39, 43, 33, 39, 36, 33, 40, 27, 33, 40, 17, + 34, 42, 7, 34, 43, -2, 35, 46, -12, 35, 48, -21, + 36, 51, -31, 37, 54, -39, 38, 57, -48, 39, 60, -55, + 40, 64, -63, 42, 68, -70, 43, 71, -78, 44, 75, -84, + 34, 37, 43, 34, 37, 37, 34, 38, 28, 34, 39, 18, + 34, 40, 8, 35, 42, -1, 35, 44, -11, 36, 46, -20, + 37, 49, -30, 38, 52, -38, 39, 56, -47, 40, 59, -54, + 41, 63, -62, 42, 66, -69, 43, 70, -77, 45, 74, -83, + 34, 35, 44, 34, 35, 37, 34, 36, 29, 35, 37, 19, + 35, 38, 9, 35, 40, 0, 36, 42, -10, 37, 45, -19, + 37, 48, -29, 38, 51, -37, 39, 54, -46, 40, 57, -53, + 41, 61, -61, 43, 65, -69, 44, 69, -76, 45, 72, -83, + 35, 33, 44, 35, 33, 38, 35, 34, 29, 35, 35, 20, + 36, 36, 10, 36, 38, 0, 37, 40, -9, 37, 43, -18, + 38, 46, -28, 39, 49, -36, 40, 52, -45, 41, 56, -53, + 42, 59, -60, 43, 63, -68, 45, 67, -75, 46, 71, -82, + 36, 31, 45, 36, 31, 39, 36, 32, 30, 36, 33, 21, + 37, 34, 11, 37, 36, 1, 38, 38, -8, 38, 41, -17, + 39, 44, -27, 40, 47, -35, 41, 50, -44, 42, 54, -51, + 43, 58, -59, 44, 61, -67, 45, 65, -74, 46, 69, -81, + 37, 29, 45, 37, 29, 40, 37, 30, 31, 37, 31, 22, + 37, 32, 12, 38, 34, 2, 38, 36, -7, 39, 39, -16, + 40, 42, -25, 40, 45, -34, 41, 48, -43, 42, 52, -50, + 43, 56, -58, 45, 60, -66, 46, 64, -73, 47, 68, -80, + 38, 26, 46, 38, 27, 40, 38, 27, 32, 38, 28, 23, + 38, 30, 13, 39, 32, 4, 39, 34, -6, 40, 37, -15, + 40, 40, -24, 41, 43, -33, 42, 46, -41, 43, 50, -49, + 44, 54, -57, 45, 58, -65, 46, 62, -72, 48, 66, -79, + 38, 24, 47, 39, 25, 41, 39, 25, 33, 39, 26, 24, + 39, 28, 14, 39, 29, 5, 40, 32, -4, 41, 34, -13, + 41, 38, -23, 42, 41, -31, 43, 44, -40, 44, 48, -48, + 45, 52, -56, 46, 56, -64, 47, 60, -71, 48, 64, -78, + 39, 22, 47, 39, 22, 42, 40, 23, 34, 40, 24, 25, + 40, 25, 15, 40, 27, 6, 41, 30, -3, 41, 32, -12, + 42, 35, -22, 43, 39, -30, 44, 42, -39, 45, 46, -47, + 46, 50, -55, 47, 54, -62, 48, 58, -70, 49, 62, -77, + 40, 20, 48, 40, 20, 43, 41, 21, 35, 41, 22, 26, + 41, 23, 17, 41, 25, 7, 42, 27, -2, 42, 30, -11, + 43, 33, -20, 44, 36, -29, 44, 40, -38, 45, 44, -46, + 46, 48, -53, 47, 52, -61, 48, 56, -69, 50, 60, -76, + 41, 17, 49, 41, 18, 43, 42, 18, 37, 42, 19, 27, + 42, 21, 18, 42, 22, 9, 43, 25, 0, 43, 28, -9, + 44, 31, -19, 45, 34, -27, 45, 38, -36, 46, 42, -44, + 47, 46, -52, 48, 50, -60, 49, 54, -68, 50, 58, -75, + 42, 15, 50, 42, 15, 44, 43, 16, 38, 43, 17, 28, + 43, 18, 19, 43, 20, 10, 44, 23, 0, 44, 25, -8, + 45, 29, -17, 45, 32, -26, 46, 36, -35, 47, 39, -43, + 48, 43, -51, 49, 48, -59, 50, 52, -66, 51, 56, -73, + 43, 13, 50, 43, 13, 45, 44, 14, 39, 44, 15, 30, + 44, 16, 21, 44, 18, 12, 45, 20, 2, 45, 23, -7, + 46, 26, -16, 46, 30, -25, 47, 33, -33, 48, 37, -42, + 49, 41, -49, 50, 45, -57, 51, 50, -65, 52, 54, -72, + 44, 10, 51, 44, 11, 46, 45, 11, 40, 45, 12, 31, + 45, 14, 22, 45, 16, 13, 46, 18, 3, 46, 21, -5, + 47, 24, -14, 47, 27, -23, 48, 31, -32, 49, 35, -40, + 50, 39, -48, 51, 43, -56, 52, 47, -64, 53, 52, -71, + 45, 8, 52, 46, 8, 47, 46, 9, 41, 46, 10, 32, + 46, 11, 23, 46, 13, 14, 47, 16, 5, 47, 18, -4, + 48, 22, -13, 48, 25, -22, 49, 29, -31, 50, 33, -39, + 51, 37, -47, 52, 41, -55, 53, 45, -62, 54, 50, -70, + 47, 6, 52, 47, 6, 48, 47, 7, 42, 47, 8, 34, + 47, 9, 25, 47, 11, 16, 48, 13, 6, 48, 16, -2, + 49, 19, -11, 49, 23, -20, 50, 27, -29, 51, 30, -37, + 52, 34, -45, 52, 39, -53, 53, 43, -61, 54, 48, -68, + 48, 3, 53, 48, 4, 49, 48, 4, 43, 48, 5, 35, + 48, 7, 26, 48, 8, 17, 49, 11, 8, 49, 14, -1, + 50, 17, -10, 50, 20, -19, 51, 24, -27, 52, 28, -36, + 52, 32, -44, 53, 36, -52, 54, 41, -59, 55, 45, -67, + 49, 1, 54, 49, 1, 50, 49, 2, 44, 49, 3, 36, + 49, 4, 28, 49, 6, 19, 50, 9, 9, 50, 11, 1, + 51, 15, -8, 51, 18, -17, 52, 22, -26, 53, 26, -34, + 53, 30, -42, 54, 34, -50, 55, 38, -58, 56, 43, -65, + 50, -1, 55, 50, -1, 51, 50, 0, 45, 50, 1, 37, + 50, 2, 29, 51, 4, 20, 51, 6, 11, 51, 9, 2, + 52, 12, -7, 52, 16, -15, 53, 19, -24, 54, 23, -33, + 54, 27, -41, 55, 32, -49, 56, 36, -57, 57, 41, -64, + 51, -3, 56, 51, -3, 52, 51, -2, 46, 51, -1, 39, + 51, 0, 30, 52, 2, 22, 52, 4, 12, 52, 7, 4, + 53, 10, -5, 53, 13, -14, 54, 17, -23, 55, 21, -31, + 55, 25, -39, 56, 29, -47, 57, 34, -55, 58, 38, -63, + 52, -6, 56, 52, -5, 53, 52, -4, 47, 52, -3, 40, + 53, -2, 32, 53, 0, 23, 53, 2, 14, 53, 4, 5, + 54, 8, -4, 54, 11, -12, 55, 15, -21, 56, 19, -29, + 56, 23, -38, 57, 27, -46, 58, 31, -54, 59, 36, -61, + 54, -8, 57, 54, -8, 54, 54, -7, 48, 54, -6, 42, + 54, -4, 34, 54, -3, 25, 54, 0, 16, 55, 2, 7, + 55, 5, -2, 56, 8, -10, 56, 12, -19, 57, 16, -27, + 58, 20, -36, 58, 24, -44, 59, 29, -52, 60, 33, -59, + 55, -10, 58, 55, -10, 55, 55, -9, 49, 55, -8, 43, + 55, -7, 35, 55, -5, 27, 56, -3, 17, 56, 0, 9, + 56, 2, 0, 57, 6, -9, 57, 10, -18, 58, 13, -26, + 59, 17, -34, 59, 22, -42, 60, 26, -50, 61, 31, -58, + 56, -13, 59, 56, -12, 56, 56, -11, 51, 56, -10, 44, + 56, -9, 36, 56, -7, 28, 57, -5, 19, 57, -2, 10, + 57, 0, 1, 58, 3, -7, 58, 7, -16, 59, 11, -24, + 60, 15, -32, 60, 20, -41, 61, 24, -48, 62, 29, -56, + 57, -15, 60, 57, -14, 57, 57, -14, 52, 57, -12, 45, + 57, -11, 38, 58, -9, 29, 58, -7, 20, 58, -5, 12, + 59, -2, 3, 59, 1, -5, 60, 5, -14, 60, 9, -23, + 61, 13, -31, 61, 17, -39, 62, 22, -47, 63, 26, -55, + 58, -17, 61, 58, -16, 58, 58, -16, 53, 58, -15, 47, + 59, -13, 39, 59, -11, 31, 59, -9, 22, 59, -7, 13, + 60, -4, 4, 60, -1, -4, 61, 3, -13, 61, 6, -21, + 62, 10, -29, 62, 15, -37, 63, 19, -45, 64, 24, -53, + 59, -19, 62, 59, -18, 59, 59, -18, 54, 60, -17, 48, + 60, -15, 40, 60, -13, 32, 60, -11, 24, 60, -9, 15, + 61, -6, 6, 61, -3, -2, 62, 0, -11, 62, 4, -19, + 63, 8, -27, 64, 13, -36, 64, 17, -44, 65, 22, -51, + 61, -21, 62, 61, -21, 60, 61, -20, 55, 61, -19, 49, + 61, -17, 42, 61, -16, 34, 61, -13, 25, 62, -11, 17, + 62, -8, 8, 62, -5, -1, 63, -2, -9, 63, 2, -18, + 64, 6, -26, 65, 10, -34, 65, 15, -42, 66, 19, -50, + 62, -23, 63, 62, -23, 61, 62, -22, 56, 62, -21, 50, + 62, -19, 43, 62, -18, 35, 62, -16, 27, 63, -13, 18, + 63, -10, 9, 63, -7, 1, 64, -4, -8, 64, 0, -16, + 65, 4, -24, 66, 8, -32, 66, 12, -40, 67, 17, -48, + 63, -25, 64, 63, -25, 62, 63, -24, 57, 63, -23, 51, + 63, -21, 44, 63, -20, 37, 64, -18, 28, 64, -15, 20, + 64, -13, 11, 65, -10, 3, 65, -6, -6, 66, -2, -14, + 66, 1, -22, 67, 6, -31, 67, 10, -39, 68, 15, -47, + 64, -27, 65, 64, -26, 63, 64, -26, 58, 64, -25, 53, + 64, -23, 46, 65, -22, 38, 65, -20, 30, 65, -17, 21, + 65, -15, 12, 66, -12, 4, 66, -8, -5, 67, -5, -13, + 67, -1, -21, 68, 4, -29, 68, 8, -37, 69, 12, -45, + 65, -29, 66, 65, -28, 64, 65, -28, 60, 65, -27, 54, + 66, -25, 47, 66, -24, 40, 66, -22, 31, 66, -19, 23, + 67, -17, 14, 67, -14, 6, 67, -10, -3, 68, -7, -11, + 68, -3, -19, 69, 1, -28, 69, 6, -35, 70, 10, -43, + 66, -31, 67, 66, -30, 65, 66, -30, 61, 67, -29, 55, + 67, -27, 48, 67, -26, 41, 67, -24, 33, 67, -21, 24, + 68, -19, 16, 68, -16, 7, 68, -12, -1, 69, -9, -9, + 69, -5, -17, 70, -1, -26, 71, 3, -34, 71, 8, -42, + 68, -33, 68, 68, -32, 66, 68, -32, 62, 68, -31, 56, + 68, -29, 49, 68, -28, 42, 68, -26, 34, 68, -23, 26, + 69, -21, 17, 69, -18, 9, 70, -15, 0, 70, -11, -8, + 70, -7, -16, 71, -3, -24, 72, 1, -32, 72, 6, -40, + 69, -34, 69, 69, -34, 67, 69, -33, 63, 69, -32, 57, + 69, -31, 51, 69, -30, 44, 69, -28, 36, 70, -25, 28, + 70, -23, 19, 70, -20, 11, 71, -17, 2, 71, -13, -6, + 72, -9, -14, 72, -5, -23, 73, -1, -30, 73, 3, -38, + 70, -36, 70, 70, -36, 68, 70, -35, 64, 70, -34, 58, + 70, -33, 52, 70, -31, 45, 71, -29, 37, 71, -27, 29, + 71, -25, 20, 71, -22, 12, 72, -19, 4, 72, -15, -4, + 73, -11, -12, 73, -7, -21, 74, -3, -29, 74, 1, -37, + 71, -38, 71, 71, -38, 69, 71, -37, 65, 71, -36, 60, + 71, -35, 53, 72, -33, 46, 72, -31, 39, 72, -29, 31, + 72, -27, 22, 73, -24, 14, 73, -21, 5, 73, -17, -3, + 74, -14, -11, 74, -10, -19, 75, -5, -27, 76, -1, -35, + 72, -40, 72, 72, -39, 70, 72, -39, 66, 72, -38, 61, + 73, -37, 55, 73, -35, 48, 73, -33, 40, 73, -31, 32, + 73, -29, 24, 74, -26, 16, 74, -23, 7, 75, -19, -1, + 75, -16, -9, 75, -12, -18, 76, -7, -25, 77, -3, -33, + 74, -42, 72, 74, -41, 71, 74, -41, 67, 74, -40, 62, + 74, -39, 56, 74, -37, 49, 74, -35, 41, 74, -33, 34, + 75, -31, 25, 75, -28, 17, 75, -25, 9, 76, -21, 0, + 76, -18, -8, 77, -14, -16, 77, -9, -24, 78, -5, -32, + 75, -43, 73, 75, -43, 72, 75, -42, 68, 75, -41, 63, + 75, -40, 57, 75, -39, 50, 75, -37, 43, 75, -35, 35, + 76, -32, 27, 76, -30, 19, 76, -27, 10, 77, -23, 2, + 77, -20, -6, 78, -16, -14, 78, -11, -22, 79, -7, -30, + 76, -45, 74, 76, -45, 72, 76, -44, 69, 76, -43, 64, + 76, -42, 58, 76, -41, 52, 76, -39, 44, 77, -37, 37, + 77, -34, 28, 77, -32, 20, 78, -29, 12, 78, -25, 4, + 78, -22, -4, 79, -18, -13, 79, -14, -20, 80, -9, -29, + 77, -47, 75, 77, -46, 73, 77, -46, 70, 77, -45, 65, + 77, -44, 59, 77, -42, 53, 78, -40, 46, 78, -38, 38, + 78, -36, 30, 78, -33, 22, 79, -30, 13, 79, -27, 5, + 79, -24, -3, 80, -20, -11, 80, -16, -19, 81, -11, -27, + 79, -49, 76, 79, -48, 75, 79, -48, 72, 79, -47, 67, + 79, -46, 61, 79, -44, 55, 79, -43, 47, 79, -41, 40, + 80, -38, 32, 80, -36, 24, 80, -33, 15, 80, -30, 7, + 81, -26, 0, 81, -22, -9, 82, -18, -17, 82, -14, -25, + 80, -50, 77, 80, -50, 76, 80, -50, 73, 80, -49, 68, + 80, -48, 62, 80, -46, 56, 80, -44, 49, 80, -42, 41, + 81, -40, 33, 81, -38, 25, 81, -35, 17, 82, -32, 9, + 82, -28, 1, 82, -24, -7, 83, -20, -15, 83, -16, -23, + 81, -52, 78, 81, -52, 77, 81, -51, 74, 81, -50, 69, + 81, -49, 63, 81, -48, 57, 81, -46, 50, 82, -44, 43, + 82, -42, 35, 82, -39, 27, 82, -36, 19, 83, -33, 11, + 83, -30, 2, 84, -26, -5, 84, -22, -13, 85, -18, -21, + 82, -54, 79, 82, -53, 78, 82, -53, 75, 82, -52, 70, + 82, -51, 65, 82, -50, 59, 83, -48, 51, 83, -46, 44, + 83, -44, 36, 83, -41, 29, 84, -38, 20, 84, -35, 12, + 84, -32, 4, 85, -28, -4, 85, -24, -12, 86, -20, -20, + 83, -55, 80, 83, -55, 78, 83, -54, 76, 83, -54, 71, + 84, -52, 66, 84, -51, 60, 84, -49, 53, 84, -48, 46, + 84, -45, 38, 84, -43, 30, 85, -40, 22, 85, -37, 14, + 85, -34, 6, 86, -30, -2, 86, -26, -10, 87, -22, -18, + 84, -57, 81, 84, -56, 79, 85, -56, 77, 85, -55, 73, + 85, -54, 67, 85, -53, 61, 85, -51, 54, 85, -49, 47, + 85, -47, 39, 86, -45, 32, 86, -42, 23, 86, -39, 16, + 87, -36, 7, 87, -32, -1, 87, -28, -8, 88, -24, -16, + 86, -58, 82, 86, -58, 80, 86, -57, 78, 86, -57, 74, + 86, -56, 68, 86, -54, 62, 86, -53, 56, 86, -51, 49, + 86, -49, 41, 87, -46, 33, 87, -44, 25, 87, -41, 17, + 88, -37, 9, 88, -34, 1, 88, -30, -7, 89, -26, -15, + 87, -60, 83, 87, -59, 81, 87, -59, 79, 87, -58, 75, + 87, -57, 70, 87, -56, 64, 87, -54, 57, 87, -52, 50, + 88, -50, 42, 88, -48, 35, 88, -45, 26, 88, -42, 19, + 89, -39, 11, 89, -36, 3, 90, -32, -5, 90, -28, -13, + 88, -61, 84, 88, -61, 82, 88, -60, 80, 88, -60, 76, + 88, -59, 71, 88, -58, 65, 88, -56, 58, 89, -54, 51, + 89, -52, 44, 89, -50, 36, 89, -47, 28, 90, -44, 20, + 90, -41, 12, 90, -37, 4, 91, -34, -3, 91, -30, -11, + 89, -63, 85, 89, -62, 83, 89, -62, 81, 89, -61, 77, + 89, -60, 72, 89, -59, 66, 90, -57, 60, 90, -56, 53, + 90, -54, 45, 90, -51, 38, 90, -49, 30, 91, -46, 22, + 91, -43, 14, 91, -39, 6, 92, -36, -2, 92, -32, -10, + 90, -64, 86, 90, -64, 84, 90, -63, 82, 90, -63, 78, + 91, -62, 73, 91, -61, 67, 91, -59, 61, 91, -57, 54, + 91, -55, 47, 91, -53, 39, 92, -50, 31, 92, -48, 23, + 92, -45, 15, 93, -41, 7, 93, -38, 0, 93, -34, -8, + 30, 53, 42, 30, 54, 34, 30, 54, 24, 31, 55, 14, + 31, 56, 3, 32, 57, -6, 32, 59, -16, 33, 61, -25, + 34, 63, -34, 35, 66, -43, 36, 69, -51, 37, 71, -58, + 38, 74, -66, 40, 77, -73, 41, 81, -80, 43, 84, -87, + 30, 53, 42, 30, 53, 34, 31, 54, 24, 31, 54, 14, + 31, 55, 3, 32, 57, -5, 32, 58, -16, 33, 60, -25, + 34, 63, -34, 35, 65, -42, 36, 68, -51, 37, 71, -58, + 39, 74, -66, 40, 77, -73, 41, 80, -80, 43, 83, -87, + 31, 52, 42, 31, 52, 34, 31, 53, 25, 31, 54, 14, + 31, 55, 4, 32, 56, -5, 33, 58, -15, 33, 60, -24, + 34, 62, -34, 35, 65, -42, 36, 68, -50, 37, 70, -58, + 39, 74, -66, 40, 77, -73, 42, 80, -80, 43, 83, -86, + 31, 52, 42, 31, 52, 35, 31, 52, 25, 31, 53, 15, + 32, 54, 4, 32, 56, -5, 33, 57, -15, 34, 59, -24, + 34, 62, -33, 35, 64, -42, 37, 67, -50, 38, 70, -58, + 39, 73, -65, 40, 76, -72, 42, 79, -80, 43, 83, -86, + 31, 51, 42, 31, 51, 35, 31, 52, 25, 32, 52, 15, + 32, 53, 4, 32, 55, -4, 33, 57, -15, 34, 59, -24, + 35, 61, -33, 36, 64, -41, 37, 66, -50, 38, 69, -57, + 39, 73, -65, 40, 76, -72, 42, 79, -79, 43, 82, -86, + 31, 50, 43, 31, 50, 35, 32, 51, 25, 32, 51, 15, + 32, 53, 5, 33, 54, -4, 33, 56, -14, 34, 58, -23, + 35, 60, -33, 36, 63, -41, 37, 66, -49, 38, 69, -57, + 39, 72, -65, 41, 75, -72, 42, 78, -79, 44, 82, -86, + 32, 49, 43, 32, 49, 35, 32, 50, 26, 32, 51, 16, + 33, 52, 5, 33, 53, -4, 34, 55, -14, 34, 57, -23, + 35, 59, -32, 36, 62, -41, 37, 65, -49, 38, 68, -57, + 40, 71, -64, 41, 74, -71, 42, 78, -79, 44, 81, -85, + 32, 48, 43, 32, 48, 36, 32, 49, 26, 33, 49, 16, + 33, 51, 6, 33, 52, -3, 34, 54, -13, 35, 56, -22, + 36, 58, -32, 36, 61, -40, 38, 64, -48, 39, 67, -56, + 40, 70, -64, 41, 73, -71, 43, 77, -78, 44, 80, -85, + 33, 47, 43, 33, 47, 36, 33, 47, 27, 33, 48, 17, + 33, 49, 6, 34, 51, -2, 34, 53, -12, 35, 55, -22, + 36, 57, -31, 37, 60, -39, 38, 63, -48, 39, 66, -56, + 40, 69, -63, 42, 73, -70, 43, 76, -78, 44, 79, -84, + 33, 45, 44, 33, 45, 37, 33, 46, 27, 34, 47, 18, + 34, 48, 7, 34, 49, -2, 35, 51, -12, 36, 53, -21, + 36, 56, -30, 37, 59, -39, 38, 62, -47, 39, 65, -55, + 41, 68, -63, 42, 72, -70, 43, 75, -77, 45, 78, -84, + 34, 44, 44, 34, 44, 37, 34, 44, 28, 34, 45, 18, + 34, 46, 8, 35, 48, -1, 35, 50, -11, 36, 52, -20, + 37, 55, -29, 38, 57, -38, 39, 61, -47, 40, 64, -54, + 41, 67, -62, 42, 70, -69, 44, 74, -77, 45, 77, -83, + 34, 42, 45, 34, 42, 38, 35, 42, 29, 35, 43, 19, + 35, 44, 9, 36, 46, 0, 36, 48, -10, 37, 50, -19, + 38, 53, -28, 38, 56, -37, 39, 59, -46, 40, 62, -53, + 42, 65, -61, 43, 69, -68, 44, 72, -76, 45, 76, -83, + 35, 40, 45, 35, 40, 39, 35, 41, 30, 35, 42, 20, + 36, 43, 10, 36, 44, 0, 37, 46, -9, 37, 49, -18, + 38, 51, -28, 39, 54, -36, 40, 57, -45, 41, 61, -52, + 42, 64, -60, 43, 67, -68, 45, 71, -75, 46, 75, -82, + 36, 38, 45, 36, 38, 39, 36, 39, 31, 36, 40, 21, + 36, 41, 11, 37, 42, 1, 37, 45, -8, 38, 47, -17, + 39, 50, -27, 40, 53, -35, 41, 56, -44, 41, 59, -52, + 43, 63, -59, 44, 66, -67, 45, 70, -74, 46, 73, -81, + 36, 36, 46, 36, 36, 40, 37, 37, 31, 37, 38, 22, + 37, 39, 12, 38, 41, 2, 38, 43, -7, 39, 45, -16, + 39, 48, -26, 40, 51, -34, 41, 54, -43, 42, 57, -51, + 43, 61, -58, 44, 64, -66, 46, 68, -73, 47, 72, -80, + 37, 34, 46, 37, 34, 41, 37, 35, 32, 38, 36, 23, + 38, 37, 13, 38, 39, 3, 39, 41, -6, 39, 43, -15, + 40, 46, -25, 41, 49, -33, 42, 52, -42, 43, 56, -50, + 44, 59, -57, 45, 63, -65, 46, 67, -73, 47, 70, -79, + 38, 32, 47, 38, 32, 41, 38, 33, 33, 38, 34, 24, + 39, 35, 14, 39, 37, 4, 40, 39, -5, 40, 41, -14, + 41, 44, -23, 42, 47, -32, 42, 50, -41, 43, 54, -49, + 44, 58, -56, 46, 61, -64, 47, 65, -72, 48, 69, -79, + 39, 30, 47, 39, 30, 42, 39, 31, 34, 39, 32, 25, + 39, 33, 15, 40, 35, 6, 40, 37, -4, 41, 39, -13, + 42, 42, -22, 42, 45, -31, 43, 49, -40, 44, 52, -48, + 45, 56, -55, 46, 59, -63, 47, 63, -71, 49, 67, -78, + 40, 28, 48, 40, 28, 43, 40, 29, 35, 40, 29, 26, + 40, 31, 16, 41, 32, 7, 41, 35, -3, 42, 37, -12, + 42, 40, -21, 43, 43, -30, 44, 47, -38, 45, 50, -46, + 46, 54, -54, 47, 57, -62, 48, 61, -70, 49, 65, -77, + 41, 26, 49, 41, 26, 43, 41, 26, 36, 41, 27, 27, + 41, 29, 17, 42, 30, 8, 42, 32, -1, 43, 35, -10, + 43, 38, -20, 44, 41, -28, 45, 44, -37, 46, 48, -45, + 46, 52, -53, 48, 56, -61, 49, 59, -68, 50, 64, -75, + 42, 23, 49, 42, 24, 44, 42, 24, 37, 42, 25, 28, + 42, 26, 18, 42, 28, 9, 43, 30, 0, 43, 33, -9, + 44, 36, -18, 45, 39, -27, 45, 42, -36, 46, 46, -44, + 47, 50, -52, 48, 54, -60, 49, 58, -67, 51, 62, -74, + 42, 21, 50, 43, 21, 45, 43, 22, 38, 43, 23, 29, + 43, 24, 20, 43, 26, 10, 44, 28, 1, 44, 31, -8, + 45, 34, -17, 46, 37, -26, 46, 40, -35, 47, 44, -43, + 48, 48, -51, 49, 52, -58, 50, 56, -66, 51, 60, -73, + 43, 19, 51, 43, 19, 46, 44, 20, 39, 44, 20, 30, + 44, 22, 21, 44, 23, 12, 45, 26, 2, 45, 28, -6, + 46, 31, -16, 46, 34, -24, 47, 38, -33, 48, 42, -41, + 49, 45, -49, 50, 49, -57, 51, 53, -65, 52, 58, -72, + 44, 16, 51, 44, 17, 46, 45, 17, 40, 45, 18, 31, + 45, 20, 22, 45, 21, 13, 46, 23, 3, 46, 26, -5, + 47, 29, -14, 47, 32, -23, 48, 36, -32, 49, 39, -40, + 50, 43, -48, 51, 47, -56, 52, 51, -64, 53, 56, -71, + 45, 14, 52, 45, 14, 47, 46, 15, 41, 46, 16, 32, + 46, 17, 24, 46, 19, 15, 47, 21, 5, 47, 24, -4, + 48, 27, -13, 48, 30, -22, 49, 34, -31, 50, 37, -39, + 51, 41, -47, 51, 45, -55, 52, 49, -62, 54, 54, -70, + 46, 12, 53, 46, 12, 48, 47, 13, 42, 47, 14, 34, + 47, 15, 25, 47, 17, 16, 48, 19, 6, 48, 21, -2, + 49, 24, -12, 49, 28, -20, 50, 31, -29, 51, 35, -37, + 51, 39, -45, 52, 43, -53, 53, 47, -61, 54, 51, -68, + 47, 9, 54, 48, 10, 49, 48, 10, 43, 48, 11, 35, + 48, 13, 26, 48, 14, 17, 49, 17, 8, 49, 19, -1, + 50, 22, -10, 50, 25, -19, 51, 29, -28, 52, 33, -36, + 52, 37, -44, 53, 41, -52, 54, 45, -60, 55, 49, -67, + 49, 7, 54, 49, 7, 50, 49, 8, 44, 49, 9, 36, + 49, 10, 28, 49, 12, 19, 50, 14, 9, 50, 17, 0, + 51, 20, -9, 51, 23, -17, 52, 27, -26, 52, 30, -34, + 53, 34, -42, 54, 39, -50, 55, 43, -58, 56, 47, -66, + 50, 5, 55, 50, 5, 51, 50, 6, 45, 50, 7, 37, + 50, 8, 29, 50, 10, 20, 51, 12, 11, 51, 14, 2, + 52, 18, -7, 52, 21, -16, 53, 24, -25, 53, 28, -33, + 54, 32, -41, 55, 36, -49, 56, 40, -57, 57, 45, -64, + 51, 3, 56, 51, 3, 52, 51, 3, 46, 51, 4, 39, + 51, 6, 30, 51, 7, 22, 52, 10, 12, 52, 12, 3, + 53, 15, -6, 53, 18, -14, 54, 22, -23, 54, 26, -31, + 55, 30, -39, 56, 34, -48, 57, 38, -55, 58, 43, -63, + 52, 0, 57, 52, 1, 53, 52, 1, 47, 52, 2, 40, + 52, 3, 32, 52, 5, 23, 53, 7, 14, 53, 10, 5, + 54, 13, -4, 54, 16, -13, 55, 20, -22, 55, 23, -30, + 56, 27, -38, 57, 32, -46, 58, 36, -54, 59, 40, -61, + 53, -2, 57, 53, -1, 54, 53, -1, 48, 53, 0, 41, + 53, 1, 33, 54, 3, 24, 54, 5, 15, 54, 7, 6, + 55, 11, -3, 55, 14, -11, 56, 17, -20, 56, 21, -28, + 57, 25, -36, 58, 29, -45, 59, 33, -52, 60, 38, -60, + 54, -5, 58, 54, -4, 55, 54, -3, 49, 55, -2, 43, + 55, -1, 35, 55, 0, 26, 55, 2, 17, 56, 5, 8, + 56, 8, -1, 56, 11, -9, 57, 15, -18, 58, 18, -26, + 58, 22, -34, 59, 26, -43, 60, 31, -50, 61, 35, -58, + 55, -7, 59, 55, -6, 56, 56, -6, 50, 56, -5, 44, + 56, -3, 36, 56, -1, 28, 56, 0, 18, 57, 2, 10, + 57, 5, 1, 58, 9, -8, 58, 12, -16, 59, 16, -25, + 59, 20, -33, 60, 24, -41, 61, 28, -49, 62, 33, -57, + 57, -9, 60, 57, -9, 57, 57, -8, 52, 57, -7, 45, + 57, -5, 37, 57, -4, 29, 57, -1, 20, 58, 0, 11, + 58, 3, 2, 59, 6, -6, 59, 10, -15, 60, 14, -23, + 60, 18, -31, 61, 22, -40, 62, 26, -47, 63, 31, -55, + 58, -11, 61, 58, -11, 58, 58, -10, 53, 58, -9, 46, + 58, -8, 39, 58, -6, 31, 59, -4, 21, 59, -1, 13, + 59, 1, 4, 60, 4, -4, 60, 8, -13, 61, 11, -22, + 61, 15, -30, 62, 19, -38, 63, 24, -46, 64, 28, -54, + 59, -13, 62, 59, -13, 59, 59, -12, 54, 59, -11, 48, + 59, -10, 40, 59, -8, 32, 60, -6, 23, 60, -4, 14, + 60, -1, 5, 61, 2, -3, 61, 5, -12, 62, 9, -20, + 62, 13, -28, 63, 17, -36, 64, 21, -44, 65, 26, -52, + 60, -15, 62, 60, -15, 60, 60, -14, 55, 60, -13, 49, + 60, -12, 41, 61, -10, 33, 61, -8, 25, 61, -6, 16, + 61, -3, 7, 62, 0, -1, 62, 3, -10, 63, 7, -18, + 63, 11, -26, 64, 15, -35, 65, 19, -43, 66, 24, -51, + 61, -17, 63, 61, -17, 61, 61, -16, 56, 61, -15, 50, + 61, -14, 43, 62, -12, 35, 62, -10, 26, 62, -8, 18, + 63, -5, 9, 63, -2, 0, 63, 1, -8, 64, 4, -17, + 65, 8, -25, 65, 13, -33, 66, 17, -41, 67, 21, -49, + 62, -19, 64, 62, -19, 62, 62, -18, 57, 62, -17, 51, + 63, -16, 44, 63, -14, 36, 63, -12, 28, 63, -10, 19, + 64, -7, 10, 64, -5, 2, 65, -1, -7, 65, 2, -15, + 66, 6, -23, 66, 10, -32, 67, 14, -39, 68, 19, -47, + 63, -21, 65, 63, -21, 63, 64, -20, 58, 64, -19, 52, + 64, -18, 45, 64, -16, 38, 64, -14, 29, 64, -12, 21, + 65, -10, 12, 65, -7, 3, 66, -3, -5, 66, 0, -13, + 67, 4, -22, 67, 8, -30, 68, 12, -38, 69, 17, -46, + 65, -23, 66, 65, -23, 64, 65, -22, 59, 65, -21, 53, + 65, -20, 46, 65, -19, 39, 65, -16, 31, 66, -14, 22, + 66, -12, 13, 66, -9, 5, 67, -5, -4, 67, -2, -12, + 68, 2, -20, 68, 6, -28, 69, 10, -36, 70, 14, -44, + 66, -25, 67, 66, -25, 65, 66, -24, 60, 66, -23, 55, + 66, -22, 48, 66, -21, 40, 66, -19, 32, 67, -16, 24, + 67, -14, 15, 67, -11, 7, 68, -8, -2, 68, -4, -10, + 69, 0, -18, 69, 4, -27, 70, 8, -35, 71, 12, -43, + 67, -27, 68, 67, -27, 66, 67, -26, 61, 67, -25, 56, + 67, -24, 49, 67, -23, 42, 68, -21, 33, 68, -18, 25, + 68, -16, 17, 69, -13, 8, 69, -10, 0, 69, -6, -9, + 70, -3, -17, 70, 1, -25, 71, 5, -33, 72, 10, -41, + 68, -29, 68, 68, -29, 66, 68, -28, 63, 68, -27, 57, + 68, -26, 50, 69, -25, 43, 69, -23, 35, 69, -20, 27, + 69, -18, 18, 70, -15, 10, 70, -12, 1, 71, -9, -7, + 71, -5, -15, 72, -1, -23, 72, 3, -31, 73, 8, -39, + 69, -31, 69, 69, -31, 67, 69, -30, 64, 69, -29, 58, + 70, -28, 51, 70, -27, 44, 70, -25, 36, 70, -22, 28, + 70, -20, 20, 71, -17, 11, 71, -14, 3, 72, -11, -5, + 72, -7, -13, 73, -3, -22, 73, 1, -30, 74, 6, -38, + 70, -33, 70, 70, -33, 68, 71, -32, 65, 71, -31, 59, + 71, -30, 53, 71, -28, 46, 71, -26, 38, 71, -24, 30, + 72, -22, 21, 72, -19, 13, 72, -16, 4, 73, -13, -4, + 73, -9, -12, 74, -5, -20, 74, -1, -28, 75, 3, -36, + 72, -35, 71, 72, -35, 69, 72, -34, 66, 72, -33, 60, + 72, -32, 54, 72, -30, 47, 72, -28, 39, 72, -26, 31, + 73, -24, 23, 73, -21, 15, 73, -18, 6, 74, -15, -2, + 74, -11, -10, 75, -7, -18, 75, -3, -26, 76, 1, -34, + 73, -37, 72, 73, -36, 70, 73, -36, 67, 73, -35, 61, + 73, -34, 55, 73, -32, 48, 73, -30, 41, 74, -28, 33, + 74, -26, 24, 74, -23, 16, 75, -20, 8, 75, -17, 0, + 75, -13, -8, 76, -9, -17, 76, -5, -25, 77, -1, -33, + 74, -38, 73, 74, -38, 71, 74, -38, 68, 74, -37, 62, + 74, -36, 56, 74, -34, 50, 75, -32, 42, 75, -30, 34, + 75, -28, 26, 75, -25, 18, 76, -22, 9, 76, -19, 1, + 77, -15, -7, 77, -12, -15, 78, -7, -23, 78, -3, -31, + 75, -40, 74, 75, -40, 72, 75, -39, 69, 75, -38, 64, + 75, -37, 58, 76, -36, 51, 76, -34, 43, 76, -32, 36, + 76, -30, 27, 76, -27, 19, 77, -24, 11, 77, -21, 3, + 78, -17, -5, 78, -14, -14, 79, -9, -21, 79, -5, -30, + 76, -42, 75, 76, -42, 73, 76, -41, 70, 76, -40, 65, + 77, -39, 59, 77, -38, 52, 77, -36, 45, 77, -34, 37, + 77, -32, 29, 78, -29, 21, 78, -26, 12, 78, -23, 4, + 79, -19, -4, 79, -16, -12, 80, -12, -20, 80, -7, -28, + 78, -44, 76, 78, -43, 74, 78, -43, 71, 78, -42, 66, + 78, -41, 60, 78, -40, 54, 78, -38, 46, 78, -36, 39, + 78, -33, 31, 79, -31, 23, 79, -28, 14, 79, -25, 6, + 80, -21, -2, 80, -18, -10, 81, -14, -18, 81, -9, -26, + 79, -46, 77, 79, -46, 75, 79, -45, 72, 79, -44, 67, + 79, -43, 62, 79, -42, 55, 79, -40, 48, 80, -38, 41, + 80, -36, 32, 80, -33, 25, 81, -30, 16, 81, -27, 8, + 81, -24, 0, 82, -20, -8, 82, -16, -16, 83, -12, -24, + 80, -48, 78, 80, -47, 76, 80, -47, 73, 80, -46, 69, + 80, -45, 63, 80, -43, 57, 81, -42, 49, 81, -40, 42, + 81, -38, 34, 81, -35, 26, 82, -32, 18, 82, -29, 10, + 82, -26, 1, 83, -22, -7, 83, -18, -14, 84, -14, -22, + 81, -49, 79, 81, -49, 77, 81, -48, 74, 81, -48, 70, + 82, -47, 64, 82, -45, 58, 82, -43, 51, 82, -42, 43, + 82, -39, 35, 82, -37, 28, 83, -34, 19, 83, -31, 11, + 83, -28, 3, 84, -24, -5, 84, -20, -13, 85, -16, -21, + 83, -51, 80, 83, -51, 78, 83, -50, 75, 83, -49, 71, + 83, -48, 65, 83, -47, 59, 83, -45, 52, 83, -43, 45, + 83, -41, 37, 84, -39, 29, 84, -36, 21, 84, -33, 13, + 85, -30, 5, 85, -26, -3, 85, -22, -11, 86, -18, -19, + 84, -52, 81, 84, -52, 79, 84, -52, 76, 84, -51, 72, + 84, -50, 66, 84, -49, 60, 84, -47, 53, 84, -45, 46, + 85, -43, 38, 85, -40, 31, 85, -38, 22, 85, -35, 14, + 86, -32, 6, 86, -28, -2, 87, -24, -10, 87, -20, -18, + 85, -54, 82, 85, -54, 80, 85, -53, 77, 85, -52, 73, + 85, -51, 68, 85, -50, 62, 85, -49, 55, 85, -47, 48, + 86, -45, 40, 86, -42, 32, 86, -39, 24, 87, -37, 16, + 87, -33, 8, 87, -30, 0, 88, -26, -8, 88, -22, -16, + 86, -56, 83, 86, -55, 81, 86, -55, 78, 86, -54, 74, + 86, -53, 69, 86, -52, 63, 86, -50, 56, 87, -48, 49, + 87, -46, 41, 87, -44, 34, 87, -41, 25, 88, -38, 18, + 88, -35, 9, 88, -32, 2, 89, -28, -6, 89, -24, -14, + 87, -57, 83, 87, -57, 82, 87, -56, 79, 87, -56, 75, + 87, -55, 70, 87, -53, 64, 88, -52, 57, 88, -50, 50, + 88, -48, 43, 88, -46, 35, 89, -43, 27, 89, -40, 19, + 89, -37, 11, 90, -34, 3, 90, -30, -5, 90, -26, -13, + 88, -59, 84, 88, -58, 83, 88, -58, 80, 88, -57, 76, + 89, -56, 71, 89, -55, 65, 89, -53, 59, 89, -52, 52, + 89, -50, 44, 89, -47, 37, 90, -45, 29, 90, -42, 21, + 90, -39, 13, 91, -35, 5, 91, -32, -3, 91, -28, -11, + 90, -60, 85, 90, -60, 84, 90, -59, 81, 90, -59, 77, + 90, -58, 72, 90, -57, 67, 90, -55, 60, 90, -53, 53, + 90, -51, 46, 91, -49, 38, 91, -46, 30, 91, -44, 22, + 91, -41, 14, 92, -37, 6, 92, -34, -1, 93, -30, -9, + 91, -62, 86, 91, -61, 85, 91, -61, 82, 91, -60, 79, + 91, -59, 74, 91, -58, 68, 91, -57, 61, 91, -55, 55, + 91, -53, 47, 92, -51, 40, 92, -48, 32, 92, -45, 24, + 93, -42, 16, 93, -39, 8, 93, -36, 0, 94, -32, -8, + 32, 55, 44, 32, 56, 36, 32, 56, 27, 32, 57, 17, + 33, 58, 6, 33, 59, -3, 34, 61, -13, 35, 62, -22, + 35, 65, -32, 36, 67, -40, 37, 70, -49, 39, 72, -56, + 40, 75, -64, 41, 78, -71, 42, 82, -78, 44, 85, -85, + 32, 55, 44, 32, 55, 37, 32, 56, 27, 33, 56, 17, + 33, 57, 6, 33, 59, -3, 34, 60, -13, 35, 62, -22, + 36, 64, -31, 37, 67, -40, 38, 69, -48, 39, 72, -56, + 40, 75, -64, 41, 78, -71, 43, 81, -78, 44, 84, -85, + 32, 54, 44, 32, 55, 37, 33, 55, 27, 33, 56, 17, + 33, 57, 6, 34, 58, -2, 34, 60, -13, 35, 61, -22, + 36, 64, -31, 37, 66, -40, 38, 69, -48, 39, 72, -56, + 40, 75, -63, 41, 78, -71, 43, 81, -78, 44, 84, -85, + 33, 54, 44, 33, 54, 37, 33, 54, 27, 33, 55, 17, + 33, 56, 7, 34, 57, -2, 34, 59, -12, 35, 61, -21, + 36, 63, -31, 37, 66, -39, 38, 68, -48, 39, 71, -55, + 40, 74, -63, 42, 77, -70, 43, 80, -78, 44, 83, -84, + 33, 53, 44, 33, 53, 37, 33, 54, 28, 33, 55, 18, + 34, 56, 7, 34, 57, -2, 35, 58, -12, 35, 60, -21, + 36, 63, -31, 37, 65, -39, 38, 68, -48, 39, 71, -55, + 40, 74, -63, 42, 77, -70, 43, 80, -77, 44, 83, -84, + 33, 52, 45, 33, 53, 37, 33, 53, 28, 34, 54, 18, + 34, 55, 7, 34, 56, -1, 35, 58, -12, 36, 60, -21, + 36, 62, -30, 37, 64, -39, 38, 67, -47, 39, 70, -55, + 41, 73, -63, 42, 76, -70, 43, 79, -77, 45, 82, -84, + 33, 51, 45, 33, 52, 38, 34, 52, 28, 34, 53, 18, + 34, 54, 8, 35, 55, -1, 35, 57, -11, 36, 59, -20, + 37, 61, -30, 38, 64, -38, 39, 66, -47, 40, 69, -54, + 41, 72, -62, 42, 75, -69, 43, 79, -77, 45, 82, -84, + 34, 50, 45, 34, 51, 38, 34, 51, 29, 34, 52, 19, + 35, 53, 8, 35, 54, -1, 36, 56, -11, 36, 58, -20, + 37, 60, -29, 38, 63, -38, 39, 65, -46, 40, 68, -54, + 41, 71, -62, 42, 75, -69, 44, 78, -76, 45, 81, -83, + 34, 49, 45, 34, 49, 38, 34, 50, 29, 35, 51, 19, + 35, 52, 9, 35, 53, 0, 36, 55, -10, 37, 57, -19, + 37, 59, -29, 38, 62, -37, 39, 64, -46, 40, 67, -53, + 41, 71, -61, 43, 74, -69, 44, 77, -76, 45, 80, -83, + 35, 48, 46, 35, 48, 39, 35, 49, 30, 35, 49, 20, + 35, 50, 10, 36, 52, 0, 36, 53, -9, 37, 55, -19, + 38, 58, -28, 39, 60, -36, 40, 63, -45, 41, 66, -53, + 42, 70, -61, 43, 73, -68, 44, 76, -75, 46, 79, -82, + 35, 46, 46, 35, 47, 39, 35, 47, 30, 36, 48, 21, + 36, 49, 10, 36, 50, 1, 37, 52, -9, 38, 54, -18, + 38, 57, -27, 39, 59, -36, 40, 62, -44, 41, 65, -52, + 42, 68, -60, 43, 72, -67, 45, 75, -75, 46, 78, -82, + 36, 44, 46, 36, 45, 40, 36, 45, 31, 36, 46, 21, + 37, 47, 11, 37, 48, 2, 38, 50, -8, 38, 52, -17, + 39, 55, -26, 40, 58, -35, 41, 61, -43, 42, 64, -51, + 43, 67, -59, 44, 70, -67, 45, 74, -74, 47, 77, -81, + 36, 43, 47, 37, 43, 41, 37, 44, 32, 37, 44, 22, + 37, 45, 12, 38, 47, 2, 38, 49, -7, 39, 51, -16, + 39, 53, -25, 40, 56, -34, 41, 59, -43, 42, 62, -51, + 43, 65, -58, 44, 69, -66, 46, 72, -73, 47, 76, -80, + 37, 41, 47, 37, 41, 41, 37, 42, 33, 38, 43, 23, + 38, 44, 13, 38, 45, 3, 39, 47, -6, 39, 49, -15, + 40, 52, -24, 41, 54, -33, 42, 58, -42, 43, 61, -50, + 44, 64, -57, 45, 67, -65, 46, 71, -73, 47, 74, -79, + 38, 39, 47, 38, 39, 42, 38, 40, 33, 38, 41, 24, + 39, 42, 14, 39, 43, 4, 39, 45, -5, 40, 47, -14, + 41, 50, -24, 41, 53, -32, 42, 56, -41, 43, 59, -49, + 44, 62, -57, 45, 66, -64, 47, 70, -72, 48, 73, -79, + 39, 37, 48, 39, 38, 42, 39, 38, 34, 39, 39, 25, + 39, 40, 15, 40, 41, 5, 40, 43, -4, 41, 46, -13, + 41, 48, -22, 42, 51, -31, 43, 54, -40, 44, 57, -48, + 45, 61, -56, 46, 64, -63, 47, 68, -71, 48, 72, -78, + 39, 35, 48, 39, 36, 43, 40, 36, 35, 40, 37, 26, + 40, 38, 16, 40, 40, 6, 41, 41, -3, 41, 44, -12, + 42, 46, -21, 43, 49, -30, 44, 52, -39, 44, 56, -47, + 45, 59, -55, 47, 63, -62, 48, 66, -70, 49, 70, -77, + 40, 33, 49, 40, 34, 44, 40, 34, 36, 40, 35, 26, + 41, 36, 17, 41, 38, 7, 42, 39, -2, 42, 42, -11, + 43, 45, -20, 43, 47, -29, 44, 51, -38, 45, 54, -46, + 46, 57, -54, 47, 61, -61, 48, 65, -69, 50, 68, -76, + 41, 31, 50, 41, 31, 44, 41, 32, 37, 41, 33, 27, + 42, 34, 18, 42, 35, 9, 42, 37, -1, 43, 40, -10, + 44, 43, -19, 44, 45, -28, 45, 49, -37, 46, 52, -45, + 47, 56, -53, 48, 59, -60, 49, 63, -68, 50, 67, -75, + 42, 29, 50, 42, 29, 45, 42, 30, 38, 42, 31, 28, + 42, 32, 19, 43, 33, 10, 43, 35, 0, 44, 38, -9, + 44, 40, -18, 45, 43, -27, 46, 47, -35, 47, 50, -44, + 48, 54, -51, 49, 57, -59, 50, 61, -67, 51, 65, -74, + 43, 27, 51, 43, 27, 46, 43, 28, 39, 43, 28, 29, + 43, 30, 20, 44, 31, 11, 44, 33, 1, 45, 36, -7, + 45, 38, -17, 46, 41, -25, 47, 45, -34, 47, 48, -42, + 48, 52, -50, 49, 55, -58, 50, 59, -66, 51, 63, -73, + 44, 25, 51, 44, 25, 46, 44, 25, 40, 44, 26, 31, + 44, 27, 21, 44, 29, 12, 45, 31, 3, 45, 33, -6, + 46, 36, -15, 47, 39, -24, 47, 43, -33, 48, 46, -41, + 49, 50, -49, 50, 53, -57, 51, 57, -65, 52, 61, -72, + 45, 22, 52, 45, 23, 47, 45, 23, 40, 45, 24, 32, + 45, 25, 23, 45, 27, 13, 46, 29, 4, 46, 31, -5, + 47, 34, -14, 47, 37, -23, 48, 40, -32, 49, 44, -40, + 50, 48, -48, 51, 51, -56, 52, 55, -63, 53, 59, -71, + 45, 20, 53, 46, 20, 48, 46, 21, 41, 46, 22, 33, + 46, 23, 24, 46, 24, 15, 47, 27, 5, 47, 29, -3, + 48, 32, -13, 48, 35, -21, 49, 38, -30, 50, 42, -39, + 51, 45, -46, 52, 49, -54, 53, 53, -62, 54, 57, -69, + 46, 18, 53, 47, 18, 49, 47, 19, 42, 47, 19, 34, + 47, 21, 25, 47, 22, 16, 48, 24, 6, 48, 27, -2, + 49, 30, -11, 49, 33, -20, 50, 36, -29, 51, 39, -37, + 51, 43, -45, 52, 47, -53, 53, 51, -61, 54, 55, -68, + 47, 16, 54, 48, 16, 49, 48, 16, 43, 48, 17, 35, + 48, 18, 26, 48, 20, 17, 49, 22, 8, 49, 24, -1, + 50, 27, -10, 50, 30, -19, 51, 34, -28, 52, 37, -36, + 52, 41, -44, 53, 45, -52, 54, 49, -60, 55, 53, -67, + 48, 13, 55, 49, 13, 50, 49, 14, 44, 49, 15, 36, + 49, 16, 28, 49, 18, 19, 50, 20, 9, 50, 22, 0, + 50, 25, -9, 51, 28, -17, 52, 32, -26, 52, 35, -34, + 53, 39, -42, 54, 43, -51, 55, 47, -58, 56, 51, -66, + 50, 11, 55, 50, 11, 51, 50, 12, 45, 50, 13, 37, + 50, 14, 29, 50, 15, 20, 51, 17, 11, 51, 20, 2, + 51, 23, -7, 52, 26, -16, 53, 29, -25, 53, 33, -33, + 54, 37, -41, 55, 41, -49, 56, 45, -57, 57, 49, -64, + 51, 9, 56, 51, 9, 52, 51, 9, 46, 51, 10, 39, + 51, 12, 30, 51, 13, 21, 52, 15, 12, 52, 18, 3, + 52, 20, -6, 53, 23, -14, 54, 27, -23, 54, 30, -32, + 55, 34, -40, 56, 38, -48, 57, 42, -56, 58, 47, -63, + 52, 6, 57, 52, 7, 53, 52, 7, 47, 52, 8, 40, + 52, 9, 32, 52, 11, 23, 53, 13, 13, 53, 15, 5, + 53, 18, -4, 54, 21, -13, 55, 25, -22, 55, 28, -30, + 56, 32, -38, 57, 36, -46, 58, 40, -54, 59, 44, -62, + 53, 4, 58, 53, 4, 54, 53, 5, 48, 53, 6, 41, + 53, 7, 33, 53, 8, 24, 54, 11, 15, 54, 13, 6, + 54, 16, -3, 55, 19, -11, 56, 22, -20, 56, 26, -29, + 57, 30, -37, 58, 34, -45, 58, 38, -53, 59, 42, -60, + 54, 2, 58, 54, 2, 55, 54, 3, 49, 54, 3, 42, + 54, 5, 34, 54, 6, 26, 55, 8, 16, 55, 11, 8, + 55, 14, -1, 56, 17, -10, 57, 20, -19, 57, 24, -27, + 58, 27, -35, 59, 32, -43, 59, 36, -51, 60, 40, -59, + 55, -1, 59, 55, 0, 56, 55, 0, 50, 55, 1, 44, + 56, 2, 36, 56, 3, 27, 56, 5, 18, 56, 8, 9, + 57, 11, 0, 57, 14, -8, 58, 17, -17, 58, 21, -25, + 59, 25, -33, 60, 29, -42, 61, 33, -49, 61, 37, -57, + 56, -3, 60, 56, -3, 57, 56, -2, 51, 56, -1, 45, + 57, 0, 37, 57, 1, 29, 57, 3, 20, 57, 6, 11, + 58, 8, 2, 58, 11, -6, 59, 15, -15, 59, 18, -24, + 60, 22, -32, 61, 26, -40, 62, 30, -48, 62, 35, -56, + 57, -5, 61, 57, -5, 58, 57, -4, 53, 58, -3, 46, + 58, -2, 38, 58, 0, 30, 58, 1, 21, 58, 3, 13, + 59, 6, 3, 59, 9, -5, 60, 13, -14, 60, 16, -22, + 61, 20, -30, 62, 24, -38, 62, 28, -46, 63, 33, -54, + 58, -7, 62, 58, -7, 59, 59, -6, 54, 59, -5, 47, + 59, -4, 40, 59, -2, 32, 59, 0, 23, 60, 1, 14, + 60, 4, 5, 60, 7, -3, 61, 10, -12, 61, 14, -20, + 62, 18, -29, 63, 22, -37, 63, 26, -45, 64, 30, -53, + 60, -10, 62, 60, -9, 60, 60, -9, 55, 60, -8, 49, + 60, -6, 41, 60, -5, 33, 60, -3, 24, 61, 0, 16, + 61, 2, 6, 61, 5, -2, 62, 8, -11, 62, 12, -19, + 63, 15, -27, 64, 19, -35, 64, 24, -43, 65, 28, -51, + 61, -12, 63, 61, -11, 61, 61, -11, 56, 61, -10, 50, + 61, -8, 42, 61, -7, 34, 61, -5, 26, 62, -3, 17, + 62, 0, 8, 63, 2, 0, 63, 6, -9, 64, 9, -17, + 64, 13, -25, 65, 17, -34, 65, 21, -42, 66, 26, -50, + 62, -14, 64, 62, -13, 62, 62, -13, 57, 62, -12, 51, + 62, -11, 43, 62, -9, 36, 63, -7, 27, 63, -5, 19, + 63, -2, 10, 64, 0, 1, 64, 4, -7, 65, 7, -16, + 65, 11, -24, 66, 15, -32, 66, 19, -40, 67, 23, -48, + 63, -16, 65, 63, -15, 63, 63, -15, 58, 63, -14, 52, + 63, -13, 45, 63, -11, 37, 64, -9, 28, 64, -7, 20, + 64, -4, 11, 65, -2, 3, 65, 1, -6, 66, 5, -14, + 66, 9, -22, 67, 13, -31, 67, 17, -39, 68, 21, -46, + 64, -18, 66, 64, -18, 63, 64, -17, 59, 64, -16, 53, + 64, -15, 46, 65, -13, 39, 65, -11, 30, 65, -9, 22, + 65, -7, 13, 66, -4, 4, 66, -1, -4, 67, 3, -13, + 67, 6, -21, 68, 10, -29, 68, 14, -37, 69, 19, -45, + 65, -20, 67, 65, -20, 64, 65, -19, 60, 65, -18, 54, + 66, -17, 47, 66, -15, 40, 66, -13, 31, 66, -11, 23, + 67, -9, 14, 67, -6, 6, 67, -3, -3, 68, 0, -11, + 68, 4, -19, 69, 8, -27, 70, 12, -35, 70, 17, -43, + 66, -22, 67, 66, -22, 65, 66, -21, 61, 67, -20, 55, + 67, -19, 49, 67, -17, 41, 67, -15, 33, 67, -13, 25, + 68, -11, 16, 68, -8, 8, 68, -5, -1, 69, -2, -9, + 69, 2, -17, 70, 6, -26, 71, 10, -34, 71, 14, -42, + 68, -24, 68, 68, -24, 66, 68, -23, 62, 68, -22, 56, + 68, -21, 50, 68, -19, 43, 68, -17, 34, 68, -15, 26, + 69, -13, 17, 69, -10, 9, 70, -7, 0, 70, -4, -8, + 70, 0, -16, 71, 4, -24, 72, 8, -32, 72, 12, -40, + 69, -26, 69, 69, -26, 67, 69, -25, 63, 69, -24, 58, + 69, -23, 51, 69, -21, 44, 69, -19, 36, 70, -17, 28, + 70, -15, 19, 70, -12, 11, 71, -9, 2, 71, -6, -6, + 72, -2, -14, 72, 1, -23, 73, 5, -31, 73, 10, -39, + 70, -28, 70, 70, -27, 68, 70, -27, 64, 70, -26, 59, + 70, -25, 52, 70, -23, 45, 70, -21, 37, 71, -19, 29, + 71, -17, 20, 71, -14, 12, 72, -11, 4, 72, -8, -4, + 73, -5, -13, 73, -1, -21, 74, 3, -29, 74, 8, -37, + 71, -30, 71, 71, -29, 69, 71, -29, 65, 71, -28, 60, + 71, -27, 53, 71, -25, 47, 72, -23, 39, 72, -21, 31, + 72, -19, 22, 72, -16, 14, 73, -13, 5, 73, -10, -3, + 74, -7, -11, 74, -3, -19, 75, 1, -27, 75, 5, -35, + 72, -32, 72, 72, -31, 70, 72, -31, 66, 72, -30, 61, + 72, -29, 55, 73, -27, 48, 73, -25, 40, 73, -23, 32, + 73, -21, 24, 74, -18, 15, 74, -15, 7, 74, -12, -1, + 75, -9, -9, 75, -5, -18, 76, -1, -26, 76, 3, -34, + 73, -33, 73, 73, -33, 71, 73, -33, 67, 73, -32, 62, + 74, -31, 56, 74, -29, 49, 74, -27, 41, 74, -25, 34, + 74, -23, 25, 75, -20, 17, 75, -17, 8, 75, -14, 0, + 76, -11, -8, 76, -7, -16, 77, -3, -24, 77, 1, -32, + 74, -35, 74, 75, -35, 72, 75, -34, 69, 75, -34, 63, + 75, -32, 57, 75, -31, 50, 75, -29, 43, 75, -27, 35, + 76, -25, 27, 76, -22, 19, 76, -19, 10, 77, -16, 2, + 77, -13, -6, 77, -9, -14, 78, -5, -22, 79, -1, -30, + 76, -37, 75, 76, -37, 73, 76, -36, 70, 76, -35, 64, + 76, -34, 58, 76, -33, 52, 76, -31, 44, 76, -29, 37, + 77, -27, 28, 77, -24, 20, 77, -21, 12, 78, -18, 4, + 78, -15, -4, 79, -11, -13, 79, -7, -21, 80, -3, -29, + 77, -39, 75, 77, -39, 74, 77, -38, 71, 77, -37, 65, + 77, -36, 59, 77, -35, 53, 77, -33, 46, 78, -31, 38, + 78, -29, 30, 78, -26, 22, 78, -23, 13, 79, -20, 5, + 79, -17, -3, 80, -13, -11, 80, -9, -19, 81, -5, -27, + 78, -41, 76, 78, -40, 75, 78, -40, 72, 78, -39, 67, + 78, -38, 61, 78, -37, 54, 78, -35, 47, 79, -33, 39, + 79, -31, 31, 79, -28, 23, 80, -25, 15, 80, -22, 7, + 80, -19, -1, 81, -15, -10, 81, -11, -17, 82, -7, -26, + 79, -43, 78, 79, -43, 76, 79, -42, 73, 80, -41, 68, + 80, -40, 62, 80, -39, 56, 80, -37, 49, 80, -35, 41, + 80, -33, 33, 81, -31, 25, 81, -28, 17, 81, -25, 9, + 82, -22, 0, 82, -18, -8, 83, -14, -15, 83, -10, -23, + 81, -45, 78, 81, -44, 77, 81, -44, 74, 81, -43, 69, + 81, -42, 63, 81, -41, 57, 81, -39, 50, 81, -37, 43, + 82, -35, 35, 82, -32, 27, 82, -30, 18, 82, -27, 10, + 83, -24, 2, 83, -20, -6, 84, -16, -14, 84, -12, -22, + 82, -46, 79, 82, -46, 78, 82, -45, 75, 82, -45, 70, + 82, -44, 65, 82, -42, 58, 82, -41, 51, 82, -39, 44, + 83, -37, 36, 83, -34, 28, 83, -32, 20, 84, -29, 12, + 84, -26, 4, 84, -22, -4, 85, -18, -12, 85, -14, -20, + 83, -48, 80, 83, -48, 78, 83, -47, 76, 83, -46, 71, + 83, -45, 66, 83, -44, 60, 83, -42, 53, 84, -41, 45, + 84, -38, 37, 84, -36, 30, 84, -33, 21, 85, -31, 14, + 85, -27, 5, 85, -24, -3, 86, -20, -11, 86, -16, -19, + 84, -50, 81, 84, -49, 79, 84, -49, 77, 84, -48, 72, + 84, -47, 67, 84, -46, 61, 85, -44, 54, 85, -42, 47, + 85, -40, 39, 85, -38, 31, 85, -35, 23, 86, -32, 15, + 86, -29, 7, 87, -26, -1, 87, -22, -9, 87, -18, -17, + 85, -51, 82, 85, -51, 80, 85, -50, 78, 85, -50, 74, + 85, -49, 68, 86, -48, 62, 86, -46, 55, 86, -44, 48, + 86, -42, 40, 86, -40, 33, 87, -37, 24, 87, -34, 17, + 87, -31, 8, 88, -28, 1, 88, -24, -7, 89, -20, -15, + 86, -53, 83, 86, -53, 81, 86, -52, 79, 87, -51, 75, + 87, -50, 69, 87, -49, 63, 87, -48, 57, 87, -46, 50, + 87, -44, 42, 87, -42, 34, 88, -39, 26, 88, -36, 18, + 88, -33, 10, 89, -30, 2, 89, -26, -6, 90, -22, -14, + 88, -54, 84, 88, -54, 82, 88, -54, 80, 88, -53, 76, + 88, -52, 71, 88, -51, 65, 88, -49, 58, 88, -48, 51, + 88, -45, 43, 89, -43, 36, 89, -41, 28, 89, -38, 20, + 89, -35, 12, 90, -31, 4, 90, -28, -4, 91, -24, -12, + 89, -56, 85, 89, -56, 83, 89, -55, 81, 89, -55, 77, + 89, -54, 72, 89, -53, 66, 89, -51, 59, 89, -49, 52, + 90, -47, 45, 90, -45, 37, 90, -42, 29, 90, -40, 21, + 91, -37, 13, 91, -33, 5, 91, -30, -2, 92, -26, -10, + 90, -58, 86, 90, -57, 84, 90, -57, 82, 90, -56, 78, + 90, -55, 73, 90, -54, 67, 90, -53, 60, 90, -51, 54, + 91, -49, 46, 91, -47, 39, 91, -44, 31, 91, -41, 23, + 92, -39, 15, 92, -35, 7, 92, -32, -1, 93, -28, -9, + 91, -59, 87, 91, -59, 85, 91, -58, 83, 91, -58, 79, + 91, -57, 74, 91, -56, 68, 91, -54, 62, 92, -53, 55, + 92, -50, 48, 92, -48, 40, 92, -46, 32, 93, -43, 24, + 93, -40, 16, 93, -37, 9, 94, -34, 0, 94, -30, -7, + 34, 58, 46, 34, 58, 39, 34, 58, 29, 34, 59, 20, + 35, 60, 9, 35, 61, 0, 36, 63, -10, 36, 64, -19, + 37, 66, -29, 38, 69, -37, 39, 71, -46, 40, 74, -54, + 41, 77, -61, 43, 79, -69, 44, 82, -76, 45, 85, -83, + 34, 57, 46, 34, 58, 39, 34, 58, 30, 35, 59, 20, + 35, 60, 9, 35, 61, 0, 36, 62, -10, 37, 64, -19, + 37, 66, -28, 38, 68, -37, 39, 71, -46, 40, 73, -53, + 41, 76, -61, 43, 79, -69, 44, 82, -76, 45, 85, -83, + 34, 57, 46, 34, 57, 39, 35, 57, 30, 35, 58, 20, + 35, 59, 9, 36, 60, 0, 36, 62, -10, 37, 63, -19, + 38, 65, -28, 38, 68, -37, 39, 70, -45, 40, 73, -53, + 42, 76, -61, 43, 79, -68, 44, 82, -76, 45, 85, -82, + 35, 56, 47, 35, 56, 39, 35, 57, 30, 35, 58, 20, + 35, 58, 10, 36, 60, 0, 36, 61, -9, 37, 63, -19, + 38, 65, -28, 39, 67, -37, 40, 70, -45, 41, 72, -53, + 42, 75, -61, 43, 78, -68, 44, 81, -76, 46, 84, -82, + 35, 56, 47, 35, 56, 40, 35, 56, 30, 35, 57, 20, + 36, 58, 10, 36, 59, 0, 37, 61, -9, 37, 62, -18, + 38, 64, -28, 39, 67, -36, 40, 69, -45, 41, 72, -53, + 42, 75, -60, 43, 78, -68, 44, 81, -75, 46, 84, -82, + 35, 55, 47, 35, 55, 40, 35, 56, 31, 35, 56, 21, + 36, 57, 10, 36, 58, 1, 37, 60, -9, 37, 62, -18, + 38, 64, -27, 39, 66, -36, 40, 69, -45, 41, 71, -52, + 42, 74, -60, 43, 77, -68, 45, 80, -75, 46, 83, -82, + 35, 54, 47, 35, 54, 40, 36, 55, 31, 36, 55, 21, + 36, 56, 11, 36, 58, 1, 37, 59, -8, 38, 61, -17, + 38, 63, -27, 39, 65, -35, 40, 68, -44, 41, 71, -52, + 42, 74, -60, 44, 77, -67, 45, 80, -75, 46, 83, -81, + 36, 53, 47, 36, 53, 40, 36, 54, 31, 36, 54, 22, + 36, 55, 11, 37, 57, 2, 37, 58, -8, 38, 60, -17, + 39, 62, -26, 40, 64, -35, 40, 67, -44, 41, 70, -51, + 43, 73, -59, 44, 76, -67, 45, 79, -74, 46, 82, -81, + 36, 52, 47, 36, 52, 41, 36, 53, 32, 36, 53, 22, + 37, 54, 12, 37, 55, 2, 38, 57, -7, 38, 59, -16, + 39, 61, -26, 40, 63, -34, 41, 66, -43, 42, 69, -51, + 43, 72, -59, 44, 75, -66, 45, 78, -74, 47, 81, -81, + 36, 51, 48, 37, 51, 41, 37, 51, 32, 37, 52, 23, + 37, 53, 12, 38, 54, 3, 38, 56, -7, 39, 58, -16, + 39, 60, -25, 40, 62, -34, 41, 65, -43, 42, 68, -50, + 43, 71, -58, 44, 74, -66, 46, 77, -73, 47, 80, -80, + 37, 49, 48, 37, 50, 42, 37, 50, 33, 37, 51, 23, + 38, 52, 13, 38, 53, 3, 39, 55, -6, 39, 56, -15, + 40, 59, -25, 41, 61, -33, 42, 64, -42, 43, 67, -50, + 44, 70, -58, 45, 73, -65, 46, 76, -73, 47, 80, -80, + 38, 48, 48, 38, 48, 42, 38, 48, 34, 38, 49, 24, + 38, 50, 14, 39, 51, 4, 39, 53, -5, 40, 55, -14, + 40, 57, -24, 41, 60, -32, 42, 62, -41, 43, 65, -49, + 44, 68, -57, 45, 72, -64, 46, 75, -72, 48, 78, -79, + 38, 46, 49, 38, 46, 43, 38, 47, 34, 39, 47, 25, + 39, 48, 15, 39, 50, 5, 40, 51, -4, 40, 53, -13, + 41, 56, -23, 42, 58, -31, 43, 61, -40, 44, 64, -48, + 45, 67, -56, 46, 70, -64, 47, 74, -71, 48, 77, -78, + 39, 44, 49, 39, 45, 43, 39, 45, 35, 39, 46, 25, + 39, 47, 15, 40, 48, 6, 40, 50, -3, 41, 52, -13, + 42, 54, -22, 42, 57, -31, 43, 60, -39, 44, 62, -47, + 45, 66, -55, 46, 69, -63, 47, 72, -71, 49, 76, -77, + 39, 43, 49, 40, 43, 44, 40, 43, 36, 40, 44, 26, + 40, 45, 16, 40, 46, 7, 41, 48, -2, 41, 50, -12, + 42, 53, -21, 43, 55, -30, 44, 58, -39, 45, 61, -47, + 46, 64, -54, 47, 67, -62, 48, 71, -70, 49, 74, -77, + 40, 41, 50, 40, 41, 44, 40, 41, 36, 41, 42, 27, + 41, 43, 17, 41, 45, 8, 42, 46, -2, 42, 48, -11, + 43, 51, -20, 43, 53, -29, 44, 56, -38, 45, 59, -46, + 46, 63, -54, 47, 66, -61, 48, 70, -69, 50, 73, -76, + 41, 39, 50, 41, 39, 45, 41, 40, 37, 41, 40, 28, + 41, 41, 18, 42, 43, 9, 42, 44, -1, 43, 47, -10, + 43, 49, -19, 44, 52, -28, 45, 55, -37, 46, 58, -45, + 47, 61, -53, 48, 64, -60, 49, 68, -68, 50, 71, -75, + 42, 37, 51, 42, 37, 45, 42, 38, 38, 42, 38, 29, + 42, 39, 19, 43, 41, 10, 43, 43, 0, 43, 45, -9, + 44, 47, -18, 45, 50, -27, 46, 53, -36, 46, 56, -44, + 47, 59, -52, 48, 63, -59, 49, 66, -67, 51, 70, -74, + 42, 35, 51, 42, 35, 46, 43, 36, 39, 43, 36, 29, + 43, 37, 20, 43, 39, 11, 44, 41, 1, 44, 43, -8, + 45, 45, -17, 46, 48, -26, 46, 51, -35, 47, 54, -43, + 48, 58, -51, 49, 61, -58, 50, 65, -66, 51, 68, -73, + 43, 33, 52, 43, 33, 47, 43, 34, 40, 44, 34, 30, + 44, 35, 21, 44, 37, 12, 45, 39, 2, 45, 41, -6, + 46, 43, -16, 46, 46, -24, 47, 49, -33, 48, 52, -42, + 49, 56, -49, 50, 59, -57, 51, 63, -65, 52, 67, -72, + 44, 31, 52, 44, 31, 47, 44, 31, 40, 44, 32, 31, + 45, 33, 22, 45, 35, 13, 45, 36, 3, 46, 39, -5, + 46, 41, -15, 47, 44, -23, 48, 47, -32, 49, 50, -40, + 49, 54, -48, 50, 57, -56, 51, 61, -64, 53, 65, -71, + 45, 28, 53, 45, 29, 48, 45, 29, 41, 45, 30, 32, + 45, 31, 23, 46, 33, 14, 46, 34, 5, 47, 37, -4, + 47, 39, -13, 48, 42, -22, 49, 45, -31, 49, 48, -39, + 50, 52, -47, 51, 55, -55, 52, 59, -63, 53, 63, -70, + 46, 26, 54, 46, 27, 49, 46, 27, 42, 46, 28, 33, + 46, 29, 25, 47, 30, 15, 47, 32, 6, 47, 34, -3, + 48, 37, -12, 49, 40, -21, 49, 43, -30, 50, 46, -38, + 51, 50, -46, 52, 53, -54, 53, 57, -62, 54, 61, -69, + 47, 24, 54, 47, 24, 49, 47, 25, 43, 47, 26, 35, + 47, 27, 26, 48, 28, 17, 48, 30, 7, 48, 32, -1, + 49, 35, -11, 49, 38, -20, 50, 41, -29, 51, 44, -37, + 52, 48, -45, 53, 51, -53, 54, 55, -60, 55, 59, -68, + 48, 22, 55, 48, 22, 50, 48, 23, 44, 48, 23, 36, + 48, 25, 27, 48, 26, 18, 49, 28, 8, 49, 30, 0, + 50, 33, -10, 50, 36, -18, 51, 39, -27, 52, 42, -35, + 52, 46, -43, 53, 49, -51, 54, 53, -59, 55, 57, -67, + 49, 20, 55, 49, 20, 51, 49, 20, 45, 49, 21, 37, + 49, 22, 28, 49, 24, 19, 50, 26, 10, 50, 28, 1, + 51, 30, -8, 51, 33, -17, 52, 37, -26, 53, 40, -34, + 53, 44, -42, 54, 47, -50, 55, 51, -58, 56, 55, -65, + 50, 17, 56, 50, 18, 52, 50, 18, 46, 50, 19, 38, + 50, 20, 29, 50, 21, 20, 51, 23, 11, 51, 26, 2, + 52, 28, -7, 52, 31, -16, 53, 34, -24, 53, 38, -33, + 54, 41, -41, 55, 45, -49, 56, 49, -57, 57, 53, -64, + 51, 15, 57, 51, 15, 53, 51, 16, 47, 51, 17, 39, + 51, 18, 31, 51, 19, 22, 52, 21, 12, 52, 23, 3, + 53, 26, -5, 53, 29, -14, 54, 32, -23, 54, 35, -31, + 55, 39, -39, 56, 43, -48, 57, 47, -55, 58, 51, -63, + 52, 13, 57, 52, 13, 53, 52, 14, 48, 52, 14, 40, + 52, 15, 32, 52, 17, 23, 53, 19, 14, 53, 21, 5, + 53, 24, -4, 54, 27, -13, 55, 30, -22, 55, 33, -30, + 56, 37, -38, 57, 41, -46, 58, 45, -54, 59, 49, -62, + 53, 11, 58, 53, 11, 54, 53, 11, 48, 53, 12, 41, + 53, 13, 33, 53, 15, 24, 54, 17, 15, 54, 19, 6, + 54, 21, -3, 55, 24, -11, 56, 28, -20, 56, 31, -29, + 57, 35, -37, 58, 38, -45, 58, 42, -53, 59, 47, -60, + 54, 8, 59, 54, 9, 55, 54, 9, 49, 54, 10, 42, + 54, 11, 34, 54, 12, 26, 55, 14, 16, 55, 16, 8, + 55, 19, -1, 56, 22, -10, 56, 25, -19, 57, 29, -27, + 58, 32, -35, 59, 36, -43, 59, 40, -51, 60, 44, -59, + 55, 6, 59, 55, 6, 56, 55, 7, 50, 55, 8, 44, + 55, 9, 36, 55, 10, 27, 56, 12, 18, 56, 14, 9, + 56, 17, 0, 57, 20, -8, 57, 23, -17, 58, 26, -26, + 59, 30, -34, 59, 34, -42, 60, 38, -50, 61, 42, -58, + 56, 3, 60, 56, 4, 57, 56, 4, 52, 56, 5, 45, + 56, 6, 37, 57, 7, 29, 57, 9, 20, 57, 11, 11, + 58, 14, 2, 58, 17, -7, 59, 20, -15, 59, 24, -24, + 60, 27, -32, 61, 31, -40, 61, 35, -48, 62, 39, -56, + 57, 1, 61, 57, 1, 58, 57, 2, 53, 57, 3, 46, + 58, 4, 38, 58, 5, 30, 58, 7, 21, 58, 9, 12, + 59, 12, 3, 59, 15, -5, 60, 18, -14, 60, 21, -22, + 61, 25, -30, 62, 29, -39, 62, 33, -47, 63, 37, -54, + 58, -1, 62, 58, -1, 59, 58, 0, 54, 58, 0, 47, + 59, 1, 40, 59, 3, 31, 59, 5, 22, 59, 7, 14, + 60, 10, 5, 60, 12, -4, 61, 16, -12, 61, 19, -21, + 62, 23, -29, 63, 27, -37, 63, 31, -45, 64, 35, -53, + 59, -3, 63, 59, -3, 60, 59, -2, 55, 59, -1, 49, + 60, 0, 41, 60, 1, 33, 60, 2, 24, 60, 5, 15, + 61, 7, 6, 61, 10, -2, 62, 13, -11, 62, 17, -19, + 63, 21, -27, 64, 24, -36, 64, 28, -44, 65, 33, -51, + 60, -5, 63, 60, -5, 61, 60, -4, 56, 61, -4, 50, + 61, -2, 42, 61, -1, 34, 61, 0, 25, 61, 2, 17, + 62, 5, 8, 62, 8, 0, 63, 11, -9, 63, 15, -18, + 64, 18, -26, 64, 22, -34, 65, 26, -42, 66, 30, -50, + 61, -8, 64, 62, -7, 62, 62, -7, 57, 62, -6, 51, + 62, -5, 43, 62, -3, 36, 62, -1, 27, 63, 0, 18, + 63, 3, 9, 63, 6, 1, 64, 9, -8, 64, 12, -16, + 65, 16, -24, 65, 20, -33, 66, 24, -41, 67, 28, -48, + 63, -10, 65, 63, -9, 63, 63, -9, 58, 63, -8, 52, + 63, -7, 45, 63, -5, 37, 63, -3, 28, 64, -1, 20, + 64, 1, 11, 64, 3, 2, 65, 7, -6, 65, 10, -15, + 66, 14, -23, 66, 18, -31, 67, 21, -39, 68, 26, -47, + 64, -12, 66, 64, -11, 63, 64, -11, 59, 64, -10, 53, + 64, -9, 46, 64, -7, 38, 64, -5, 30, 65, -3, 21, + 65, -1, 12, 65, 1, 4, 66, 4, -5, 66, 8, -13, + 67, 11, -21, 68, 15, -30, 68, 19, -37, 69, 24, -45, + 65, -14, 67, 65, -14, 64, 65, -13, 60, 65, -12, 54, + 65, -11, 47, 65, -9, 40, 66, -8, 31, 66, -5, 23, + 66, -3, 14, 67, 0, 5, 67, 2, -3, 67, 6, -11, + 68, 9, -20, 69, 13, -28, 69, 17, -36, 70, 21, -44, + 66, -16, 67, 66, -16, 65, 66, -15, 61, 66, -14, 55, + 66, -13, 48, 66, -12, 41, 67, -10, 32, 67, -8, 24, + 67, -5, 15, 68, -3, 7, 68, 0, -2, 68, 3, -10, + 69, 7, -18, 70, 11, -26, 70, 15, -34, 71, 19, -42, + 67, -18, 68, 67, -18, 66, 67, -17, 62, 67, -16, 56, + 67, -15, 49, 68, -14, 42, 68, -12, 34, 68, -10, 26, + 68, -7, 17, 69, -5, 9, 69, -2, 0, 70, 1, -8, + 70, 5, -16, 71, 9, -25, 71, 12, -33, 72, 17, -41, + 68, -20, 69, 68, -20, 67, 68, -19, 63, 68, -18, 57, + 69, -17, 51, 69, -16, 44, 69, -14, 35, 69, -12, 27, + 69, -9, 18, 70, -7, 10, 70, -4, 1, 71, -1, -7, + 71, 3, -15, 72, 6, -23, 72, 10, -31, 73, 15, -39, + 69, -22, 70, 69, -22, 68, 69, -21, 64, 70, -20, 58, + 70, -19, 52, 70, -18, 45, 70, -16, 37, 70, -14, 29, + 71, -12, 20, 71, -9, 12, 71, -6, 3, 72, -3, -5, + 72, 0, -13, 73, 4, -22, 73, 8, -30, 74, 12, -38, + 70, -24, 71, 71, -24, 69, 71, -23, 65, 71, -22, 60, + 71, -21, 53, 71, -20, 46, 71, -18, 38, 71, -16, 30, + 72, -14, 21, 72, -11, 13, 72, -8, 5, 73, -5, -4, + 73, -2, -12, 74, 2, -20, 74, 6, -28, 75, 10, -36, + 72, -26, 72, 72, -26, 70, 72, -25, 66, 72, -24, 61, + 72, -23, 54, 72, -22, 47, 72, -20, 39, 72, -18, 32, + 73, -16, 23, 73, -13, 15, 73, -10, 6, 74, -7, -2, + 74, -4, -10, 75, 0, -18, 75, 4, -26, 76, 8, -34, + 73, -28, 73, 73, -28, 71, 73, -27, 67, 73, -26, 62, + 73, -25, 56, 73, -24, 49, 73, -22, 41, 74, -20, 33, + 74, -18, 25, 74, -15, 16, 75, -12, 8, 75, -9, 0, + 75, -6, -8, 76, -2, -17, 76, 1, -25, 77, 6, -33, + 74, -30, 73, 74, -29, 72, 74, -29, 68, 74, -28, 63, + 74, -27, 57, 74, -26, 50, 74, -24, 42, 75, -22, 35, + 75, -20, 26, 75, -17, 18, 76, -14, 9, 76, -11, 1, + 76, -8, -7, 77, -4, -15, 77, 0, -23, 78, 3, -31, + 75, -32, 74, 75, -31, 72, 75, -31, 69, 75, -30, 64, + 75, -29, 58, 75, -28, 51, 76, -26, 44, 76, -24, 36, + 76, -22, 28, 76, -19, 19, 77, -16, 11, 77, -13, 3, + 78, -10, -5, 78, -6, -14, 79, -3, -21, 79, 1, -30, + 76, -33, 75, 76, -33, 73, 76, -33, 70, 76, -32, 65, + 76, -31, 59, 77, -30, 53, 77, -28, 45, 77, -26, 37, + 77, -24, 29, 78, -21, 21, 78, -18, 12, 78, -15, 4, + 79, -12, -4, 79, -9, -12, 80, -5, -20, 80, -1, -28, + 77, -35, 76, 77, -35, 74, 77, -35, 71, 77, -34, 66, + 78, -33, 60, 78, -31, 54, 78, -30, 46, 78, -28, 39, + 78, -26, 31, 79, -23, 23, 79, -20, 14, 79, -17, 6, + 80, -14, -2, 80, -11, -10, 81, -7, -18, 81, -3, -26, + 79, -37, 77, 79, -37, 75, 79, -36, 72, 79, -36, 67, + 79, -35, 61, 79, -33, 55, 79, -32, 48, 79, -30, 40, + 79, -28, 32, 80, -25, 24, 80, -22, 16, 80, -19, 8, + 81, -16, 0, 81, -13, -9, 82, -9, -17, 82, -5, -25, + 80, -39, 78, 80, -39, 76, 80, -39, 73, 80, -38, 69, + 80, -37, 63, 80, -36, 57, 80, -34, 49, 81, -32, 42, + 81, -30, 34, 81, -28, 26, 81, -25, 18, 82, -22, 10, + 82, -19, 1, 83, -15, -7, 83, -12, -15, 84, -8, -23, + 81, -41, 79, 81, -41, 77, 81, -40, 74, 81, -40, 70, + 81, -39, 64, 81, -37, 58, 82, -36, 51, 82, -34, 43, + 82, -32, 35, 82, -29, 27, 83, -27, 19, 83, -24, 11, + 83, -21, 3, 84, -17, -5, 84, -14, -13, 85, -10, -21, + 82, -43, 80, 82, -43, 78, 82, -42, 75, 82, -41, 71, + 82, -40, 65, 83, -39, 59, 83, -38, 52, 83, -36, 45, + 83, -34, 37, 83, -31, 29, 84, -29, 21, 84, -26, 13, + 84, -23, 4, 85, -19, -4, 85, -16, -11, 86, -12, -19, + 83, -45, 81, 83, -44, 79, 83, -44, 76, 84, -43, 72, + 84, -42, 66, 84, -41, 60, 84, -39, 53, 84, -38, 46, + 84, -35, 38, 85, -33, 30, 85, -31, 22, 85, -28, 14, + 85, -25, 6, 86, -21, -2, 86, -18, -10, 87, -14, -18, + 85, -46, 82, 85, -46, 80, 85, -46, 77, 85, -45, 73, + 85, -44, 68, 85, -43, 62, 85, -41, 55, 85, -39, 48, + 85, -37, 40, 86, -35, 32, 86, -32, 24, 86, -30, 16, + 87, -27, 8, 87, -23, 0, 87, -20, -8, 88, -16, -16, + 86, -48, 83, 86, -48, 81, 86, -47, 78, 86, -47, 74, + 86, -46, 69, 86, -44, 63, 86, -43, 56, 86, -41, 49, + 87, -39, 41, 87, -37, 33, 87, -34, 25, 87, -32, 17, + 88, -29, 9, 88, -25, 1, 89, -22, -7, 89, -18, -15, + 87, -50, 84, 87, -49, 82, 87, -49, 79, 87, -48, 75, + 87, -47, 70, 87, -46, 64, 87, -45, 57, 87, -43, 50, + 88, -41, 42, 88, -39, 35, 88, -36, 27, 88, -33, 19, + 89, -30, 11, 89, -27, 3, 90, -24, -5, 90, -20, -13, + 88, -51, 84, 88, -51, 83, 88, -51, 80, 88, -50, 76, + 88, -49, 71, 88, -48, 65, 88, -46, 59, 89, -45, 52, + 89, -43, 44, 89, -40, 36, 89, -38, 28, 90, -35, 20, + 90, -32, 12, 90, -29, 4, 91, -26, -3, 91, -22, -11, + 89, -53, 85, 89, -53, 84, 89, -52, 81, 89, -52, 77, + 89, -51, 72, 89, -50, 66, 90, -48, 60, 90, -46, 53, + 90, -44, 45, 90, -42, 38, 90, -40, 30, 91, -37, 22, + 91, -34, 14, 91, -31, 6, 92, -28, -2, 92, -24, -10, + 90, -55, 86, 90, -54, 85, 90, -54, 82, 90, -53, 78, + 90, -52, 73, 91, -51, 68, 91, -50, 61, 91, -48, 54, + 91, -46, 47, 91, -44, 39, 92, -41, 31, 92, -39, 24, + 92, -36, 15, 93, -33, 8, 93, -29, 0, 93, -26, -8, + 91, -56, 87, 91, -56, 85, 92, -55, 83, 92, -55, 80, + 92, -54, 75, 92, -53, 69, 92, -51, 62, 92, -50, 56, + 92, -48, 48, 92, -46, 41, 93, -43, 33, 93, -41, 25, + 93, -38, 17, 94, -35, 9, 94, -31, 1, 94, -28, -7, + 36, 60, 48, 36, 60, 41, 36, 60, 32, 36, 61, 22, + 37, 62, 12, 37, 63, 2, 37, 64, -7, 38, 66, -17, + 39, 68, -26, 40, 70, -35, 41, 72, -43, 42, 75, -51, + 43, 78, -59, 44, 80, -67, 45, 83, -74, 46, 86, -81, + 36, 59, 48, 36, 60, 41, 36, 60, 32, 36, 61, 22, + 37, 61, 12, 37, 63, 2, 38, 64, -7, 38, 66, -16, + 39, 67, -26, 40, 70, -34, 41, 72, -43, 42, 75, -51, + 43, 77, -59, 44, 80, -66, 45, 83, -74, 47, 86, -81, + 36, 59, 48, 36, 59, 41, 36, 60, 32, 37, 60, 22, + 37, 61, 12, 37, 62, 2, 38, 64, -7, 38, 65, -16, + 39, 67, -26, 40, 69, -34, 41, 72, -43, 42, 74, -51, + 43, 77, -59, 44, 80, -66, 45, 83, -74, 47, 86, -81, + 36, 58, 48, 36, 59, 42, 36, 59, 33, 37, 60, 23, + 37, 61, 12, 37, 62, 3, 38, 63, -7, 39, 65, -16, + 39, 67, -25, 40, 69, -34, 41, 71, -43, 42, 74, -51, + 43, 77, -58, 44, 79, -66, 45, 82, -73, 47, 85, -80, + 36, 58, 48, 37, 58, 42, 37, 58, 33, 37, 59, 23, + 37, 60, 13, 38, 61, 3, 38, 62, -6, 39, 64, -16, + 39, 66, -25, 40, 68, -34, 41, 71, -43, 42, 73, -50, + 43, 76, -58, 44, 79, -66, 46, 82, -73, 47, 85, -80, + 37, 57, 49, 37, 57, 42, 37, 58, 33, 37, 58, 23, + 37, 59, 13, 38, 60, 3, 38, 62, -6, 39, 63, -15, + 40, 65, -25, 40, 68, -33, 41, 70, -42, 42, 73, -50, + 43, 76, -58, 45, 78, -65, 46, 81, -73, 47, 84, -80, + 37, 56, 49, 37, 57, 42, 37, 57, 33, 37, 58, 24, + 38, 58, 13, 38, 60, 4, 39, 61, -6, 39, 63, -15, + 40, 65, -24, 41, 67, -33, 42, 69, -42, 43, 72, -50, + 44, 75, -58, 45, 78, -65, 46, 81, -73, 47, 84, -79, + 37, 55, 49, 37, 56, 43, 38, 56, 34, 38, 57, 24, + 38, 58, 14, 38, 59, 4, 39, 60, -5, 40, 62, -15, + 40, 64, -24, 41, 66, -33, 42, 69, -41, 43, 71, -49, + 44, 74, -57, 45, 77, -65, 46, 80, -72, 48, 83, -79, + 38, 54, 49, 38, 55, 43, 38, 55, 34, 38, 56, 24, + 38, 57, 14, 39, 58, 5, 39, 59, -5, 40, 61, -14, + 41, 63, -23, 41, 65, -32, 42, 68, -41, 43, 70, -49, + 44, 73, -57, 45, 76, -64, 46, 79, -72, 48, 82, -79, + 38, 53, 49, 38, 53, 43, 38, 54, 35, 38, 54, 25, + 39, 55, 15, 39, 57, 5, 40, 58, -4, 40, 60, -13, + 41, 62, -23, 42, 64, -32, 43, 67, -40, 43, 69, -48, + 44, 72, -56, 46, 75, -64, 47, 78, -71, 48, 82, -78, + 39, 52, 50, 39, 52, 44, 39, 53, 35, 39, 53, 25, + 39, 54, 15, 40, 55, 6, 40, 57, -4, 41, 59, -13, + 41, 61, -22, 42, 63, -31, 43, 66, -40, 44, 68, -48, + 45, 71, -56, 46, 74, -63, 47, 77, -71, 48, 81, -78, + 39, 50, 50, 39, 50, 44, 39, 51, 36, 40, 51, 26, + 40, 52, 16, 40, 54, 7, 41, 55, -3, 41, 57, -12, + 42, 59, -21, 43, 61, -30, 43, 64, -39, 44, 67, -47, + 45, 70, -55, 46, 73, -62, 48, 76, -70, 49, 79, -77, + 40, 49, 50, 40, 49, 45, 40, 49, 36, 40, 50, 27, + 40, 51, 17, 41, 52, 7, 41, 54, -2, 42, 56, -11, + 42, 58, -21, 43, 60, -29, 44, 63, -38, 45, 66, -46, + 46, 69, -54, 47, 72, -62, 48, 75, -69, 49, 78, -76, + 40, 47, 51, 40, 47, 45, 40, 48, 37, 41, 48, 27, + 41, 49, 18, 41, 51, 8, 42, 52, -1, 42, 54, -10, + 43, 56, -20, 44, 59, -28, 44, 61, -37, 45, 64, -45, + 46, 67, -53, 47, 70, -61, 48, 74, -69, 50, 77, -76, + 41, 45, 51, 41, 46, 45, 41, 46, 38, 41, 47, 28, + 42, 48, 18, 42, 49, 9, 42, 51, 0, 43, 52, -9, + 43, 55, -19, 44, 57, -28, 45, 60, -37, 46, 63, -45, + 47, 66, -52, 48, 69, -60, 49, 72, -68, 50, 76, -75, + 42, 44, 51, 42, 44, 46, 42, 44, 38, 42, 45, 29, + 42, 46, 19, 43, 47, 10, 43, 49, 0, 43, 51, -9, + 44, 53, -18, 45, 56, -27, 46, 58, -36, 46, 61, -44, + 47, 64, -52, 48, 68, -59, 49, 71, -67, 51, 74, -74, + 42, 42, 52, 42, 42, 46, 42, 43, 39, 43, 43, 30, + 43, 44, 20, 43, 46, 11, 44, 47, 1, 44, 49, -8, + 45, 51, -17, 45, 54, -26, 46, 57, -35, 47, 60, -43, + 48, 63, -51, 49, 66, -59, 50, 69, -66, 51, 73, -73, + 43, 40, 52, 43, 40, 47, 43, 41, 40, 43, 41, 30, + 44, 42, 21, 44, 44, 12, 44, 45, 2, 45, 47, -7, + 45, 50, -16, 46, 52, -25, 47, 55, -34, 48, 58, -42, + 48, 61, -50, 49, 64, -58, 51, 68, -65, 52, 71, -72, + 44, 38, 53, 44, 38, 47, 44, 39, 41, 44, 39, 31, + 44, 40, 22, 45, 42, 13, 45, 43, 3, 45, 45, -6, + 46, 48, -15, 47, 50, -24, 47, 53, -33, 48, 56, -41, + 49, 59, -49, 50, 63, -57, 51, 66, -64, 52, 70, -72, + 45, 36, 53, 45, 36, 48, 45, 37, 41, 45, 37, 32, + 45, 38, 23, 45, 40, 14, 46, 41, 4, 46, 43, -4, + 47, 46, -14, 47, 48, -23, 48, 51, -32, 49, 54, -40, + 50, 58, -48, 51, 61, -56, 52, 65, -63, 53, 68, -71, + 45, 34, 54, 45, 34, 49, 45, 35, 42, 46, 35, 33, + 46, 36, 24, 46, 38, 15, 47, 39, 5, 47, 41, -3, + 48, 44, -13, 48, 46, -21, 49, 49, -30, 50, 52, -39, + 50, 56, -47, 51, 59, -55, 52, 63, -62, 53, 66, -70, + 46, 32, 54, 46, 32, 49, 46, 33, 43, 46, 33, 34, + 47, 34, 25, 47, 36, 16, 47, 37, 6, 48, 39, -2, + 48, 42, -12, 49, 44, -20, 50, 47, -29, 50, 50, -37, + 51, 54, -45, 52, 57, -53, 53, 61, -61, 54, 64, -69, + 47, 30, 55, 47, 30, 50, 47, 30, 44, 47, 31, 35, + 48, 32, 26, 48, 34, 17, 48, 35, 8, 49, 37, -1, + 49, 40, -10, 50, 42, -19, 50, 45, -28, 51, 48, -36, + 52, 52, -44, 53, 55, -52, 54, 59, -60, 55, 63, -67, + 48, 28, 55, 48, 28, 51, 48, 28, 44, 48, 29, 36, + 48, 30, 27, 49, 31, 18, 49, 33, 9, 49, 35, 0, + 50, 38, -9, 51, 40, -18, 51, 43, -27, 52, 46, -35, + 53, 50, -43, 54, 53, -51, 54, 57, -59, 56, 61, -66, + 49, 25, 56, 49, 26, 51, 49, 26, 45, 49, 27, 37, + 49, 28, 28, 50, 29, 20, 50, 31, 10, 50, 33, 1, + 51, 35, -8, 51, 38, -17, 52, 41, -26, 53, 44, -34, + 53, 48, -42, 54, 51, -50, 55, 55, -58, 56, 59, -65, + 50, 23, 57, 50, 23, 52, 50, 24, 46, 50, 25, 38, + 50, 26, 30, 50, 27, 21, 51, 29, 11, 51, 31, 2, + 52, 33, -7, 52, 36, -15, 53, 39, -24, 54, 42, -33, + 54, 46, -41, 55, 49, -49, 56, 53, -57, 57, 57, -64, + 51, 21, 57, 51, 21, 53, 51, 22, 47, 51, 22, 39, + 51, 23, 31, 51, 25, 22, 52, 27, 13, 52, 29, 4, + 53, 31, -5, 53, 34, -14, 54, 37, -23, 54, 40, -31, + 55, 44, -39, 56, 47, -47, 57, 51, -55, 58, 55, -63, + 52, 19, 58, 52, 19, 54, 52, 19, 48, 52, 20, 40, + 52, 21, 32, 52, 23, 23, 53, 24, 14, 53, 26, 5, + 53, 29, -4, 54, 32, -13, 55, 35, -22, 55, 38, -30, + 56, 41, -38, 57, 45, -46, 58, 49, -54, 59, 53, -62, + 53, 16, 58, 53, 17, 55, 53, 17, 49, 53, 18, 42, + 53, 19, 33, 53, 20, 25, 54, 22, 15, 54, 24, 6, + 54, 27, -3, 55, 29, -11, 55, 33, -20, 56, 36, -28, + 57, 39, -37, 58, 43, -45, 58, 47, -53, 59, 51, -60, + 54, 14, 59, 54, 14, 55, 54, 15, 50, 54, 16, 43, + 54, 17, 34, 54, 18, 26, 55, 20, 16, 55, 22, 8, + 55, 24, -1, 56, 27, -10, 56, 30, -19, 57, 33, -27, + 58, 37, -35, 58, 41, -43, 59, 45, -51, 60, 48, -59, + 55, 12, 60, 55, 12, 56, 55, 13, 51, 55, 13, 44, + 55, 14, 36, 55, 16, 27, 56, 18, 18, 56, 20, 9, + 56, 22, 0, 57, 25, -8, 57, 28, -17, 58, 31, -26, + 59, 35, -34, 59, 38, -42, 60, 42, -50, 61, 46, -58, + 56, 10, 60, 56, 10, 57, 56, 10, 52, 56, 11, 45, + 56, 12, 37, 56, 14, 28, 57, 15, 19, 57, 17, 10, + 57, 20, 1, 58, 23, -7, 58, 26, -16, 59, 29, -24, + 60, 33, -32, 60, 36, -41, 61, 40, -49, 62, 44, -56, + 57, 7, 61, 57, 7, 58, 57, 8, 53, 57, 8, 46, + 57, 9, 38, 58, 11, 30, 58, 12, 21, 58, 15, 12, + 59, 17, 3, 59, 20, -5, 59, 23, -14, 60, 26, -22, + 61, 30, -31, 61, 33, -39, 62, 37, -47, 63, 41, -55, + 58, 5, 62, 58, 5, 59, 58, 5, 54, 58, 6, 47, + 58, 7, 40, 59, 8, 31, 59, 10, 22, 59, 12, 14, + 60, 15, 5, 60, 18, -4, 60, 21, -13, 61, 24, -21, + 62, 28, -29, 62, 31, -37, 63, 35, -45, 64, 39, -53, + 59, 3, 63, 59, 3, 60, 59, 3, 55, 59, 4, 49, + 59, 5, 41, 60, 6, 33, 60, 8, 24, 60, 10, 15, + 61, 13, 6, 61, 15, -2, 61, 18, -11, 62, 22, -19, + 63, 25, -28, 63, 29, -36, 64, 33, -44, 65, 37, -52, + 60, 0, 64, 60, 1, 61, 60, 1, 56, 60, 2, 50, + 60, 3, 42, 61, 4, 34, 61, 6, 25, 61, 8, 17, + 62, 10, 7, 62, 13, -1, 62, 16, -10, 63, 19, -18, + 64, 23, -26, 64, 27, -35, 65, 31, -42, 66, 35, -50, + 61, -2, 64, 61, -1, 62, 61, -1, 57, 61, 0, 51, + 62, 1, 43, 62, 2, 35, 62, 4, 26, 62, 6, 18, + 63, 8, 9, 63, 11, 0, 63, 14, -8, 64, 17, -16, + 65, 21, -25, 65, 24, -33, 66, 28, -41, 67, 32, -49, + 62, -4, 65, 62, -4, 63, 62, -3, 58, 62, -2, 52, + 63, -1, 44, 63, 0, 37, 63, 1, 28, 63, 3, 19, + 64, 6, 10, 64, 9, 2, 64, 12, -7, 65, 15, -15, + 66, 19, -23, 66, 22, -32, 67, 26, -39, 68, 30, -47, + 63, -6, 66, 63, -6, 64, 63, -5, 59, 64, -4, 53, + 64, -3, 46, 64, -2, 38, 64, 0, 29, 64, 1, 21, + 65, 4, 12, 65, 6, 4, 66, 9, -5, 66, 13, -13, + 67, 16, -22, 67, 20, -30, 68, 24, -38, 69, 28, -46, + 64, -8, 67, 64, -8, 64, 65, -7, 60, 65, -6, 54, + 65, -5, 47, 65, -4, 39, 65, -2, 31, 65, 0, 22, + 66, 1, 13, 66, 4, 5, 67, 7, -4, 67, 10, -12, + 68, 14, -20, 68, 18, -28, 69, 22, -36, 70, 26, -44, + 66, -10, 67, 66, -10, 65, 66, -9, 61, 66, -9, 55, + 66, -7, 48, 66, -6, 41, 66, -4, 32, 66, -2, 24, + 67, 0, 15, 67, 2, 7, 68, 5, -2, 68, 8, -10, + 69, 12, -18, 69, 15, -27, 70, 19, -35, 70, 23, -43, + 67, -12, 68, 67, -12, 66, 67, -12, 62, 67, -11, 56, + 67, -10, 49, 67, -8, 42, 67, -6, 33, 68, -4, 25, + 68, -2, 16, 68, 0, 8, 69, 3, -1, 69, 6, -9, + 70, 10, -17, 70, 13, -25, 71, 17, -33, 71, 21, -41, + 68, -14, 69, 68, -14, 67, 68, -14, 63, 68, -13, 57, + 68, -12, 50, 68, -10, 43, 68, -9, 35, 69, -7, 27, + 69, -4, 18, 69, -2, 10, 70, 1, 1, 70, 4, -7, + 71, 7, -15, 71, 11, -24, 72, 15, -32, 72, 19, -40, + 69, -17, 70, 69, -16, 68, 69, -16, 64, 69, -15, 58, + 69, -14, 52, 69, -12, 44, 70, -11, 36, 70, -9, 28, + 70, -6, 19, 70, -4, 11, 71, -1, 2, 71, 2, -6, + 72, 5, -14, 72, 9, -22, 73, 13, -30, 73, 17, -38, + 70, -19, 71, 70, -18, 69, 70, -18, 65, 70, -17, 59, + 70, -16, 53, 70, -14, 46, 71, -13, 38, 71, -11, 30, + 71, -8, 21, 71, -6, 13, 72, -3, 4, 72, 0, -4, + 73, 3, -12, 73, 7, -21, 74, 10, -29, 74, 15, -37, + 71, -21, 72, 71, -20, 70, 71, -20, 66, 71, -19, 60, + 71, -18, 54, 72, -17, 47, 72, -15, 39, 72, -13, 31, + 72, -11, 22, 73, -8, 14, 73, -5, 5, 73, -2, -3, + 74, 1, -11, 74, 4, -19, 75, 8, -27, 75, 12, -35, + 72, -23, 72, 72, -22, 70, 72, -22, 67, 72, -21, 61, + 72, -20, 55, 73, -19, 48, 73, -17, 40, 73, -15, 32, + 73, -13, 24, 74, -10, 16, 74, -7, 7, 74, -4, -1, + 75, -1, -9, 75, 2, -18, 76, 6, -25, 77, 10, -33, + 73, -24, 73, 73, -24, 71, 73, -24, 68, 74, -23, 62, + 74, -22, 56, 74, -21, 50, 74, -19, 42, 74, -17, 34, + 74, -15, 25, 75, -12, 17, 75, -9, 9, 75, -7, 1, + 76, -3, -8, 76, 0, -16, 77, 4, -24, 78, 8, -32, + 74, -26, 74, 75, -26, 72, 75, -26, 69, 75, -25, 64, + 75, -24, 57, 75, -23, 51, 75, -21, 43, 75, -19, 35, + 76, -17, 27, 76, -14, 19, 76, -12, 10, 77, -9, 2, + 77, -6, -6, 77, -2, -14, 78, 2, -22, 79, 6, -30, + 76, -28, 75, 76, -28, 73, 76, -28, 70, 76, -27, 65, + 76, -26, 59, 76, -24, 52, 76, -23, 44, 76, -21, 37, + 77, -19, 28, 77, -16, 20, 77, -14, 12, 78, -11, 4, + 78, -8, -4, 79, -4, -13, 79, 0, -21, 80, 4, -29, + 77, -30, 76, 77, -30, 74, 77, -29, 71, 77, -29, 66, + 77, -28, 60, 77, -26, 53, 77, -25, 46, 77, -23, 38, + 78, -21, 30, 78, -18, 22, 78, -16, 13, 79, -13, 5, + 79, -10, -3, 80, -6, -11, 80, -2, -19, 81, 1, -27, + 78, -32, 77, 78, -32, 75, 78, -31, 72, 78, -31, 67, + 78, -30, 61, 78, -28, 55, 78, -27, 47, 79, -25, 40, + 79, -23, 31, 79, -20, 23, 79, -18, 15, 80, -15, 7, + 80, -12, -1, 81, -8, -10, 81, -5, -17, 82, -1, -26, + 79, -34, 78, 79, -34, 76, 79, -33, 73, 79, -32, 68, + 79, -31, 62, 79, -30, 56, 80, -29, 48, 80, -27, 41, + 80, -25, 33, 80, -22, 25, 81, -20, 16, 81, -17, 8, + 81, -14, 0, 82, -10, -8, 82, -7, -16, 83, -3, -24, + 80, -36, 79, 80, -36, 77, 81, -35, 74, 81, -35, 69, + 81, -34, 64, 81, -33, 57, 81, -31, 50, 81, -29, 43, + 81, -27, 35, 82, -25, 27, 82, -22, 18, 82, -19, 10, + 83, -16, 2, 83, -13, -6, 84, -9, -14, 84, -5, -22, + 82, -38, 80, 82, -38, 78, 82, -37, 75, 82, -37, 70, + 82, -36, 65, 82, -34, 59, 82, -33, 51, 82, -31, 44, + 82, -29, 36, 83, -27, 28, 83, -24, 20, 83, -21, 12, + 84, -18, 3, 84, -15, -4, 85, -11, -12, 85, -8, -20, + 83, -40, 81, 83, -40, 79, 83, -39, 76, 83, -38, 71, + 83, -37, 66, 83, -36, 60, 83, -35, 53, 83, -33, 45, + 84, -31, 37, 84, -29, 30, 84, -26, 21, 84, -23, 13, + 85, -20, 5, 85, -17, -3, 86, -13, -11, 86, -10, -19, + 84, -42, 81, 84, -41, 80, 84, -41, 77, 84, -40, 73, + 84, -39, 67, 84, -38, 61, 84, -36, 54, 85, -35, 47, + 85, -33, 39, 85, -30, 31, 85, -28, 23, 86, -25, 15, + 86, -22, 7, 86, -19, -1, 87, -15, -9, 87, -12, -17, + 85, -43, 82, 85, -43, 81, 85, -43, 78, 85, -42, 74, + 85, -41, 68, 85, -40, 62, 85, -38, 55, 86, -37, 48, + 86, -34, 40, 86, -32, 33, 86, -30, 24, 87, -27, 16, + 87, -24, 8, 87, -21, 0, 88, -17, -7, 88, -14, -16, + 86, -45, 83, 86, -45, 81, 86, -44, 79, 86, -44, 75, + 86, -43, 69, 86, -42, 63, 87, -40, 57, 87, -38, 50, + 87, -36, 42, 87, -34, 34, 87, -32, 26, 88, -29, 18, + 88, -26, 10, 89, -23, 2, 89, -19, -6, 89, -16, -14, + 87, -47, 84, 87, -46, 82, 87, -46, 80, 87, -45, 76, + 87, -44, 71, 88, -43, 65, 88, -42, 58, 88, -40, 51, + 88, -38, 43, 88, -36, 36, 89, -34, 27, 89, -31, 20, + 89, -28, 11, 90, -25, 3, 90, -21, -4, 90, -18, -12, + 88, -48, 85, 88, -48, 83, 88, -48, 81, 89, -47, 77, + 89, -46, 72, 89, -45, 66, 89, -44, 59, 89, -42, 52, + 89, -40, 45, 89, -38, 37, 90, -35, 29, 90, -33, 21, + 90, -30, 13, 91, -27, 5, 91, -23, -3, 92, -20, -11, + 90, -50, 86, 90, -50, 84, 90, -49, 82, 90, -49, 78, + 90, -48, 73, 90, -47, 67, 90, -45, 60, 90, -44, 54, + 90, -42, 46, 91, -40, 38, 91, -37, 30, 91, -35, 23, + 91, -32, 14, 92, -29, 7, 92, -25, -1, 93, -22, -9, + 91, -52, 87, 91, -51, 85, 91, -51, 83, 91, -50, 79, + 91, -50, 74, 91, -48, 68, 91, -47, 62, 91, -45, 55, + 91, -43, 47, 92, -41, 40, 92, -39, 32, 92, -36, 24, + 93, -34, 16, 93, -30, 8, 93, -27, 0, 94, -24, -8, + 92, -53, 88, 92, -53, 86, 92, -53, 84, 92, -52, 80, + 92, -51, 75, 92, -50, 69, 92, -49, 63, 92, -47, 56, + 93, -45, 49, 93, -43, 41, 93, -41, 33, 93, -38, 26, + 94, -35, 18, 94, -32, 10, 94, -29, 2, 95, -25, -6, + 38, 62, 50, 38, 62, 43, 38, 62, 34, 38, 63, 25, + 38, 64, 14, 39, 65, 5, 39, 66, -5, 40, 68, -14, + 40, 69, -24, 41, 71, -32, 42, 74, -41, 43, 76, -49, + 44, 79, -57, 45, 81, -64, 46, 84, -72, 48, 87, -79, + 38, 61, 50, 38, 62, 44, 38, 62, 35, 38, 63, 25, + 38, 63, 14, 39, 64, 5, 39, 66, -5, 40, 67, -14, + 41, 69, -23, 41, 71, -32, 42, 73, -41, 43, 76, -49, + 44, 78, -57, 45, 81, -64, 46, 84, -72, 48, 87, -79, + 38, 61, 50, 38, 61, 44, 38, 62, 35, 38, 62, 25, + 39, 63, 15, 39, 64, 5, 39, 65, -4, 40, 67, -14, + 41, 69, -23, 41, 71, -32, 42, 73, -41, 43, 75, -49, + 44, 78, -56, 45, 81, -64, 47, 84, -72, 48, 87, -78, + 38, 61, 50, 38, 61, 44, 38, 61, 35, 38, 62, 25, + 39, 63, 15, 39, 64, 5, 40, 65, -4, 40, 66, -13, + 41, 68, -23, 42, 70, -32, 42, 73, -40, 43, 75, -48, + 44, 78, -56, 46, 80, -64, 47, 83, -71, 48, 86, -78, + 38, 60, 50, 38, 60, 44, 38, 61, 35, 39, 61, 25, + 39, 62, 15, 39, 63, 5, 40, 64, -4, 40, 66, -13, + 41, 68, -23, 42, 70, -31, 43, 72, -40, 44, 75, -48, + 45, 77, -56, 46, 80, -64, 47, 83, -71, 48, 86, -78, + 38, 59, 50, 38, 60, 44, 39, 60, 35, 39, 60, 26, + 39, 61, 15, 39, 62, 6, 40, 64, -4, 41, 65, -13, + 41, 67, -22, 42, 69, -31, 43, 72, -40, 44, 74, -48, + 45, 77, -56, 46, 79, -63, 47, 82, -71, 48, 85, -78, + 39, 59, 50, 39, 59, 44, 39, 59, 36, 39, 60, 26, + 39, 61, 16, 40, 62, 6, 40, 63, -3, 41, 65, -12, + 41, 66, -22, 42, 68, -31, 43, 71, -39, 44, 73, -47, + 45, 76, -55, 46, 79, -63, 47, 82, -71, 49, 85, -78, + 39, 58, 51, 39, 58, 45, 39, 58, 36, 39, 59, 26, + 40, 60, 16, 40, 61, 7, 41, 62, -3, 41, 64, -12, + 42, 66, -22, 42, 68, -30, 43, 70, -39, 44, 73, -47, + 45, 75, -55, 46, 78, -63, 47, 81, -70, 49, 84, -77, + 39, 57, 51, 39, 57, 45, 40, 57, 36, 40, 58, 27, + 40, 59, 17, 40, 60, 7, 41, 61, -2, 41, 63, -12, + 42, 65, -21, 43, 67, -30, 44, 69, -39, 45, 72, -47, + 45, 75, -54, 47, 77, -62, 48, 80, -70, 49, 83, -77, + 40, 56, 51, 40, 56, 45, 40, 56, 37, 40, 57, 27, + 40, 58, 17, 41, 59, 8, 41, 60, -2, 42, 62, -11, + 42, 64, -21, 43, 66, -29, 44, 68, -38, 45, 71, -46, + 46, 74, -54, 47, 77, -62, 48, 80, -69, 49, 83, -76, + 40, 54, 51, 40, 55, 46, 40, 55, 37, 41, 56, 28, + 41, 57, 18, 41, 58, 8, 42, 59, -1, 42, 61, -10, + 43, 63, -20, 43, 65, -29, 44, 67, -37, 45, 70, -45, + 46, 73, -53, 47, 76, -61, 48, 79, -69, 50, 82, -76, + 41, 53, 52, 41, 53, 46, 41, 53, 38, 41, 54, 28, + 41, 55, 18, 42, 56, 9, 42, 57, 0, 43, 59, -10, + 43, 61, -19, 44, 63, -28, 45, 66, -37, 46, 68, -45, + 47, 71, -53, 48, 74, -60, 49, 77, -68, 50, 80, -75, + 41, 51, 52, 41, 52, 46, 41, 52, 38, 42, 53, 29, + 42, 54, 19, 42, 55, 10, 43, 56, 0, 43, 58, -9, + 44, 60, -18, 44, 62, -27, 45, 65, -36, 46, 67, -44, + 47, 70, -52, 48, 73, -60, 49, 76, -67, 50, 79, -75, + 42, 50, 52, 42, 50, 47, 42, 50, 39, 42, 51, 29, + 42, 52, 20, 43, 53, 10, 43, 55, 0, 44, 56, -8, + 44, 58, -18, 45, 61, -26, 46, 63, -35, 47, 66, -43, + 47, 69, -51, 49, 72, -59, 50, 75, -67, 51, 78, -74, + 42, 48, 53, 42, 48, 47, 43, 49, 40, 43, 50, 30, + 43, 50, 21, 43, 52, 11, 44, 53, 1, 44, 55, -7, + 45, 57, -17, 45, 59, -25, 46, 62, -34, 47, 64, -43, + 48, 67, -50, 49, 70, -58, 50, 74, -66, 51, 77, -73, + 43, 47, 53, 43, 47, 48, 43, 47, 40, 43, 48, 31, + 44, 49, 21, 44, 50, 12, 44, 51, 2, 45, 53, -6, + 45, 55, -16, 46, 58, -25, 47, 60, -34, 48, 63, -42, + 49, 66, -50, 50, 69, -58, 51, 72, -65, 52, 76, -72, + 44, 45, 53, 44, 45, 48, 44, 45, 41, 44, 46, 32, + 44, 47, 22, 45, 48, 13, 45, 50, 3, 45, 51, -6, + 46, 54, -15, 47, 56, -24, 47, 59, -33, 48, 61, -41, + 49, 65, -49, 50, 68, -57, 51, 71, -64, 52, 74, -72, + 44, 43, 54, 44, 43, 49, 45, 44, 42, 45, 44, 32, + 45, 45, 23, 45, 46, 14, 46, 48, 4, 46, 50, -5, + 47, 52, -14, 47, 54, -23, 48, 57, -32, 49, 60, -40, + 50, 63, -48, 51, 66, -56, 52, 69, -63, 53, 73, -71, + 45, 41, 54, 45, 41, 49, 45, 42, 42, 45, 42, 33, + 46, 43, 24, 46, 45, 15, 46, 46, 5, 47, 48, -4, + 47, 50, -13, 48, 53, -22, 49, 55, -31, 49, 58, -39, + 50, 61, -47, 51, 64, -55, 52, 68, -63, 53, 71, -70, + 46, 39, 55, 46, 39, 50, 46, 40, 43, 46, 40, 34, + 46, 41, 25, 47, 43, 16, 47, 44, 6, 47, 46, -3, + 48, 48, -12, 49, 51, -21, 49, 53, -30, 50, 56, -38, + 51, 60, -46, 52, 63, -54, 53, 66, -62, 54, 70, -69, + 47, 37, 55, 47, 37, 50, 47, 38, 44, 47, 38, 35, + 47, 39, 26, 47, 41, 17, 48, 42, 7, 48, 44, -1, + 49, 46, -11, 49, 49, -20, 50, 52, -29, 51, 54, -37, + 52, 58, -45, 52, 61, -53, 53, 64, -61, 54, 68, -68, + 47, 35, 56, 47, 35, 51, 48, 36, 44, 48, 36, 36, + 48, 37, 27, 48, 39, 18, 49, 40, 8, 49, 42, 0, + 49, 44, -10, 50, 47, -18, 51, 50, -27, 51, 53, -36, + 52, 56, -44, 53, 59, -52, 54, 63, -60, 55, 66, -67, + 48, 33, 56, 48, 33, 51, 48, 34, 45, 48, 34, 37, + 49, 35, 28, 49, 37, 19, 49, 38, 9, 50, 40, 0, + 50, 42, -9, 51, 45, -17, 51, 48, -26, 52, 51, -35, + 53, 54, -43, 54, 57, -51, 55, 61, -58, 56, 64, -66, + 49, 31, 57, 49, 31, 52, 49, 32, 46, 49, 32, 38, + 50, 33, 29, 50, 35, 20, 50, 36, 10, 51, 38, 2, + 51, 40, -7, 52, 43, -16, 52, 46, -25, 53, 49, -33, + 54, 52, -41, 55, 55, -50, 55, 59, -57, 56, 62, -65, + 50, 29, 57, 50, 29, 53, 50, 29, 47, 50, 30, 39, + 50, 31, 30, 51, 32, 21, 51, 34, 12, 51, 36, 3, + 52, 38, -6, 52, 41, -15, 53, 44, -24, 54, 47, -32, + 54, 50, -40, 55, 53, -48, 56, 57, -56, 57, 61, -64, + 51, 27, 58, 51, 27, 54, 51, 27, 48, 51, 28, 40, + 51, 29, 31, 52, 30, 22, 52, 32, 13, 52, 34, 4, + 53, 36, -5, 53, 39, -14, 54, 42, -23, 55, 45, -31, + 55, 48, -39, 56, 51, -47, 57, 55, -55, 58, 59, -63, + 52, 24, 58, 52, 25, 54, 52, 25, 48, 52, 26, 41, + 52, 27, 32, 52, 28, 24, 53, 30, 14, 53, 32, 5, + 54, 34, -4, 54, 37, -12, 55, 39, -21, 55, 42, -30, + 56, 46, -38, 57, 49, -46, 58, 53, -54, 59, 57, -61, + 53, 22, 59, 53, 22, 55, 53, 23, 49, 53, 24, 42, + 53, 25, 34, 53, 26, 25, 54, 27, 15, 54, 29, 7, + 54, 32, -2, 55, 34, -11, 56, 37, -20, 56, 40, -28, + 57, 44, -36, 58, 47, -45, 58, 51, -52, 59, 55, -60, + 54, 20, 60, 54, 20, 56, 54, 21, 50, 54, 21, 43, + 54, 22, 35, 54, 24, 26, 55, 25, 17, 55, 27, 8, + 55, 30, -1, 56, 32, -10, 56, 35, -19, 57, 38, -27, + 58, 42, -35, 58, 45, -43, 59, 49, -51, 60, 52, -59, + 55, 18, 60, 55, 18, 57, 55, 18, 51, 55, 19, 44, + 55, 20, 36, 55, 21, 27, 56, 23, 18, 56, 25, 9, + 56, 27, 0, 57, 30, -8, 57, 33, -17, 58, 36, -26, + 59, 39, -34, 59, 43, -42, 60, 47, -50, 61, 50, -58, + 56, 16, 61, 56, 16, 58, 56, 16, 52, 56, 17, 45, + 56, 18, 37, 56, 19, 28, 56, 21, 19, 57, 23, 11, + 57, 25, 1, 58, 28, -7, 58, 31, -16, 59, 34, -24, + 59, 37, -32, 60, 41, -41, 61, 44, -49, 62, 48, -56, + 57, 13, 62, 57, 14, 58, 57, 14, 53, 57, 15, 46, + 57, 16, 38, 57, 17, 30, 57, 19, 21, 58, 20, 12, + 58, 23, 3, 59, 26, -6, 59, 29, -15, 60, 32, -23, + 60, 35, -31, 61, 39, -39, 62, 42, -47, 63, 46, -55, + 58, 11, 62, 58, 11, 59, 58, 11, 54, 58, 12, 48, + 58, 13, 40, 58, 14, 31, 59, 16, 22, 59, 18, 14, + 59, 20, 4, 60, 23, -4, 60, 26, -13, 61, 29, -21, + 61, 32, -29, 62, 36, -38, 63, 40, -45, 64, 43, -53, + 59, 8, 63, 59, 9, 60, 59, 9, 55, 59, 10, 49, + 59, 11, 41, 59, 12, 33, 60, 14, 24, 60, 15, 15, + 60, 18, 6, 61, 20, -2, 61, 23, -11, 62, 27, -20, + 62, 30, -28, 63, 34, -36, 64, 37, -44, 65, 41, -52, + 60, 6, 64, 60, 6, 61, 60, 7, 56, 60, 7, 50, + 60, 8, 42, 60, 10, 34, 61, 11, 25, 61, 13, 16, + 61, 16, 7, 62, 18, -1, 62, 21, -10, 63, 24, -18, + 63, 28, -26, 64, 31, -35, 65, 35, -43, 66, 39, -50, + 61, 4, 65, 61, 4, 62, 61, 5, 57, 61, 5, 51, + 61, 6, 43, 61, 7, 35, 62, 9, 26, 62, 11, 18, + 62, 13, 9, 63, 16, 0, 63, 19, -8, 64, 22, -17, + 64, 26, -25, 65, 29, -33, 66, 33, -41, 66, 37, -49, + 62, 2, 65, 62, 2, 63, 62, 2, 58, 62, 3, 52, + 62, 4, 44, 62, 5, 36, 63, 7, 28, 63, 9, 19, + 63, 11, 10, 64, 14, 2, 64, 17, -7, 65, 20, -15, + 65, 23, -23, 66, 27, -32, 67, 31, -40, 67, 35, -48, + 63, 0, 66, 63, 0, 64, 63, 0, 59, 63, 1, 53, + 63, 2, 45, 64, 3, 38, 64, 5, 29, 64, 7, 21, + 64, 9, 12, 65, 12, 3, 65, 15, -6, 66, 18, -14, + 66, 21, -22, 67, 25, -30, 68, 28, -38, 68, 32, -46, + 64, -2, 67, 64, -2, 64, 64, -2, 60, 64, -1, 54, + 64, 0, 47, 65, 1, 39, 65, 3, 30, 65, 4, 22, + 65, 7, 13, 66, 9, 5, 66, 12, -4, 67, 15, -12, + 67, 19, -20, 68, 22, -29, 68, 26, -37, 69, 30, -45, + 65, -5, 68, 65, -4, 65, 65, -4, 61, 65, -3, 55, + 65, -2, 48, 66, -1, 40, 66, 0, 32, 66, 2, 23, + 66, 5, 15, 67, 7, 6, 67, 10, -3, 68, 13, -11, + 68, 17, -19, 69, 20, -27, 69, 24, -35, 70, 28, -43, + 66, -7, 68, 66, -6, 66, 66, -6, 62, 66, -5, 56, + 67, -4, 49, 67, -3, 42, 67, -1, 33, 67, 0, 25, + 68, 2, 16, 68, 5, 8, 68, 8, -1, 69, 11, -9, + 69, 14, -17, 70, 18, -26, 70, 22, -34, 71, 26, -42, + 67, -9, 69, 67, -9, 67, 67, -8, 63, 68, -7, 57, + 68, -6, 50, 68, -5, 43, 68, -3, 34, 68, -1, 26, + 69, 0, 17, 69, 3, 9, 69, 6, 0, 70, 9, -8, + 70, 12, -16, 71, 16, -24, 71, 19, -32, 72, 23, -40, + 68, -11, 70, 68, -11, 68, 69, -10, 64, 69, -9, 58, + 69, -8, 51, 69, -7, 44, 69, -5, 36, 69, -3, 28, + 70, -1, 19, 70, 1, 11, 70, 3, 2, 71, 7, -6, + 71, 10, -14, 72, 13, -23, 72, 17, -31, 73, 21, -39, + 70, -13, 71, 70, -13, 69, 70, -12, 65, 70, -11, 59, + 70, -10, 52, 70, -9, 45, 70, -7, 37, 70, -5, 29, + 71, -3, 20, 71, -1, 12, 71, 1, 3, 72, 4, -5, + 72, 8, -13, 73, 11, -21, 73, 15, -29, 74, 19, -37, + 71, -15, 72, 71, -15, 69, 71, -14, 66, 71, -13, 60, + 71, -12, 54, 71, -11, 47, 71, -9, 39, 71, -8, 31, + 72, -5, 22, 72, -3, 14, 72, 0, 5, 73, 2, -3, + 73, 6, -11, 74, 9, -20, 74, 13, -28, 75, 17, -36, + 72, -17, 72, 72, -17, 70, 72, -16, 67, 72, -16, 61, + 72, -14, 55, 72, -13, 48, 72, -12, 40, 73, -10, 32, + 73, -7, 23, 73, -5, 15, 74, -2, 6, 74, 0, -2, + 74, 3, -10, 75, 7, -18, 75, 11, -26, 76, 15, -34, + 73, -19, 73, 73, -19, 71, 73, -18, 68, 73, -18, 62, + 73, -17, 56, 73, -15, 49, 73, -14, 41, 74, -12, 33, + 74, -10, 25, 74, -7, 17, 75, -5, 8, 75, -2, 0, + 75, 1, -8, 76, 5, -17, 76, 8, -25, 77, 12, -33, + 74, -21, 74, 74, -21, 72, 74, -20, 69, 74, -20, 63, + 74, -19, 57, 74, -17, 50, 75, -16, 43, 75, -14, 35, + 75, -12, 26, 75, -9, 18, 76, -7, 10, 76, -4, 1, + 76, -1, -7, 77, 3, -15, 77, 6, -23, 78, 10, -31, + 75, -23, 75, 75, -23, 73, 75, -22, 70, 75, -22, 64, + 75, -21, 58, 75, -19, 52, 76, -18, 44, 76, -16, 36, + 76, -14, 28, 76, -11, 20, 77, -9, 11, 77, -6, 3, + 78, -3, -5, 78, 0, -13, 79, 4, -21, 79, 8, -29, + 76, -25, 76, 76, -25, 74, 76, -24, 71, 76, -23, 65, + 76, -23, 59, 77, -21, 53, 77, -20, 45, 77, -18, 38, + 77, -16, 29, 77, -13, 21, 78, -11, 13, 78, -8, 5, + 79, -5, -3, 79, -1, -12, 80, 2, -20, 80, 6, -28, + 77, -27, 77, 77, -27, 75, 77, -26, 72, 77, -25, 66, + 78, -24, 61, 78, -23, 54, 78, -22, 47, 78, -20, 39, + 78, -18, 31, 79, -15, 23, 79, -13, 14, 79, -10, 6, + 80, -7, -2, 80, -4, -10, 81, 0, -18, 81, 4, -26, + 78, -29, 77, 78, -29, 76, 79, -28, 73, 79, -27, 68, + 79, -26, 62, 79, -25, 55, 79, -24, 48, 79, -22, 40, + 79, -20, 32, 80, -17, 24, 80, -15, 16, 80, -12, 8, + 81, -9, 0, 81, -6, -9, 82, -2, -17, 82, 1, -25, + 80, -31, 78, 80, -30, 76, 80, -30, 74, 80, -29, 69, + 80, -28, 63, 80, -27, 57, 80, -26, 49, 80, -24, 42, + 81, -22, 34, 81, -19, 26, 81, -17, 17, 81, -14, 9, + 82, -11, 1, 82, -8, -7, 83, -4, -15, 83, -1, -23, + 81, -33, 79, 81, -33, 78, 81, -32, 75, 81, -32, 70, + 81, -31, 64, 81, -29, 58, 81, -28, 51, 82, -26, 43, + 82, -24, 35, 82, -22, 27, 82, -19, 19, 83, -17, 11, + 83, -14, 3, 84, -10, -5, 84, -7, -13, 85, -3, -21, + 82, -35, 80, 82, -35, 78, 82, -34, 76, 82, -33, 71, + 82, -33, 65, 82, -31, 59, 83, -30, 52, 83, -28, 45, + 83, -26, 37, 83, -24, 29, 84, -21, 21, 84, -19, 13, + 84, -16, 4, 85, -12, -4, 85, -9, -12, 86, -5, -20, + 83, -37, 81, 83, -36, 79, 83, -36, 77, 83, -35, 72, + 83, -34, 67, 84, -33, 60, 84, -32, 53, 84, -30, 46, + 84, -28, 38, 84, -26, 30, 85, -23, 22, 85, -21, 14, + 85, -18, 6, 86, -14, -2, 86, -11, -10, 87, -7, -18, + 84, -38, 82, 84, -38, 80, 84, -38, 78, 84, -37, 73, + 85, -36, 68, 85, -35, 62, 85, -34, 55, 85, -32, 48, + 85, -30, 40, 85, -28, 32, 86, -25, 24, 86, -23, 16, + 86, -20, 7, 87, -16, 0, 87, -13, -8, 88, -9, -16, + 85, -40, 83, 86, -40, 81, 86, -40, 79, 86, -39, 74, + 86, -38, 69, 86, -37, 63, 86, -35, 56, 86, -34, 49, + 86, -32, 41, 87, -30, 33, 87, -27, 25, 87, -25, 17, + 87, -22, 9, 88, -18, 1, 88, -15, -7, 89, -11, -15, + 87, -42, 84, 87, -42, 82, 87, -41, 80, 87, -41, 75, + 87, -40, 70, 87, -39, 64, 87, -37, 57, 87, -36, 50, + 87, -34, 42, 88, -31, 35, 88, -29, 27, 88, -26, 19, + 89, -24, 10, 89, -20, 3, 89, -17, -5, 90, -13, -13, + 88, -44, 85, 88, -43, 83, 88, -43, 80, 88, -42, 76, + 88, -42, 71, 88, -40, 65, 88, -39, 58, 88, -37, 52, + 89, -35, 44, 89, -33, 36, 89, -31, 28, 89, -28, 20, + 90, -26, 12, 90, -22, 4, 90, -19, -4, 91, -15, -12, + 89, -45, 85, 89, -45, 84, 89, -45, 81, 89, -44, 77, + 89, -43, 72, 89, -42, 66, 89, -41, 60, 89, -39, 53, + 90, -37, 45, 90, -35, 38, 90, -33, 30, 90, -30, 22, + 91, -27, 14, 91, -24, 6, 92, -21, -2, 92, -17, -10, + 90, -47, 86, 90, -47, 85, 90, -46, 82, 90, -46, 78, + 90, -45, 73, 90, -44, 68, 90, -43, 61, 91, -41, 54, + 91, -39, 47, 91, -37, 39, 91, -35, 31, 92, -32, 23, + 92, -29, 15, 92, -26, 7, 93, -23, 0, 93, -19, -8, + 91, -49, 87, 91, -49, 86, 91, -48, 83, 91, -48, 80, + 91, -47, 75, 91, -46, 69, 92, -44, 62, 92, -43, 55, + 92, -41, 48, 92, -39, 41, 92, -36, 32, 93, -34, 25, + 93, -31, 17, 93, -28, 9, 94, -25, 1, 94, -21, -7, + 92, -50, 88, 92, -50, 86, 92, -50, 84, 92, -49, 81, + 92, -48, 76, 93, -47, 70, 93, -46, 63, 93, -44, 57, + 93, -42, 49, 93, -40, 42, 93, -38, 34, 94, -36, 26, + 94, -33, 18, 94, -30, 10, 95, -27, 2, 95, -23, -5, + 39, 64, 52, 39, 64, 46, 39, 64, 37, 40, 65, 27, + 40, 66, 17, 40, 67, 7, 41, 68, -2, 41, 69, -11, + 42, 71, -21, 43, 73, -30, 44, 75, -39, 44, 77, -47, + 45, 80, -54, 47, 83, -62, 48, 85, -70, 49, 88, -77, + 39, 64, 52, 39, 64, 46, 40, 64, 37, 40, 65, 27, + 40, 65, 17, 40, 66, 7, 41, 68, -2, 41, 69, -11, + 42, 71, -21, 43, 73, -29, 44, 75, -38, 45, 77, -46, + 46, 80, -54, 47, 82, -62, 48, 85, -70, 49, 88, -77, + 40, 63, 52, 40, 63, 46, 40, 64, 37, 40, 64, 27, + 40, 65, 17, 41, 66, 8, 41, 67, -2, 42, 69, -11, + 42, 70, -21, 43, 72, -29, 44, 74, -38, 45, 77, -46, + 46, 79, -54, 47, 82, -62, 48, 85, -69, 49, 88, -76, + 40, 63, 52, 40, 63, 46, 40, 63, 37, 40, 64, 28, + 40, 64, 17, 41, 65, 8, 41, 67, -2, 42, 68, -11, + 42, 70, -20, 43, 72, -29, 44, 74, -38, 45, 76, -46, + 46, 79, -54, 47, 82, -62, 48, 84, -69, 49, 87, -76, + 40, 62, 52, 40, 62, 46, 40, 63, 37, 40, 63, 28, + 41, 64, 18, 41, 65, 8, 41, 66, -1, 42, 68, -11, + 43, 69, -20, 43, 71, -29, 44, 74, -38, 45, 76, -46, + 46, 79, -54, 47, 81, -61, 48, 84, -69, 49, 87, -76, + 40, 62, 52, 40, 62, 46, 40, 62, 38, 40, 63, 28, + 41, 63, 18, 41, 64, 8, 42, 66, -1, 42, 67, -10, + 43, 69, -20, 43, 71, -29, 44, 73, -37, 45, 75, -45, + 46, 78, -53, 47, 81, -61, 48, 84, -69, 50, 86, -76, + 40, 61, 52, 40, 61, 46, 41, 61, 38, 41, 62, 28, + 41, 63, 18, 41, 64, 9, 42, 65, -1, 42, 66, -10, + 43, 68, -19, 44, 70, -28, 45, 72, -37, 45, 75, -45, + 46, 77, -53, 47, 80, -61, 48, 83, -68, 50, 86, -76, + 41, 60, 52, 41, 60, 47, 41, 61, 38, 41, 61, 29, + 41, 62, 19, 42, 63, 9, 42, 64, 0, 43, 66, -10, + 43, 67, -19, 44, 69, -28, 45, 72, -37, 46, 74, -45, + 47, 77, -53, 48, 79, -60, 49, 82, -68, 50, 85, -75, + 41, 59, 53, 41, 59, 47, 41, 60, 39, 41, 60, 29, + 42, 61, 19, 42, 62, 9, 42, 63, 0, 43, 65, -9, + 44, 67, -19, 44, 69, -27, 45, 71, -36, 46, 73, -44, + 47, 76, -52, 48, 79, -60, 49, 82, -68, 50, 84, -75, + 41, 58, 53, 41, 58, 47, 42, 59, 39, 42, 59, 29, + 42, 60, 19, 42, 61, 10, 43, 62, 0, 43, 64, -9, + 44, 66, -18, 45, 68, -27, 45, 70, -36, 46, 72, -44, + 47, 75, -52, 48, 78, -60, 49, 81, -67, 50, 84, -74, + 42, 57, 53, 42, 57, 47, 42, 57, 39, 42, 58, 30, + 42, 59, 20, 43, 60, 10, 43, 61, 1, 44, 63, -8, + 44, 65, -18, 45, 67, -26, 46, 69, -35, 47, 71, -43, + 47, 74, -51, 49, 77, -59, 50, 80, -67, 51, 83, -74, + 42, 55, 53, 42, 56, 48, 42, 56, 40, 43, 56, 30, + 43, 57, 21, 43, 58, 11, 44, 60, 1, 44, 61, -7, + 45, 63, -17, 45, 65, -25, 46, 68, -34, 47, 70, -43, + 48, 73, -50, 49, 76, -58, 50, 79, -66, 51, 82, -73, + 43, 54, 54, 43, 54, 48, 43, 55, 40, 43, 55, 31, + 43, 56, 21, 44, 57, 12, 44, 58, 2, 45, 60, -7, + 45, 62, -16, 46, 64, -25, 47, 66, -34, 47, 69, -42, + 48, 72, -50, 49, 74, -58, 50, 78, -65, 52, 81, -73, + 43, 53, 54, 43, 53, 48, 43, 53, 41, 44, 54, 32, + 44, 55, 22, 44, 56, 13, 45, 57, 3, 45, 59, -6, + 46, 60, -15, 46, 63, -24, 47, 65, -33, 48, 68, -41, + 49, 70, -49, 50, 73, -57, 51, 76, -65, 52, 79, -72, + 44, 51, 54, 44, 51, 49, 44, 52, 42, 44, 52, 32, + 44, 53, 23, 45, 54, 13, 45, 56, 4, 46, 57, -5, + 46, 59, -15, 47, 61, -23, 48, 64, -32, 48, 66, -40, + 49, 69, -48, 50, 72, -56, 51, 75, -64, 52, 78, -71, + 44, 49, 55, 45, 50, 49, 45, 50, 42, 45, 51, 33, + 45, 51, 24, 45, 53, 14, 46, 54, 4, 46, 56, -4, + 47, 58, -14, 47, 60, -22, 48, 62, -31, 49, 65, -40, + 50, 68, -48, 51, 71, -56, 52, 74, -63, 53, 77, -71, + 45, 48, 55, 45, 48, 50, 45, 48, 43, 45, 49, 34, + 46, 50, 24, 46, 51, 15, 46, 52, 5, 47, 54, -3, + 47, 56, -13, 48, 58, -22, 49, 61, -31, 49, 63, -39, + 50, 66, -47, 51, 69, -55, 52, 72, -62, 53, 76, -70, + 46, 46, 55, 46, 46, 50, 46, 47, 43, 46, 47, 34, + 46, 48, 25, 47, 49, 16, 47, 51, 6, 47, 52, -2, + 48, 54, -12, 49, 57, -21, 49, 59, -30, 50, 62, -38, + 51, 65, -46, 52, 68, -54, 53, 71, -62, 54, 74, -69, + 46, 44, 56, 46, 44, 51, 47, 45, 44, 47, 45, 35, + 47, 46, 26, 47, 47, 17, 48, 49, 7, 48, 50, -2, + 49, 53, -11, 49, 55, -20, 50, 57, -29, 51, 60, -37, + 51, 63, -45, 52, 66, -53, 53, 69, -61, 54, 73, -68, + 47, 42, 56, 47, 42, 51, 47, 43, 45, 47, 43, 36, + 48, 44, 27, 48, 46, 18, 48, 47, 8, 49, 49, -1, + 49, 51, -10, 50, 53, -19, 51, 56, -28, 51, 58, -36, + 52, 61, -44, 53, 64, -52, 54, 68, -60, 55, 71, -67, + 48, 40, 57, 48, 41, 52, 48, 41, 45, 48, 42, 37, + 48, 42, 28, 49, 44, 19, 49, 45, 9, 49, 47, 0, + 50, 49, -9, 51, 51, -18, 51, 54, -27, 52, 57, -35, + 53, 60, -43, 54, 63, -51, 54, 66, -59, 56, 69, -66, + 49, 38, 57, 49, 39, 52, 49, 39, 46, 49, 40, 38, + 49, 41, 29, 49, 42, 20, 50, 43, 10, 50, 45, 1, + 51, 47, -8, 51, 49, -17, 52, 52, -26, 53, 55, -34, + 53, 58, -42, 54, 61, -50, 55, 64, -58, 56, 68, -65, + 49, 36, 58, 49, 37, 53, 50, 37, 47, 50, 38, 39, + 50, 38, 30, 50, 40, 21, 51, 41, 11, 51, 43, 2, + 51, 45, -7, 52, 47, -15, 53, 50, -24, 53, 53, -33, + 54, 56, -41, 55, 59, -49, 56, 63, -57, 57, 66, -64, + 50, 34, 58, 50, 34, 54, 50, 35, 48, 51, 36, 39, + 51, 36, 31, 51, 38, 22, 51, 39, 12, 52, 41, 3, + 52, 43, -6, 53, 45, -14, 53, 48, -23, 54, 51, -32, + 55, 54, -40, 56, 57, -48, 56, 61, -56, 57, 64, -63, + 51, 32, 59, 51, 32, 54, 51, 33, 48, 51, 33, 40, + 52, 34, 32, 52, 36, 23, 52, 37, 13, 53, 39, 5, + 53, 41, -4, 53, 43, -13, 54, 46, -22, 55, 49, -30, + 55, 52, -38, 56, 55, -47, 57, 59, -55, 58, 62, -62, + 52, 30, 59, 52, 30, 55, 52, 31, 49, 52, 31, 41, + 52, 32, 33, 53, 33, 24, 53, 35, 15, 53, 37, 6, + 54, 39, -3, 54, 41, -12, 55, 44, -21, 56, 47, -29, + 56, 50, -37, 57, 53, -46, 58, 57, -53, 59, 60, -61, + 53, 28, 60, 53, 28, 56, 53, 29, 50, 53, 29, 42, + 53, 30, 34, 54, 31, 25, 54, 33, 16, 54, 35, 7, + 55, 37, -2, 55, 39, -11, 56, 42, -20, 56, 45, -28, + 57, 48, -36, 58, 51, -44, 59, 55, -52, 60, 58, -60, + 54, 26, 60, 54, 26, 56, 54, 26, 51, 54, 27, 43, + 54, 28, 35, 54, 29, 26, 55, 31, 17, 55, 32, 8, + 56, 35, -1, 56, 37, -9, 57, 40, -18, 57, 43, -27, + 58, 46, -35, 59, 49, -43, 59, 53, -51, 60, 56, -59, + 55, 24, 61, 55, 24, 57, 55, 24, 51, 55, 25, 44, + 55, 26, 36, 55, 27, 28, 56, 28, 18, 56, 30, 9, + 56, 33, 0, 57, 35, -8, 57, 38, -17, 58, 41, -25, + 59, 44, -34, 59, 47, -42, 60, 51, -50, 61, 54, -57, + 56, 21, 61, 56, 22, 58, 56, 22, 52, 56, 23, 45, + 56, 24, 37, 56, 25, 29, 57, 26, 19, 57, 28, 11, + 57, 30, 1, 58, 33, -7, 58, 36, -16, 59, 39, -24, + 60, 42, -32, 60, 45, -41, 61, 49, -48, 62, 52, -56, + 57, 19, 62, 57, 19, 59, 57, 20, 53, 57, 20, 46, + 57, 21, 38, 57, 23, 30, 57, 24, 21, 58, 26, 12, + 58, 28, 3, 59, 31, -5, 59, 33, -14, 60, 36, -23, + 60, 40, -31, 61, 43, -39, 62, 47, -47, 63, 50, -55, + 58, 17, 63, 58, 17, 60, 58, 18, 54, 58, 18, 48, + 58, 19, 40, 58, 20, 31, 58, 22, 22, 59, 24, 13, + 59, 26, 4, 60, 28, -4, 60, 31, -13, 61, 34, -21, + 61, 38, -30, 62, 41, -38, 63, 45, -46, 64, 48, -54, + 59, 14, 63, 59, 14, 61, 59, 15, 55, 59, 15, 49, + 59, 16, 41, 59, 18, 33, 60, 19, 24, 60, 21, 15, + 60, 23, 6, 61, 26, -2, 61, 28, -11, 62, 31, -20, + 62, 35, -28, 63, 38, -36, 64, 42, -44, 65, 45, -52, + 60, 12, 64, 60, 12, 61, 60, 13, 56, 60, 13, 50, + 60, 14, 42, 60, 15, 34, 61, 17, 25, 61, 19, 16, + 61, 21, 7, 62, 23, -1, 62, 26, -10, 63, 29, -18, + 63, 33, -26, 64, 36, -35, 65, 40, -43, 65, 43, -51, + 61, 10, 65, 61, 10, 62, 61, 10, 57, 61, 11, 51, + 61, 12, 43, 61, 13, 35, 62, 15, 26, 62, 16, 18, + 62, 19, 9, 63, 21, 0, 63, 24, -9, 64, 27, -17, + 64, 30, -25, 65, 34, -33, 66, 37, -41, 66, 41, -49, + 62, 8, 66, 62, 8, 63, 62, 8, 58, 62, 9, 52, + 62, 10, 44, 62, 11, 36, 63, 12, 28, 63, 14, 19, + 63, 17, 10, 64, 19, 2, 64, 22, -7, 65, 25, -15, + 65, 28, -24, 66, 32, -32, 66, 35, -40, 67, 39, -48, + 63, 5, 66, 63, 6, 64, 63, 6, 59, 63, 7, 53, + 63, 8, 45, 63, 9, 38, 64, 10, 29, 64, 12, 20, + 64, 14, 11, 65, 17, 3, 65, 20, -6, 66, 23, -14, + 66, 26, -22, 67, 29, -31, 67, 33, -38, 68, 37, -46, + 64, 3, 67, 64, 3, 65, 64, 4, 60, 64, 4, 54, + 64, 5, 47, 64, 7, 39, 65, 8, 30, 65, 10, 22, + 65, 12, 13, 66, 15, 4, 66, 17, -4, 66, 20, -13, + 67, 24, -21, 68, 27, -29, 68, 31, -37, 69, 35, -45, + 65, 1, 68, 65, 1, 65, 65, 2, 61, 65, 2, 55, + 65, 3, 48, 65, 4, 40, 66, 6, 32, 66, 8, 23, + 66, 10, 14, 67, 12, 6, 67, 15, -3, 67, 18, -11, + 68, 21, -19, 69, 25, -28, 69, 29, -36, 70, 32, -44, + 66, -1, 68, 66, -1, 66, 66, 0, 62, 66, 0, 56, + 66, 1, 49, 66, 2, 41, 67, 4, 33, 67, 5, 25, + 67, 8, 16, 68, 10, 7, 68, 13, -1, 68, 16, -10, + 69, 19, -18, 70, 23, -26, 70, 26, -34, 71, 30, -42, + 67, -3, 69, 67, -3, 67, 67, -2, 63, 67, -2, 57, + 67, -1, 50, 67, 0, 43, 68, 2, 34, 68, 3, 26, + 68, 5, 17, 69, 8, 9, 69, 11, 0, 69, 14, -8, + 70, 17, -16, 71, 20, -25, 71, 24, -33, 72, 28, -41, + 68, -5, 70, 68, -5, 68, 68, -4, 64, 68, -4, 58, + 68, -3, 51, 69, -1, 44, 69, 0, 36, 69, 1, 27, + 69, 3, 19, 70, 6, 10, 70, 9, 1, 70, 11, -7, + 71, 15, -15, 72, 18, -23, 72, 22, -31, 73, 26, -39, + 69, -7, 71, 69, -7, 69, 69, -7, 65, 69, -6, 59, + 69, -5, 52, 70, -4, 45, 70, -2, 37, 70, 0, 29, + 70, 1, 20, 71, 4, 12, 71, 6, 3, 71, 9, -5, + 72, 13, -13, 73, 16, -22, 73, 20, -30, 74, 23, -38, + 70, -9, 72, 70, -9, 69, 70, -9, 66, 70, -8, 60, + 71, -7, 53, 71, -6, 46, 71, -4, 38, 71, -2, 30, + 71, 0, 21, 72, 1, 13, 72, 4, 4, 73, 7, -4, + 73, 10, -12, 74, 14, -20, 74, 18, -28, 75, 21, -36, + 71, -12, 72, 71, -11, 70, 71, -11, 67, 71, -10, 61, + 72, -9, 55, 72, -8, 48, 72, -6, 40, 72, -4, 32, + 72, -2, 23, 73, 0, 15, 73, 2, 6, 74, 5, -2, + 74, 8, -10, 75, 12, -19, 75, 15, -27, 76, 19, -35, + 72, -14, 73, 72, -13, 71, 72, -13, 68, 73, -12, 62, + 73, -11, 56, 73, -10, 49, 73, -8, 41, 73, -6, 33, + 74, -4, 24, 74, -2, 16, 74, 0, 7, 75, 3, -1, + 75, 6, -9, 76, 9, -17, 76, 13, -25, 77, 17, -33, + 74, -16, 74, 74, -15, 72, 74, -15, 69, 74, -14, 63, + 74, -13, 57, 74, -12, 50, 74, -10, 42, 74, -9, 34, + 75, -6, 26, 75, -4, 18, 75, -2, 9, 76, 1, 1, + 76, 4, -7, 77, 7, -16, 77, 11, -24, 78, 15, -32, + 75, -18, 75, 75, -17, 73, 75, -17, 70, 75, -16, 64, + 75, -15, 58, 75, -14, 51, 75, -12, 43, 75, -11, 36, + 76, -9, 27, 76, -6, 19, 76, -4, 10, 77, -1, 2, + 77, 2, -6, 78, 5, -14, 78, 9, -22, 79, 12, -30, + 76, -20, 76, 76, -19, 74, 76, -19, 71, 76, -18, 65, + 76, -17, 59, 76, -16, 52, 76, -14, 45, 76, -13, 37, + 77, -11, 29, 77, -8, 21, 77, -6, 12, 78, -3, 4, + 78, 0, -4, 79, 3, -13, 79, 7, -20, 80, 10, -29, + 77, -22, 76, 77, -21, 75, 77, -21, 71, 77, -20, 66, + 77, -19, 60, 77, -18, 54, 77, -16, 46, 78, -15, 38, + 78, -13, 30, 78, -10, 22, 78, -8, 13, 79, -5, 5, + 79, -2, -3, 80, 1, -11, 80, 4, -19, 81, 8, -27, + 78, -24, 77, 78, -23, 75, 78, -23, 72, 78, -22, 67, + 78, -21, 61, 78, -20, 55, 78, -18, 47, 79, -17, 40, + 79, -15, 32, 79, -13, 24, 79, -10, 15, 80, -7, 7, + 80, -4, -1, 81, -1, -9, 81, 2, -17, 82, 6, -25, + 79, -25, 78, 79, -25, 76, 79, -25, 73, 79, -24, 68, + 79, -23, 62, 79, -22, 56, 80, -20, 49, 80, -19, 41, + 80, -17, 33, 80, -15, 25, 81, -12, 16, 81, -9, 8, + 81, -6, 0, 82, -3, -8, 82, 0, -16, 83, 4, -24, + 80, -27, 79, 80, -27, 77, 80, -27, 74, 80, -26, 69, + 80, -25, 64, 80, -24, 57, 81, -22, 50, 81, -21, 43, + 81, -19, 34, 81, -17, 26, 82, -14, 18, 82, -11, 10, + 82, -9, 2, 83, -5, -6, 83, -2, -14, 84, 2, -22, + 82, -30, 80, 82, -29, 78, 82, -29, 75, 82, -28, 71, + 82, -27, 65, 82, -26, 59, 82, -25, 52, 82, -23, 44, + 82, -21, 36, 83, -19, 28, 83, -17, 20, 83, -14, 12, + 84, -11, 4, 84, -8, -4, 85, -5, -12, 85, -1, -20, + 83, -32, 81, 83, -31, 79, 83, -31, 76, 83, -30, 72, + 83, -29, 66, 83, -28, 60, 83, -27, 53, 83, -25, 46, + 84, -23, 38, 84, -21, 30, 84, -19, 21, 84, -16, 13, + 85, -13, 5, 85, -10, -3, 86, -7, -11, 86, -3, -19, + 84, -33, 82, 84, -33, 80, 84, -33, 77, 84, -32, 73, + 84, -31, 67, 84, -30, 61, 84, -29, 54, 84, -27, 47, + 85, -25, 39, 85, -23, 31, 85, -20, 23, 85, -18, 15, + 86, -15, 7, 86, -12, -1, 87, -9, -9, 87, -5, -17, + 85, -35, 83, 85, -35, 81, 85, -35, 78, 85, -34, 74, + 85, -33, 68, 85, -32, 62, 85, -31, 55, 86, -29, 48, + 86, -27, 40, 86, -25, 33, 86, -22, 24, 87, -20, 16, + 87, -17, 8, 87, -14, 0, 88, -11, -8, 88, -7, -16, + 86, -37, 83, 86, -37, 82, 86, -36, 79, 86, -36, 75, + 86, -35, 70, 86, -34, 64, 86, -32, 57, 87, -31, 50, + 87, -29, 42, 87, -27, 34, 87, -24, 26, 88, -22, 18, + 88, -19, 10, 88, -16, 2, 89, -13, -6, 89, -9, -14, + 87, -39, 84, 87, -39, 83, 87, -38, 80, 87, -38, 76, + 87, -37, 71, 87, -36, 65, 88, -34, 58, 88, -33, 51, + 88, -31, 43, 88, -29, 35, 88, -26, 27, 89, -24, 19, + 89, -21, 11, 89, -18, 3, 90, -15, -4, 90, -11, -13, + 88, -41, 85, 88, -40, 83, 88, -40, 81, 88, -39, 77, + 88, -39, 72, 89, -37, 66, 89, -36, 59, 89, -34, 52, + 89, -33, 44, 89, -31, 37, 90, -28, 29, 90, -26, 21, + 90, -23, 13, 91, -20, 5, 91, -17, -3, 91, -13, -11, + 89, -42, 86, 89, -42, 84, 89, -42, 82, 89, -41, 78, + 90, -40, 73, 90, -39, 67, 90, -38, 60, 90, -36, 54, + 90, -34, 46, 90, -32, 38, 91, -30, 30, 91, -28, 22, + 91, -25, 14, 92, -22, 6, 92, -19, -1, 92, -15, -9, + 90, -44, 87, 90, -44, 85, 91, -44, 83, 91, -43, 79, + 91, -42, 74, 91, -41, 68, 91, -40, 62, 91, -38, 55, + 91, -36, 47, 91, -34, 40, 92, -32, 32, 92, -29, 24, + 92, -27, 16, 93, -24, 8, 93, -21, 0, 93, -17, -8, + 92, -46, 88, 92, -46, 86, 92, -45, 84, 92, -45, 80, + 92, -44, 75, 92, -43, 69, 92, -41, 63, 92, -40, 56, + 92, -38, 49, 93, -36, 41, 93, -34, 33, 93, -31, 25, + 93, -29, 17, 94, -26, 9, 94, -23, 1, 95, -19, -6, + 93, -48, 89, 93, -47, 87, 93, -47, 85, 93, -46, 81, + 93, -46, 76, 93, -44, 71, 93, -43, 64, 93, -42, 57, + 93, -40, 50, 94, -38, 43, 94, -36, 35, 94, -33, 27, + 94, -31, 19, 95, -28, 11, 95, -25, 3, 96, -21, -5, + 41, 66, 54, 41, 66, 48, 41, 66, 39, 41, 67, 29, + 42, 68, 19, 42, 69, 10, 42, 70, 0, 43, 71, -9, + 44, 73, -18, 44, 74, -27, 45, 77, -36, 46, 79, -44, + 47, 81, -52, 48, 84, -60, 49, 86, -68, 50, 89, -75, + 41, 66, 54, 41, 66, 48, 41, 66, 39, 41, 67, 30, + 42, 67, 20, 42, 68, 10, 43, 69, 0, 43, 71, -9, + 44, 72, -18, 44, 74, -27, 45, 76, -36, 46, 78, -44, + 47, 81, -52, 48, 83, -60, 49, 86, -67, 50, 89, -75, + 41, 65, 54, 41, 65, 48, 41, 66, 39, 42, 66, 30, + 42, 67, 20, 42, 68, 10, 43, 69, 0, 43, 70, -8, + 44, 72, -18, 44, 74, -27, 45, 76, -36, 46, 78, -44, + 47, 81, -52, 48, 83, -60, 49, 86, -67, 50, 89, -74, + 41, 65, 54, 41, 65, 48, 42, 65, 40, 42, 66, 30, + 42, 66, 20, 42, 67, 10, 43, 69, 0, 43, 70, -8, + 44, 72, -18, 45, 73, -27, 45, 76, -36, 46, 78, -44, + 47, 80, -52, 48, 83, -59, 49, 86, -67, 51, 88, -74, + 42, 64, 54, 42, 64, 48, 42, 65, 40, 42, 65, 30, + 42, 66, 20, 43, 67, 11, 43, 68, 1, 43, 69, -8, + 44, 71, -18, 45, 73, -26, 46, 75, -35, 46, 77, -43, + 47, 80, -51, 48, 82, -59, 49, 85, -67, 51, 88, -74, + 42, 64, 54, 42, 64, 48, 42, 64, 40, 42, 65, 30, + 42, 65, 20, 43, 66, 11, 43, 68, 1, 44, 69, -8, + 44, 71, -17, 45, 72, -26, 46, 75, -35, 47, 77, -43, + 47, 79, -51, 49, 82, -59, 50, 85, -67, 51, 87, -74, + 42, 63, 54, 42, 63, 48, 42, 63, 40, 42, 64, 31, + 43, 65, 21, 43, 66, 11, 43, 67, 1, 44, 68, -7, + 45, 70, -17, 45, 72, -26, 46, 74, -35, 47, 76, -43, + 48, 79, -51, 49, 81, -59, 50, 84, -66, 51, 87, -73, + 42, 62, 54, 42, 62, 49, 42, 63, 40, 43, 63, 31, + 43, 64, 21, 43, 65, 11, 44, 66, 2, 44, 68, -7, + 45, 69, -17, 45, 71, -25, 46, 73, -34, 47, 76, -42, + 48, 78, -50, 49, 81, -58, 50, 84, -66, 51, 86, -73, + 43, 61, 54, 43, 62, 49, 43, 62, 41, 43, 62, 31, + 43, 63, 21, 44, 64, 12, 44, 65, 2, 44, 67, -7, + 45, 68, -16, 46, 70, -25, 46, 72, -34, 47, 75, -42, + 48, 77, -50, 49, 80, -58, 50, 83, -66, 51, 86, -73, + 43, 60, 55, 43, 61, 49, 43, 61, 41, 43, 61, 32, + 44, 62, 22, 44, 63, 12, 44, 64, 2, 45, 66, -6, + 45, 68, -16, 46, 69, -24, 47, 72, -33, 48, 74, -42, + 48, 77, -50, 49, 79, -57, 51, 82, -65, 52, 85, -72, + 43, 59, 55, 43, 59, 49, 44, 60, 42, 44, 60, 32, + 44, 61, 22, 44, 62, 13, 45, 63, 3, 45, 65, -6, + 46, 67, -15, 46, 68, -24, 47, 71, -33, 48, 73, -41, + 49, 76, -49, 50, 78, -57, 51, 81, -65, 52, 84, -72, + 44, 58, 55, 44, 58, 50, 44, 58, 42, 44, 59, 33, + 44, 60, 23, 45, 61, 14, 45, 62, 4, 46, 63, -5, + 46, 65, -14, 47, 67, -23, 48, 69, -32, 48, 72, -40, + 49, 74, -48, 50, 77, -56, 51, 80, -64, 52, 83, -71, + 44, 57, 55, 44, 57, 50, 44, 57, 43, 45, 58, 33, + 45, 58, 24, 45, 59, 14, 46, 61, 4, 46, 62, -4, + 47, 64, -14, 47, 66, -23, 48, 68, -32, 49, 71, -40, + 50, 73, -48, 51, 76, -56, 52, 79, -63, 53, 82, -71, + 45, 55, 56, 45, 55, 50, 45, 56, 43, 45, 56, 34, + 45, 57, 24, 46, 58, 15, 46, 59, 5, 47, 61, -4, + 47, 63, -13, 48, 65, -22, 48, 67, -31, 49, 69, -39, + 50, 72, -47, 51, 75, -55, 52, 78, -63, 53, 81, -70, + 45, 54, 56, 45, 54, 51, 46, 54, 44, 46, 55, 34, + 46, 56, 25, 46, 57, 16, 47, 58, 6, 47, 59, -3, + 48, 61, -12, 48, 63, -21, 49, 66, -30, 50, 68, -38, + 50, 71, -46, 51, 74, -54, 52, 77, -62, 54, 80, -69, + 46, 52, 56, 46, 52, 51, 46, 53, 44, 46, 53, 35, + 46, 54, 26, 47, 55, 16, 47, 56, 7, 48, 58, -2, + 48, 60, -12, 49, 62, -20, 49, 64, -29, 50, 67, -38, + 51, 69, -46, 52, 72, -54, 53, 75, -61, 54, 78, -69, + 47, 51, 57, 47, 51, 51, 47, 51, 45, 47, 52, 36, + 47, 52, 26, 47, 53, 17, 48, 55, 7, 48, 56, -1, + 49, 58, -11, 49, 60, -19, 50, 63, -28, 51, 65, -37, + 51, 68, -45, 52, 71, -53, 53, 74, -61, 54, 77, -68, + 47, 49, 57, 47, 49, 52, 47, 49, 45, 47, 50, 36, + 48, 51, 27, 48, 52, 18, 48, 53, 8, 49, 55, 0, + 49, 57, -10, 50, 59, -19, 51, 61, -28, 51, 64, -36, + 52, 67, -44, 53, 69, -52, 54, 73, -60, 55, 76, -67, + 48, 47, 57, 48, 47, 52, 48, 48, 46, 48, 48, 37, + 48, 49, 28, 49, 50, 19, 49, 51, 9, 49, 53, 0, + 50, 55, -9, 50, 57, -18, 51, 60, -27, 52, 62, -35, + 53, 65, -43, 53, 68, -51, 54, 71, -59, 55, 74, -66, + 48, 45, 58, 49, 45, 53, 49, 46, 46, 49, 46, 38, + 49, 47, 29, 49, 48, 20, 50, 50, 10, 50, 51, 1, + 51, 53, -8, 51, 55, -17, 52, 58, -26, 52, 60, -34, + 53, 63, -42, 54, 66, -50, 55, 69, -58, 56, 73, -65, + 49, 43, 58, 49, 44, 53, 49, 44, 47, 49, 45, 39, + 50, 45, 30, 50, 46, 21, 50, 48, 11, 51, 49, 2, + 51, 51, -7, 52, 54, -16, 52, 56, -25, 53, 59, -33, + 54, 62, -41, 55, 65, -49, 56, 68, -57, 57, 71, -65, + 50, 41, 59, 50, 42, 54, 50, 42, 48, 50, 43, 39, + 50, 43, 31, 51, 45, 22, 51, 46, 12, 51, 48, 3, + 52, 50, -6, 52, 52, -15, 53, 54, -24, 54, 57, -32, + 54, 60, -40, 55, 63, -48, 56, 66, -56, 57, 69, -64, + 51, 40, 59, 51, 40, 54, 51, 40, 48, 51, 41, 40, + 51, 42, 32, 51, 43, 23, 52, 44, 13, 52, 46, 4, + 53, 48, -5, 53, 50, -14, 54, 52, -23, 54, 55, -31, + 55, 58, -39, 56, 61, -47, 57, 64, -55, 58, 68, -63, + 51, 38, 59, 52, 38, 55, 52, 38, 49, 52, 39, 41, + 52, 40, 33, 52, 41, 24, 53, 42, 14, 53, 44, 5, + 53, 46, -4, 54, 48, -12, 54, 51, -21, 55, 53, -30, + 56, 56, -38, 57, 59, -46, 57, 63, -54, 58, 66, -62, + 52, 35, 60, 52, 36, 56, 52, 36, 50, 53, 37, 42, + 53, 38, 34, 53, 39, 25, 53, 40, 15, 54, 42, 6, + 54, 44, -3, 55, 46, -11, 55, 49, -20, 56, 51, -29, + 57, 54, -37, 57, 57, -45, 58, 61, -53, 59, 64, -61, + 53, 33, 60, 53, 34, 56, 53, 34, 51, 53, 35, 43, + 54, 35, 35, 54, 37, 26, 54, 38, 16, 54, 40, 7, + 55, 42, -1, 55, 44, -10, 56, 47, -19, 57, 49, -27, + 57, 52, -36, 58, 55, -44, 59, 59, -52, 60, 62, -59, + 54, 31, 61, 54, 31, 57, 54, 32, 51, 54, 32, 44, + 54, 33, 36, 55, 34, 27, 55, 36, 17, 55, 38, 9, + 56, 40, 0, 56, 42, -9, 57, 45, -18, 57, 47, -26, + 58, 50, -34, 59, 53, -43, 60, 57, -51, 61, 60, -58, + 55, 29, 61, 55, 29, 58, 55, 30, 52, 55, 30, 45, + 55, 31, 37, 55, 32, 28, 56, 34, 19, 56, 35, 10, + 57, 38, 1, 57, 40, -8, 58, 42, -17, 58, 45, -25, + 59, 48, -33, 60, 51, -42, 60, 55, -49, 61, 58, -57, + 56, 27, 62, 56, 27, 59, 56, 28, 53, 56, 28, 46, + 56, 29, 38, 56, 30, 29, 57, 32, 20, 57, 33, 11, + 57, 35, 2, 58, 38, -6, 58, 40, -16, 59, 43, -24, + 60, 46, -32, 60, 49, -40, 61, 53, -48, 62, 56, -56, + 57, 25, 63, 57, 25, 59, 57, 25, 54, 57, 26, 47, + 57, 27, 39, 57, 28, 30, 58, 29, 21, 58, 31, 12, + 58, 33, 3, 59, 36, -5, 59, 38, -14, 60, 41, -23, + 60, 44, -31, 61, 47, -39, 62, 51, -47, 63, 54, -55, + 58, 23, 63, 58, 23, 60, 58, 23, 54, 58, 24, 48, + 58, 25, 40, 58, 26, 31, 58, 27, 22, 59, 29, 14, + 59, 31, 4, 60, 33, -4, 60, 36, -13, 61, 39, -21, + 61, 42, -29, 62, 45, -38, 63, 49, -46, 64, 52, -53, + 59, 20, 64, 59, 21, 61, 59, 21, 55, 59, 22, 49, + 59, 23, 41, 59, 24, 33, 59, 25, 23, 60, 27, 15, + 60, 29, 6, 60, 31, -3, 61, 34, -12, 62, 37, -20, + 62, 40, -28, 63, 43, -36, 64, 47, -44, 64, 50, -52, + 60, 18, 65, 60, 18, 62, 60, 18, 56, 60, 19, 50, + 60, 20, 42, 60, 21, 34, 61, 22, 25, 61, 24, 16, + 61, 26, 7, 62, 29, -1, 62, 31, -10, 63, 34, -18, + 63, 37, -26, 64, 40, -35, 65, 44, -43, 65, 48, -51, + 61, 16, 65, 61, 16, 63, 61, 16, 57, 61, 17, 51, + 61, 18, 43, 61, 19, 35, 61, 20, 26, 62, 22, 18, + 62, 24, 9, 63, 26, 0, 63, 29, -9, 64, 32, -17, + 64, 35, -25, 65, 38, -33, 65, 42, -41, 66, 45, -49, + 62, 13, 66, 62, 14, 63, 62, 14, 58, 62, 15, 52, + 62, 15, 45, 62, 17, 37, 62, 18, 28, 63, 20, 19, + 63, 22, 10, 63, 24, 2, 64, 27, -7, 64, 30, -16, + 65, 33, -24, 66, 36, -32, 66, 40, -40, 67, 43, -48, + 63, 11, 67, 63, 11, 64, 63, 12, 59, 63, 12, 53, + 63, 13, 46, 63, 14, 38, 63, 16, 29, 64, 17, 20, + 64, 20, 11, 64, 22, 3, 65, 25, -6, 65, 27, -14, + 66, 31, -22, 67, 34, -31, 67, 38, -39, 68, 41, -47, + 64, 9, 67, 64, 9, 65, 64, 10, 60, 64, 10, 54, + 64, 11, 47, 64, 12, 39, 64, 14, 30, 65, 15, 22, + 65, 17, 13, 65, 20, 4, 66, 22, -4, 66, 25, -13, + 67, 29, -21, 67, 32, -29, 68, 35, -37, 69, 39, -45, + 65, 7, 68, 65, 7, 66, 65, 7, 61, 65, 8, 55, + 65, 9, 48, 65, 10, 40, 65, 11, 31, 66, 13, 23, + 66, 15, 14, 66, 18, 6, 67, 20, -3, 67, 23, -11, + 68, 26, -19, 68, 30, -28, 69, 33, -36, 70, 37, -44, + 66, 5, 69, 66, 5, 66, 66, 5, 62, 66, 6, 56, + 66, 7, 49, 66, 8, 41, 66, 9, 33, 67, 11, 24, + 67, 13, 15, 67, 15, 7, 68, 18, -2, 68, 21, -10, + 69, 24, -18, 69, 27, -26, 70, 31, -34, 71, 35, -42, + 67, 2, 69, 67, 3, 67, 67, 3, 63, 67, 4, 57, + 67, 5, 50, 67, 6, 43, 67, 7, 34, 68, 9, 26, + 68, 11, 17, 68, 13, 9, 69, 16, 0, 69, 19, -8, + 70, 22, -17, 70, 25, -25, 71, 29, -33, 72, 32, -41, + 68, 0, 70, 68, 1, 68, 68, 1, 64, 68, 1, 58, + 68, 2, 51, 68, 3, 44, 68, 5, 35, 69, 7, 27, + 69, 9, 18, 69, 11, 10, 70, 14, 1, 70, 16, -7, + 71, 20, -15, 71, 23, -24, 72, 27, -31, 73, 30, -40, + 69, -2, 71, 69, -1, 69, 69, -1, 65, 69, 0, 59, + 69, 0, 52, 69, 1, 45, 70, 3, 37, 70, 4, 28, + 70, 6, 20, 70, 9, 11, 71, 11, 3, 71, 14, -5, + 72, 17, -14, 72, 21, -22, 73, 24, -30, 73, 28, -38, + 70, -4, 72, 70, -4, 70, 70, -3, 66, 70, -2, 60, + 70, -1, 53, 70, 0, 46, 71, 1, 38, 71, 2, 30, + 71, 4, 21, 71, 7, 13, 72, 9, 4, 72, 12, -4, + 73, 15, -12, 73, 19, -21, 74, 22, -29, 74, 26, -37, + 71, -6, 72, 71, -6, 70, 71, -5, 67, 71, -4, 61, + 71, -4, 54, 71, -2, 47, 72, -1, 39, 72, 0, 31, + 72, 2, 23, 72, 4, 14, 73, 7, 6, 73, 10, -3, + 74, 13, -11, 74, 16, -19, 75, 20, -27, 75, 24, -35, + 72, -8, 73, 72, -8, 71, 72, -7, 68, 72, -7, 62, + 72, -6, 56, 72, -4, 49, 73, -3, 41, 73, -1, 33, + 73, 0, 24, 73, 2, 16, 74, 5, 7, 74, 8, -1, + 75, 11, -9, 75, 14, -18, 76, 18, -26, 76, 21, -34, + 73, -10, 74, 73, -10, 72, 73, -9, 68, 73, -9, 63, + 73, -8, 57, 74, -7, 50, 74, -5, 42, 74, -3, 34, + 74, -1, 25, 74, 0, 17, 75, 3, 8, 75, 6, 0, + 76, 9, -8, 76, 12, -16, 77, 16, -24, 77, 19, -32, + 74, -12, 75, 74, -12, 73, 74, -11, 69, 74, -11, 64, + 74, -10, 58, 75, -9, 51, 75, -7, 43, 75, -5, 35, + 75, -3, 27, 76, -1, 19, 76, 1, 10, 76, 3, 2, + 77, 7, -6, 77, 10, -15, 78, 13, -23, 78, 17, -31, + 75, -14, 76, 75, -14, 74, 75, -13, 70, 75, -13, 65, + 76, -12, 59, 76, -11, 52, 76, -9, 44, 76, -7, 37, + 76, -5, 28, 77, -3, 20, 77, -1, 11, 77, 1, 3, + 78, 4, -5, 78, 8, -13, 79, 11, -21, 79, 15, -29, + 76, -16, 76, 76, -16, 74, 76, -15, 71, 76, -15, 66, + 77, -14, 60, 77, -13, 53, 77, -11, 46, 77, -10, 38, + 77, -7, 30, 78, -5, 22, 78, -3, 13, 78, 0, 5, + 79, 2, -3, 79, 5, -12, 80, 9, -19, 80, 13, -28, + 77, -18, 77, 77, -18, 75, 78, -17, 72, 78, -17, 67, + 78, -16, 61, 78, -15, 55, 78, -13, 47, 78, -12, 39, + 78, -10, 31, 79, -7, 23, 79, -5, 14, 79, -2, 6, + 80, 0, -2, 80, 3, -10, 81, 7, -18, 81, 10, -26, + 79, -20, 78, 79, -20, 76, 79, -19, 73, 79, -19, 68, + 79, -18, 62, 79, -17, 56, 79, -15, 48, 79, -14, 41, + 79, -12, 32, 80, -9, 24, 80, -7, 16, 80, -4, 8, + 81, -2, 0, 81, 1, -8, 82, 5, -16, 82, 8, -25, + 80, -22, 79, 80, -22, 77, 80, -21, 74, 80, -21, 69, + 80, -20, 63, 80, -19, 57, 80, -17, 49, 80, -16, 42, + 81, -14, 34, 81, -12, 26, 81, -9, 17, 81, -7, 9, + 82, -4, 1, 82, -1, -7, 83, 3, -15, 83, 6, -23, + 81, -24, 80, 81, -24, 78, 81, -23, 75, 81, -23, 70, + 81, -22, 64, 81, -21, 58, 81, -19, 51, 81, -18, 43, + 82, -16, 35, 82, -14, 27, 82, -11, 19, 83, -9, 11, + 83, -6, 2, 83, -3, -5, 84, 0, -13, 84, 4, -21, + 82, -26, 81, 82, -26, 79, 82, -26, 76, 82, -25, 71, + 82, -24, 66, 82, -23, 60, 83, -22, 52, 83, -20, 45, + 83, -18, 37, 83, -16, 29, 84, -14, 21, 84, -11, 13, + 84, -8, 4, 85, -5, -4, 85, -2, -11, 86, 1, -20, + 83, -28, 82, 83, -28, 80, 83, -28, 77, 83, -27, 72, + 83, -26, 67, 84, -25, 61, 84, -24, 54, 84, -22, 46, + 84, -20, 38, 84, -18, 31, 85, -16, 22, 85, -13, 14, + 85, -10, 6, 86, -7, -2, 86, -4, -10, 87, -1, -18, + 84, -30, 82, 84, -30, 81, 84, -30, 78, 84, -29, 74, + 85, -28, 68, 85, -27, 62, 85, -26, 55, 85, -24, 48, + 85, -22, 40, 85, -20, 32, 86, -18, 24, 86, -15, 16, + 86, -12, 7, 87, -9, 0, 87, -6, -8, 88, -3, -16, + 85, -32, 83, 85, -32, 82, 85, -31, 79, 86, -31, 75, + 86, -30, 69, 86, -29, 63, 86, -27, 56, 86, -26, 49, + 86, -24, 41, 86, -22, 33, 87, -20, 25, 87, -17, 17, + 87, -14, 9, 88, -11, 1, 88, -8, -7, 89, -5, -15, + 87, -34, 84, 87, -34, 82, 87, -33, 80, 87, -33, 76, + 87, -32, 70, 87, -31, 64, 87, -29, 57, 87, -28, 50, + 87, -26, 42, 88, -24, 35, 88, -22, 27, 88, -19, 19, + 88, -16, 10, 89, -13, 3, 89, -10, -5, 90, -7, -13, + 88, -36, 85, 88, -35, 83, 88, -35, 81, 88, -34, 77, + 88, -34, 71, 88, -33, 65, 88, -31, 59, 88, -30, 52, + 88, -28, 44, 89, -26, 36, 89, -23, 28, 89, -21, 20, + 90, -18, 12, 90, -15, 4, 90, -12, -4, 91, -9, -12, + 89, -38, 86, 89, -37, 84, 89, -37, 82, 89, -36, 78, + 89, -35, 72, 89, -34, 67, 89, -33, 60, 89, -32, 53, + 90, -30, 45, 90, -28, 38, 90, -25, 29, 90, -23, 22, + 91, -20, 14, 91, -17, 6, 91, -14, -2, 92, -11, -10, + 90, -39, 87, 90, -39, 85, 90, -39, 83, 90, -38, 79, + 90, -37, 74, 90, -36, 68, 90, -35, 61, 90, -33, 54, + 91, -32, 47, 91, -30, 39, 91, -27, 31, 91, -25, 23, + 92, -22, 15, 92, -19, 7, 92, -16, -1, 93, -13, -9, + 91, -41, 87, 91, -41, 86, 91, -40, 83, 91, -40, 80, + 91, -39, 75, 91, -38, 69, 91, -37, 62, 92, -35, 56, + 92, -33, 48, 92, -31, 40, 92, -29, 32, 92, -27, 25, + 93, -24, 17, 93, -21, 9, 93, -18, 1, 94, -15, -7, + 92, -43, 88, 92, -43, 87, 92, -42, 84, 92, -42, 81, + 92, -41, 76, 92, -40, 70, 92, -39, 63, 93, -37, 57, + 93, -35, 49, 93, -33, 42, 93, -31, 34, 94, -29, 26, + 94, -26, 18, 94, -23, 10, 95, -20, 2, 95, -17, -6, + 93, -45, 89, 93, -44, 88, 93, -44, 85, 93, -43, 82, + 93, -43, 77, 93, -42, 71, 94, -40, 65, 94, -39, 58, + 94, -37, 51, 94, -35, 43, 94, -33, 35, 95, -31, 28, + 95, -28, 20, 95, -25, 12, 96, -22, 4, 96, -19, -4, + 43, 68, 56, 43, 68, 50, 43, 69, 42, 43, 69, 32, + 44, 70, 22, 44, 71, 13, 44, 72, 3, 45, 73, -6, + 45, 75, -16, 46, 76, -24, 47, 78, -33, 48, 80, -41, + 48, 83, -49, 49, 85, -57, 50, 88, -65, 52, 90, -72, + 43, 68, 56, 43, 68, 50, 43, 68, 42, 43, 69, 32, + 44, 69, 22, 44, 70, 13, 44, 71, 3, 45, 73, -6, + 45, 74, -15, 46, 76, -24, 47, 78, -33, 48, 80, -41, + 49, 82, -49, 50, 85, -57, 51, 87, -65, 52, 90, -72, + 43, 67, 56, 43, 68, 50, 43, 68, 42, 44, 68, 32, + 44, 69, 23, 44, 70, 13, 45, 71, 3, 45, 72, -6, + 46, 74, -15, 46, 76, -24, 47, 78, -33, 48, 80, -41, + 49, 82, -49, 50, 84, -57, 51, 87, -65, 52, 90, -72, + 43, 67, 56, 43, 67, 50, 43, 68, 42, 44, 68, 33, + 44, 69, 23, 44, 70, 13, 45, 71, 3, 45, 72, -5, + 46, 74, -15, 46, 75, -24, 47, 77, -33, 48, 79, -41, + 49, 82, -49, 50, 84, -57, 51, 87, -65, 52, 89, -72, + 43, 67, 56, 44, 67, 50, 44, 67, 42, 44, 68, 33, + 44, 68, 23, 44, 69, 13, 45, 70, 3, 45, 72, -5, + 46, 73, -15, 46, 75, -24, 47, 77, -33, 48, 79, -41, + 49, 81, -49, 50, 84, -57, 51, 86, -64, 52, 89, -72, + 44, 66, 56, 44, 66, 50, 44, 67, 42, 44, 67, 33, + 44, 68, 23, 45, 69, 14, 45, 70, 4, 45, 71, -5, + 46, 73, -15, 47, 74, -23, 47, 76, -32, 48, 78, -40, + 49, 81, -48, 50, 83, -56, 51, 86, -64, 52, 89, -71, + 44, 65, 56, 44, 66, 51, 44, 66, 43, 44, 66, 33, + 44, 67, 23, 45, 68, 14, 45, 69, 4, 46, 70, -5, + 46, 72, -14, 47, 74, -23, 48, 76, -32, 48, 78, -40, + 49, 80, -48, 50, 83, -56, 51, 85, -64, 52, 88, -71, + 44, 65, 56, 44, 65, 51, 44, 65, 43, 44, 66, 33, + 45, 66, 24, 45, 67, 14, 45, 68, 4, 46, 70, -4, + 46, 71, -14, 47, 73, -23, 48, 75, -32, 49, 77, -40, + 49, 80, -48, 50, 82, -56, 51, 85, -64, 53, 88, -71, + 44, 64, 56, 45, 64, 51, 45, 64, 43, 45, 65, 34, + 45, 66, 24, 45, 66, 15, 46, 68, 5, 46, 69, -4, + 47, 71, -13, 47, 72, -22, 48, 74, -31, 49, 76, -39, + 50, 79, -47, 51, 81, -55, 52, 84, -63, 53, 87, -70, + 45, 63, 57, 45, 63, 51, 45, 63, 44, 45, 64, 34, + 45, 65, 25, 46, 66, 15, 46, 67, 5, 46, 68, -3, + 47, 70, -13, 48, 71, -22, 48, 73, -31, 49, 76, -39, + 50, 78, -47, 51, 81, -55, 52, 83, -63, 53, 86, -70, + 45, 62, 57, 45, 62, 51, 45, 62, 44, 45, 63, 34, + 46, 64, 25, 46, 65, 16, 46, 66, 6, 47, 67, -3, + 47, 69, -12, 48, 70, -21, 49, 73, -30, 49, 75, -39, + 50, 77, -47, 51, 80, -55, 52, 83, -62, 53, 85, -70, + 46, 61, 57, 46, 61, 52, 46, 61, 44, 46, 62, 35, + 46, 62, 26, 46, 63, 16, 47, 64, 6, 47, 66, -2, + 48, 67, -12, 48, 69, -21, 49, 71, -30, 50, 74, -38, + 51, 76, -46, 52, 79, -54, 53, 81, -62, 54, 84, -69, + 46, 59, 57, 46, 59, 52, 46, 60, 45, 46, 60, 35, + 47, 61, 26, 47, 62, 17, 47, 63, 7, 48, 65, -2, + 48, 66, -11, 49, 68, -20, 50, 70, -29, 50, 72, -37, + 51, 75, -45, 52, 78, -53, 53, 80, -61, 54, 83, -68, + 47, 58, 57, 47, 58, 52, 47, 59, 45, 47, 59, 36, + 47, 60, 27, 47, 61, 17, 48, 62, 8, 48, 63, -1, + 49, 65, -11, 49, 67, -19, 50, 69, -28, 51, 71, -37, + 51, 74, -45, 52, 76, -53, 53, 79, -60, 54, 82, -68, + 47, 57, 58, 47, 57, 53, 47, 57, 46, 47, 58, 37, + 48, 58, 27, 48, 59, 18, 48, 61, 8, 49, 62, 0, + 49, 64, -10, 50, 66, -19, 50, 68, -28, 51, 70, -36, + 52, 73, -44, 53, 75, -52, 54, 78, -60, 55, 81, -67, + 48, 55, 58, 48, 55, 53, 48, 56, 46, 48, 56, 37, + 48, 57, 28, 48, 58, 19, 49, 59, 9, 49, 61, 0, + 50, 62, -9, 50, 64, -18, 51, 66, -27, 52, 69, -35, + 52, 71, -43, 53, 74, -51, 54, 77, -59, 55, 80, -67, + 48, 54, 58, 48, 54, 53, 48, 54, 47, 48, 55, 38, + 49, 55, 29, 49, 56, 20, 49, 58, 10, 50, 59, 1, + 50, 61, -8, 51, 63, -17, 51, 65, -26, 52, 67, -34, + 53, 70, -42, 54, 73, -51, 55, 76, -58, 56, 79, -66, + 49, 52, 59, 49, 52, 54, 49, 53, 47, 49, 53, 38, + 49, 54, 29, 49, 55, 20, 50, 56, 11, 50, 57, 2, + 51, 59, -7, 51, 61, -16, 52, 63, -25, 53, 66, -34, + 53, 69, -42, 54, 71, -50, 55, 74, -58, 56, 77, -65, + 49, 50, 59, 49, 50, 54, 49, 51, 48, 50, 51, 39, + 50, 52, 30, 50, 53, 21, 50, 54, 11, 51, 56, 2, + 51, 58, -7, 52, 60, -15, 53, 62, -24, 53, 64, -33, + 54, 67, -41, 55, 70, -49, 56, 73, -57, 57, 76, -64, + 50, 49, 59, 50, 49, 55, 50, 49, 48, 50, 50, 40, + 50, 50, 31, 51, 51, 22, 51, 53, 12, 51, 54, 3, + 52, 56, -6, 52, 58, -14, 53, 60, -23, 54, 63, -32, + 55, 65, -40, 55, 68, -48, 56, 71, -56, 57, 74, -63, + 51, 47, 60, 51, 47, 55, 51, 47, 49, 51, 48, 41, + 51, 49, 32, 51, 50, 23, 52, 51, 13, 52, 52, 4, + 53, 54, -5, 53, 56, -13, 54, 59, -23, 54, 61, -31, + 55, 64, -39, 56, 67, -47, 57, 70, -55, 58, 73, -63, + 51, 45, 60, 51, 45, 56, 52, 45, 50, 52, 46, 41, + 52, 47, 33, 52, 48, 24, 52, 49, 14, 53, 51, 5, + 53, 52, -4, 54, 54, -12, 54, 57, -22, 55, 59, -30, + 56, 62, -38, 57, 65, -46, 57, 68, -54, 58, 71, -62, + 52, 43, 61, 52, 43, 56, 52, 44, 50, 52, 44, 42, + 53, 45, 34, 53, 46, 25, 53, 47, 15, 53, 49, 6, + 54, 51, -3, 54, 53, -11, 55, 55, -20, 56, 58, -29, + 56, 60, -37, 57, 63, -45, 58, 66, -53, 59, 70, -61, + 53, 41, 61, 53, 41, 57, 53, 42, 51, 53, 42, 43, + 53, 43, 35, 54, 44, 26, 54, 45, 16, 54, 47, 7, + 55, 49, -2, 55, 51, -10, 56, 53, -19, 56, 56, -28, + 57, 59, -36, 58, 61, -44, 59, 65, -52, 60, 68, -60, + 54, 39, 61, 54, 39, 57, 54, 40, 52, 54, 40, 44, + 54, 41, 36, 54, 42, 27, 55, 43, 17, 55, 45, 8, + 55, 47, -1, 56, 49, -9, 56, 51, -18, 57, 54, -27, + 58, 57, -35, 59, 60, -43, 59, 63, -51, 60, 66, -59, + 54, 37, 62, 55, 37, 58, 55, 38, 52, 55, 38, 45, + 55, 39, 36, 55, 40, 28, 55, 41, 18, 56, 43, 9, + 56, 45, 0, 57, 47, -8, 57, 49, -17, 58, 52, -26, + 58, 55, -34, 59, 58, -42, 60, 61, -50, 61, 64, -58, + 55, 35, 62, 55, 35, 59, 55, 36, 53, 56, 36, 46, + 56, 37, 37, 56, 38, 29, 56, 39, 19, 57, 41, 11, + 57, 43, 1, 57, 45, -7, 58, 47, -16, 59, 50, -24, + 59, 53, -33, 60, 56, -41, 61, 59, -49, 62, 62, -57, + 56, 33, 63, 56, 33, 59, 56, 33, 54, 56, 34, 47, + 57, 35, 38, 57, 36, 30, 57, 37, 21, 57, 39, 12, + 58, 41, 2, 58, 43, -6, 59, 45, -15, 59, 48, -23, + 60, 51, -31, 61, 54, -40, 61, 57, -48, 62, 60, -55, + 57, 31, 63, 57, 31, 60, 57, 31, 54, 57, 32, 48, + 57, 33, 39, 58, 34, 31, 58, 35, 22, 58, 37, 13, + 59, 39, 4, 59, 41, -5, 60, 43, -14, 60, 46, -22, + 61, 49, -30, 61, 52, -39, 62, 55, -46, 63, 58, -54, + 58, 29, 64, 58, 29, 61, 58, 29, 55, 58, 30, 49, + 58, 31, 41, 58, 32, 32, 59, 33, 23, 59, 35, 14, + 59, 37, 5, 60, 39, -3, 60, 41, -12, 61, 44, -21, + 62, 47, -29, 62, 50, -37, 63, 53, -45, 64, 57, -53, + 59, 27, 65, 59, 27, 61, 59, 27, 56, 59, 28, 49, + 59, 28, 42, 59, 30, 33, 60, 31, 24, 60, 32, 15, + 60, 34, 6, 61, 37, -2, 61, 39, -11, 62, 42, -19, + 62, 45, -28, 63, 48, -36, 64, 51, -44, 65, 55, -52, + 60, 24, 65, 60, 25, 62, 60, 25, 57, 60, 25, 50, + 60, 26, 43, 60, 27, 34, 61, 29, 25, 61, 30, 17, + 61, 32, 7, 62, 34, -1, 62, 37, -10, 63, 40, -18, + 63, 43, -26, 64, 46, -35, 65, 49, -43, 65, 52, -51, + 61, 22, 66, 61, 22, 63, 61, 22, 58, 61, 23, 52, + 61, 24, 44, 61, 25, 36, 62, 26, 27, 62, 28, 18, + 62, 30, 9, 63, 32, 0, 63, 34, -8, 64, 37, -17, + 64, 40, -25, 65, 43, -33, 66, 47, -41, 66, 50, -49, + 62, 20, 66, 62, 20, 64, 62, 20, 59, 62, 21, 53, + 62, 21, 45, 62, 22, 37, 63, 24, 28, 63, 25, 19, + 63, 27, 10, 64, 30, 2, 64, 32, -7, 65, 35, -15, + 65, 38, -23, 66, 41, -32, 66, 44, -40, 67, 48, -48, + 63, 17, 67, 63, 18, 65, 63, 18, 60, 63, 18, 53, + 63, 19, 46, 63, 20, 38, 64, 22, 29, 64, 23, 21, + 64, 25, 12, 65, 27, 3, 65, 30, -6, 65, 33, -14, + 66, 36, -22, 67, 39, -31, 67, 42, -38, 68, 46, -46, + 64, 15, 68, 64, 15, 65, 64, 16, 60, 64, 16, 54, + 64, 17, 47, 64, 18, 39, 64, 20, 30, 65, 21, 22, + 65, 23, 13, 65, 25, 4, 66, 28, -4, 66, 31, -13, + 67, 34, -21, 68, 37, -29, 68, 40, -37, 69, 44, -45, + 65, 13, 68, 65, 13, 66, 65, 14, 61, 65, 14, 55, + 65, 15, 48, 65, 16, 40, 65, 17, 32, 66, 19, 23, + 66, 21, 14, 66, 23, 6, 67, 26, -3, 67, 28, -11, + 68, 31, -19, 68, 35, -28, 69, 38, -36, 70, 41, -44, + 66, 11, 69, 66, 11, 67, 66, 11, 62, 66, 12, 56, + 66, 13, 49, 66, 14, 42, 66, 15, 33, 67, 17, 25, + 67, 19, 16, 67, 21, 7, 68, 24, -2, 68, 26, -10, + 69, 29, -18, 69, 32, -26, 70, 36, -34, 71, 39, -42, + 67, 9, 70, 67, 9, 68, 67, 9, 63, 67, 10, 57, + 67, 11, 50, 67, 12, 43, 67, 13, 34, 68, 15, 26, + 68, 16, 17, 68, 19, 9, 69, 21, 0, 69, 24, -8, + 70, 27, -17, 70, 30, -25, 71, 34, -33, 72, 37, -41, + 68, 7, 70, 68, 7, 68, 68, 7, 64, 68, 8, 58, + 68, 8, 51, 68, 9, 44, 68, 11, 35, 69, 12, 27, + 69, 14, 18, 69, 17, 10, 70, 19, 1, 70, 22, -7, + 71, 25, -15, 71, 28, -24, 72, 31, -32, 72, 35, -40, + 69, 4, 71, 69, 5, 69, 69, 5, 65, 69, 5, 59, + 69, 6, 52, 69, 7, 45, 69, 9, 37, 70, 10, 28, + 70, 12, 20, 70, 14, 11, 71, 17, 3, 71, 20, -6, + 72, 23, -14, 72, 26, -22, 73, 29, -30, 73, 33, -38, + 70, 2, 72, 70, 2, 70, 70, 3, 66, 70, 3, 60, + 70, 4, 53, 70, 5, 46, 70, 7, 38, 71, 8, 30, + 71, 10, 21, 71, 12, 13, 72, 15, 4, 72, 17, -4, + 73, 21, -12, 73, 24, -21, 74, 27, -29, 74, 31, -37, + 71, 0, 73, 71, 0, 71, 71, 1, 67, 71, 1, 61, + 71, 2, 54, 71, 3, 47, 71, 4, 39, 72, 6, 31, + 72, 8, 22, 72, 10, 14, 73, 13, 5, 73, 15, -3, + 73, 18, -11, 74, 21, -19, 75, 25, -27, 75, 28, -35, + 72, -2, 73, 72, -2, 71, 72, -1, 68, 72, -1, 62, + 72, 0, 56, 72, 1, 49, 72, 2, 40, 73, 4, 32, + 73, 6, 24, 73, 8, 16, 74, 10, 7, 74, 13, -1, + 74, 16, -9, 75, 19, -18, 76, 23, -26, 76, 26, -34, + 73, -4, 74, 73, -4, 72, 73, -3, 69, 73, -3, 63, + 73, -2, 57, 73, -1, 50, 73, 0, 42, 74, 2, 34, + 74, 4, 25, 74, 6, 17, 75, 8, 8, 75, 11, 0, + 75, 14, -8, 76, 17, -16, 76, 21, -24, 77, 24, -32, + 74, -6, 75, 74, -6, 73, 74, -5, 69, 74, -5, 64, + 74, -4, 58, 74, -3, 51, 74, -1, 43, 75, 0, 35, + 75, 1, 27, 75, 4, 18, 76, 6, 10, 76, 9, 2, + 76, 12, -6, 77, 15, -15, 77, 18, -23, 78, 22, -31, + 75, -8, 76, 75, -8, 74, 75, -8, 70, 75, -7, 65, + 75, -6, 59, 75, -5, 52, 76, -3, 44, 76, -2, 36, + 76, 0, 28, 76, 1, 20, 77, 4, 11, 77, 7, 3, + 77, 10, -5, 78, 13, -13, 78, 16, -21, 79, 20, -29, + 76, -10, 77, 76, -10, 75, 76, -10, 71, 76, -9, 66, + 76, -8, 60, 76, -7, 53, 77, -5, 46, 77, -4, 38, + 77, -2, 29, 77, 0, 21, 78, 2, 13, 78, 4, 5, + 78, 7, -3, 79, 11, -12, 79, 14, -20, 80, 17, -28, + 77, -12, 77, 77, -12, 75, 77, -12, 72, 77, -11, 67, + 77, -10, 61, 77, -9, 54, 78, -8, 47, 78, -6, 39, + 78, -4, 31, 78, -2, 23, 79, 0, 14, 79, 2, 6, + 79, 5, -2, 80, 8, -10, 80, 12, -18, 81, 15, -26, + 78, -14, 78, 78, -14, 76, 78, -14, 73, 78, -13, 68, + 78, -12, 62, 79, -11, 56, 79, -10, 48, 79, -8, 40, + 79, -6, 32, 79, -4, 24, 80, -2, 16, 80, 0, 7, + 80, 3, -1, 81, 6, -9, 81, 10, -17, 82, 13, -25, + 79, -16, 79, 79, -16, 77, 79, -16, 74, 79, -15, 69, + 79, -14, 63, 80, -13, 57, 80, -12, 49, 80, -10, 42, + 80, -8, 33, 80, -6, 26, 81, -4, 17, 81, -1, 9, + 82, 1, 0, 82, 4, -7, 82, 8, -15, 83, 11, -23, + 80, -18, 80, 80, -18, 78, 80, -18, 75, 80, -17, 70, + 81, -16, 64, 81, -15, 58, 81, -14, 50, 81, -12, 43, + 81, -10, 35, 82, -8, 27, 82, -6, 18, 82, -3, 10, + 83, -1, 2, 83, 2, -6, 83, 5, -14, 84, 9, -22, + 81, -20, 81, 81, -20, 79, 81, -20, 76, 82, -19, 71, + 82, -18, 65, 82, -17, 59, 82, -16, 52, 82, -14, 44, + 82, -12, 36, 83, -10, 28, 83, -8, 20, 83, -5, 12, + 84, -3, 3, 84, 0, -4, 84, 3, -12, 85, 7, -20, + 83, -23, 82, 83, -22, 80, 83, -22, 77, 83, -21, 72, + 83, -21, 67, 83, -20, 60, 83, -18, 53, 83, -17, 46, + 84, -15, 38, 84, -13, 30, 84, -10, 22, 85, -8, 14, + 85, -5, 5, 85, -2, -3, 86, 1, -10, 86, 4, -18, + 84, -25, 82, 84, -24, 81, 84, -24, 78, 84, -23, 73, + 84, -23, 68, 84, -21, 62, 84, -20, 55, 84, -19, 47, + 85, -17, 39, 85, -15, 32, 85, -12, 23, 86, -10, 15, + 86, -7, 7, 86, -4, -1, 87, -1, -9, 87, 2, -17, + 85, -27, 83, 85, -26, 81, 85, -26, 79, 85, -25, 74, + 85, -24, 69, 85, -23, 63, 85, -22, 56, 86, -21, 49, + 86, -19, 41, 86, -17, 33, 86, -14, 25, 87, -12, 17, + 87, -9, 8, 87, -6, 0, 88, -3, -7, 88, 0, -15, + 86, -28, 84, 86, -28, 82, 86, -28, 80, 86, -27, 75, + 86, -26, 70, 86, -25, 64, 86, -24, 57, 87, -22, 50, + 87, -21, 42, 87, -19, 34, 87, -16, 26, 88, -14, 18, + 88, -11, 10, 88, -8, 2, 89, -5, -6, 89, -2, -14, + 87, -30, 85, 87, -30, 83, 87, -30, 81, 87, -29, 76, + 87, -28, 71, 87, -27, 65, 88, -26, 58, 88, -24, 51, + 88, -23, 43, 88, -21, 36, 88, -18, 27, 89, -16, 20, + 89, -13, 11, 89, -10, 3, 90, -7, -4, 90, -4, -12, + 88, -32, 86, 88, -32, 84, 88, -32, 81, 88, -31, 77, + 88, -30, 72, 89, -29, 66, 89, -28, 59, 89, -26, 52, + 89, -24, 45, 89, -23, 37, 90, -20, 29, 90, -18, 21, + 90, -15, 13, 91, -12, 5, 91, -9, -3, 91, -6, -11, + 89, -34, 86, 89, -34, 85, 89, -33, 82, 89, -33, 78, + 90, -32, 73, 90, -31, 67, 90, -30, 61, 90, -28, 54, + 90, -26, 46, 90, -24, 39, 91, -22, 30, 91, -20, 23, + 91, -17, 14, 92, -14, 6, 92, -11, -1, 92, -8, -9, + 90, -36, 87, 90, -36, 86, 90, -35, 83, 91, -35, 79, + 91, -34, 74, 91, -33, 68, 91, -32, 62, 91, -30, 55, + 91, -28, 47, 91, -26, 40, 92, -24, 32, 92, -22, 24, + 92, -19, 16, 93, -16, 8, 93, -13, 0, 93, -10, -8, + 92, -38, 88, 92, -37, 87, 92, -37, 84, 92, -36, 80, + 92, -36, 75, 92, -35, 70, 92, -33, 63, 92, -32, 56, + 92, -30, 49, 92, -28, 41, 93, -26, 33, 93, -24, 26, + 93, -21, 17, 94, -18, 10, 94, -15, 1, 94, -12, -6, + 93, -39, 89, 93, -39, 87, 93, -39, 85, 93, -38, 81, + 93, -37, 77, 93, -36, 71, 93, -35, 64, 93, -34, 58, + 93, -32, 50, 94, -30, 43, 94, -28, 35, 94, -26, 27, + 94, -23, 19, 95, -20, 11, 95, -17, 3, 96, -14, -5, + 94, -41, 90, 94, -41, 88, 94, -41, 86, 94, -40, 82, + 94, -39, 78, 94, -38, 72, 94, -37, 65, 94, -36, 59, + 94, -34, 51, 95, -32, 44, 95, -30, 36, 95, -28, 28, + 95, -25, 20, 96, -22, 13, 96, -19, 5, 97, -16, -3, + 45, 70, 57, 45, 70, 52, 45, 71, 44, 45, 71, 34, + 45, 72, 25, 46, 72, 15, 46, 74, 5, 46, 75, -3, + 47, 76, -13, 48, 78, -22, 48, 80, -31, 49, 82, -39, + 50, 84, -47, 51, 86, -55, 52, 89, -63, 53, 91, -70, + 45, 70, 57, 45, 70, 52, 45, 70, 44, 45, 71, 35, + 45, 71, 25, 46, 72, 15, 46, 73, 5, 46, 74, -3, + 47, 76, -13, 48, 77, -22, 48, 79, -31, 49, 81, -39, + 50, 84, -47, 51, 86, -55, 52, 89, -63, 53, 91, -70, + 45, 69, 57, 45, 70, 52, 45, 70, 44, 45, 70, 35, + 45, 71, 25, 46, 72, 15, 46, 73, 6, 47, 74, -3, + 47, 76, -13, 48, 77, -21, 48, 79, -30, 49, 81, -39, + 50, 83, -47, 51, 86, -55, 52, 88, -63, 53, 91, -70, + 45, 69, 58, 45, 69, 52, 45, 70, 44, 45, 70, 35, + 46, 71, 25, 46, 71, 16, 46, 73, 6, 47, 74, -3, + 47, 75, -12, 48, 77, -21, 49, 79, -30, 49, 81, -39, + 50, 83, -47, 51, 85, -55, 52, 88, -62, 53, 90, -70, + 45, 69, 58, 45, 69, 52, 45, 69, 44, 45, 70, 35, + 46, 70, 25, 46, 71, 16, 46, 72, 6, 47, 73, -3, + 47, 75, -12, 48, 76, -21, 49, 78, -30, 49, 80, -38, + 50, 83, -46, 51, 85, -54, 52, 88, -62, 53, 90, -70, + 45, 68, 58, 45, 68, 52, 46, 69, 45, 46, 69, 35, + 46, 70, 26, 46, 71, 16, 47, 72, 6, 47, 73, -2, + 48, 74, -12, 48, 76, -21, 49, 78, -30, 50, 80, -38, + 50, 82, -46, 51, 85, -54, 52, 87, -62, 54, 90, -69, + 46, 68, 58, 46, 68, 52, 46, 68, 45, 46, 68, 35, + 46, 69, 26, 46, 70, 16, 47, 71, 6, 47, 72, -2, + 48, 74, -12, 48, 75, -21, 49, 77, -30, 50, 79, -38, + 51, 82, -46, 52, 84, -54, 53, 87, -62, 54, 89, -69, + 46, 67, 58, 46, 67, 53, 46, 67, 45, 46, 68, 36, + 46, 68, 26, 47, 69, 17, 47, 70, 7, 47, 72, -2, + 48, 73, -11, 49, 75, -20, 49, 77, -29, 50, 79, -37, + 51, 81, -46, 52, 83, -54, 53, 86, -61, 54, 89, -69, + 46, 66, 58, 46, 66, 53, 46, 67, 45, 46, 67, 36, + 47, 68, 26, 47, 68, 17, 47, 70, 7, 48, 71, -1, + 48, 72, -11, 49, 74, -20, 50, 76, -29, 50, 78, -37, + 51, 80, -45, 52, 83, -53, 53, 85, -61, 54, 88, -68, + 46, 65, 58, 46, 65, 53, 47, 66, 46, 47, 66, 36, + 47, 67, 27, 47, 68, 17, 48, 69, 8, 48, 70, -1, + 49, 72, -11, 49, 73, -19, 50, 75, -28, 51, 77, -37, + 51, 80, -45, 52, 82, -53, 53, 85, -61, 54, 87, -68, + 47, 64, 58, 47, 64, 53, 47, 65, 46, 47, 65, 37, + 47, 66, 27, 48, 67, 18, 48, 68, 8, 48, 69, -1, + 49, 71, -10, 49, 72, -19, 50, 74, -28, 51, 76, -36, + 52, 79, -44, 53, 81, -52, 54, 84, -60, 55, 87, -68, + 47, 63, 59, 47, 63, 53, 47, 63, 46, 47, 64, 37, + 48, 65, 28, 48, 65, 19, 48, 67, 9, 49, 68, 0, + 49, 69, -9, 50, 71, -18, 51, 73, -27, 51, 75, -36, + 52, 78, -44, 53, 80, -52, 54, 83, -60, 55, 86, -67, + 48, 62, 59, 48, 62, 54, 48, 62, 47, 48, 63, 38, + 48, 63, 28, 48, 64, 19, 49, 65, 9, 49, 67, 0, + 50, 68, -9, 50, 70, -18, 51, 72, -27, 52, 74, -35, + 52, 77, -43, 53, 79, -51, 54, 82, -59, 55, 85, -66, + 48, 61, 59, 48, 61, 54, 48, 61, 47, 48, 61, 38, + 49, 62, 29, 49, 63, 20, 49, 64, 10, 50, 65, 1, + 50, 67, -8, 51, 69, -17, 51, 71, -26, 52, 73, -34, + 53, 76, -42, 54, 78, -51, 55, 81, -58, 56, 84, -66, + 49, 59, 59, 49, 59, 54, 49, 60, 48, 49, 60, 39, + 49, 61, 30, 49, 62, 20, 50, 63, 11, 50, 64, 1, + 51, 66, -8, 51, 68, -16, 52, 70, -25, 52, 72, -34, + 53, 74, -42, 54, 77, -50, 55, 80, -58, 56, 82, -65, + 49, 58, 60, 49, 58, 55, 49, 58, 48, 49, 59, 39, + 50, 59, 30, 50, 60, 21, 50, 61, 11, 51, 63, 2, + 51, 64, -7, 52, 66, -16, 52, 68, -25, 53, 71, -33, + 54, 73, -41, 55, 76, -49, 55, 78, -57, 56, 81, -65, + 50, 56, 60, 50, 56, 55, 50, 57, 48, 50, 57, 40, + 50, 58, 31, 50, 59, 22, 51, 60, 12, 51, 61, 3, + 52, 63, -6, 52, 65, -15, 53, 67, -24, 53, 69, -32, + 54, 72, -40, 55, 74, -49, 56, 77, -56, 57, 80, -64, + 50, 55, 60, 50, 55, 55, 50, 55, 49, 50, 56, 40, + 51, 56, 32, 51, 57, 22, 51, 59, 13, 52, 60, 4, + 52, 62, -5, 53, 63, -14, 53, 66, -23, 54, 68, -31, + 55, 70, -40, 55, 73, -48, 56, 76, -56, 57, 79, -63, + 51, 53, 61, 51, 53, 56, 51, 54, 50, 51, 54, 41, + 51, 55, 32, 51, 56, 23, 52, 57, 14, 52, 58, 5, + 53, 60, -4, 53, 62, -13, 54, 64, -22, 54, 66, -31, + 55, 69, -39, 56, 72, -47, 57, 74, -55, 58, 77, -62, + 51, 51, 61, 51, 52, 56, 52, 52, 50, 52, 52, 42, + 52, 53, 33, 52, 54, 24, 52, 55, 14, 53, 57, 5, + 53, 58, -4, 54, 60, -12, 54, 62, -21, 55, 65, -30, + 56, 67, -38, 57, 70, -46, 57, 73, -54, 58, 76, -62, + 52, 50, 61, 52, 50, 57, 52, 50, 51, 52, 51, 43, + 52, 51, 34, 53, 52, 25, 53, 54, 15, 53, 55, 6, + 54, 57, -3, 54, 59, -11, 55, 61, -21, 56, 63, -29, + 56, 66, -37, 57, 68, -45, 58, 71, -53, 59, 74, -61, + 53, 48, 62, 53, 48, 57, 53, 48, 51, 53, 49, 43, + 53, 50, 35, 53, 51, 26, 54, 52, 16, 54, 53, 7, + 55, 55, -2, 55, 57, -10, 56, 59, -20, 56, 61, -28, + 57, 64, -36, 58, 67, -44, 59, 70, -52, 59, 73, -60, + 53, 46, 62, 53, 46, 58, 54, 47, 52, 54, 47, 44, + 54, 48, 36, 54, 49, 27, 54, 50, 17, 55, 51, 8, + 55, 53, -1, 56, 55, -9, 56, 57, -19, 57, 60, -27, + 58, 62, -35, 58, 65, -43, 59, 68, -51, 60, 71, -59, + 54, 44, 62, 54, 44, 58, 54, 45, 52, 54, 45, 45, + 55, 46, 36, 55, 47, 28, 55, 48, 18, 55, 50, 9, + 56, 51, 0, 56, 53, -8, 57, 56, -18, 58, 58, -26, + 58, 61, -34, 59, 63, -42, 60, 67, -50, 61, 70, -58, + 55, 42, 63, 55, 42, 59, 55, 43, 53, 55, 43, 46, + 55, 44, 37, 56, 45, 29, 56, 46, 19, 56, 48, 10, + 57, 49, 1, 57, 51, -7, 58, 54, -16, 58, 56, -25, + 59, 59, -33, 60, 62, -41, 60, 65, -49, 61, 68, -57, + 56, 40, 63, 56, 40, 60, 56, 41, 54, 56, 41, 47, + 56, 42, 38, 56, 43, 29, 57, 44, 20, 57, 46, 11, + 57, 47, 2, 58, 49, -6, 58, 52, -15, 59, 54, -24, + 60, 57, -32, 60, 60, -40, 61, 63, -48, 62, 66, -56, + 56, 38, 64, 57, 38, 60, 57, 39, 54, 57, 39, 47, + 57, 40, 39, 57, 41, 31, 57, 42, 21, 58, 44, 12, + 58, 46, 3, 59, 47, -5, 59, 50, -14, 60, 52, -23, + 60, 55, -31, 61, 58, -39, 62, 61, -47, 63, 64, -55, + 57, 36, 64, 57, 36, 61, 57, 37, 55, 58, 37, 48, + 58, 38, 40, 58, 39, 32, 58, 40, 22, 58, 42, 13, + 59, 44, 4, 59, 46, -4, 60, 48, -13, 60, 50, -21, + 61, 53, -30, 62, 56, -38, 62, 59, -46, 63, 62, -54, + 58, 34, 65, 58, 34, 61, 58, 35, 56, 58, 35, 49, + 59, 36, 41, 59, 37, 33, 59, 38, 23, 59, 40, 15, + 60, 41, 5, 60, 43, -3, 61, 46, -12, 61, 48, -20, + 62, 51, -28, 62, 54, -37, 63, 57, -45, 64, 60, -53, + 59, 32, 65, 59, 32, 62, 59, 33, 57, 59, 33, 50, + 59, 34, 42, 60, 35, 34, 60, 36, 24, 60, 38, 16, + 61, 39, 7, 61, 41, -2, 61, 44, -11, 62, 46, -19, + 63, 49, -27, 63, 52, -36, 64, 55, -44, 65, 59, -51, + 60, 30, 66, 60, 30, 63, 60, 30, 57, 60, 31, 51, + 60, 32, 43, 60, 33, 35, 61, 34, 26, 61, 36, 17, + 61, 37, 8, 62, 39, -1, 62, 42, -10, 63, 44, -18, + 63, 47, -26, 64, 50, -35, 65, 53, -42, 66, 57, -50, + 61, 28, 66, 61, 28, 63, 61, 28, 58, 61, 29, 52, + 61, 30, 44, 61, 31, 36, 62, 32, 27, 62, 33, 18, + 62, 35, 9, 63, 37, 0, 63, 40, -8, 64, 42, -17, + 64, 45, -25, 65, 48, -33, 65, 51, -41, 66, 55, -49, + 62, 25, 67, 62, 25, 64, 62, 26, 59, 62, 26, 53, + 62, 27, 45, 62, 28, 37, 63, 29, 28, 63, 31, 20, + 63, 33, 11, 64, 35, 2, 64, 37, -7, 65, 40, -15, + 65, 43, -23, 66, 45, -32, 67, 49, -40, 67, 52, -48, + 63, 23, 68, 63, 23, 65, 63, 23, 60, 63, 24, 54, + 63, 25, 46, 63, 26, 38, 64, 27, 29, 64, 29, 21, + 64, 30, 12, 65, 33, 3, 65, 35, -5, 66, 38, -14, + 66, 40, -22, 67, 43, -30, 67, 47, -38, 68, 50, -46, + 64, 21, 68, 64, 21, 66, 64, 21, 61, 64, 22, 55, + 64, 23, 47, 64, 24, 39, 64, 25, 31, 65, 26, 22, + 65, 28, 13, 65, 30, 5, 66, 33, -4, 66, 35, -12, + 67, 38, -21, 68, 41, -29, 68, 45, -37, 69, 48, -45, + 65, 19, 69, 65, 19, 66, 65, 19, 62, 65, 20, 56, + 65, 20, 48, 65, 21, 41, 65, 23, 32, 66, 24, 23, + 66, 26, 14, 66, 28, 6, 67, 31, -3, 67, 33, -11, + 68, 36, -19, 68, 39, -28, 69, 42, -36, 70, 46, -44, + 66, 17, 69, 66, 17, 67, 66, 17, 62, 66, 18, 57, + 66, 18, 49, 66, 19, 42, 66, 21, 33, 67, 22, 25, + 67, 24, 16, 67, 26, 7, 68, 29, -2, 68, 31, -10, + 69, 34, -18, 69, 37, -26, 70, 40, -34, 71, 44, -42, + 67, 14, 70, 67, 15, 68, 67, 15, 63, 67, 15, 57, + 67, 16, 50, 67, 17, 43, 67, 18, 34, 68, 20, 26, + 68, 22, 17, 68, 24, 9, 69, 26, 0, 69, 29, -8, + 70, 32, -17, 70, 35, -25, 71, 38, -33, 71, 42, -41, + 68, 12, 71, 68, 12, 69, 68, 13, 64, 68, 13, 58, + 68, 14, 51, 68, 15, 44, 68, 16, 35, 68, 18, 27, + 69, 20, 18, 69, 22, 10, 70, 24, 1, 70, 27, -7, + 70, 30, -15, 71, 33, -24, 72, 36, -32, 72, 39, -40, + 69, 10, 71, 69, 10, 69, 69, 11, 65, 69, 11, 59, + 69, 12, 52, 69, 13, 45, 69, 14, 37, 69, 16, 28, + 70, 17, 20, 70, 20, 11, 71, 22, 2, 71, 25, -6, + 71, 28, -14, 72, 31, -22, 73, 34, -30, 73, 37, -38, + 70, 8, 72, 70, 8, 70, 70, 8, 66, 70, 9, 60, + 70, 10, 53, 70, 11, 46, 70, 12, 38, 70, 13, 30, + 71, 15, 21, 71, 17, 13, 71, 20, 4, 72, 22, -4, + 72, 25, -12, 73, 28, -21, 73, 32, -29, 74, 35, -37, + 71, 6, 73, 71, 6, 71, 71, 6, 67, 71, 7, 61, + 71, 8, 55, 71, 9, 47, 71, 10, 39, 71, 11, 31, + 72, 13, 22, 72, 15, 14, 72, 18, 5, 73, 20, -3, + 73, 23, -11, 74, 26, -20, 74, 30, -27, 75, 33, -36, + 72, 4, 74, 72, 4, 72, 72, 4, 68, 72, 5, 62, + 72, 5, 56, 72, 6, 49, 72, 8, 40, 72, 9, 32, + 73, 11, 24, 73, 13, 15, 73, 16, 7, 74, 18, -1, + 74, 21, -10, 75, 24, -18, 75, 27, -26, 76, 31, -34, + 73, 2, 74, 73, 2, 72, 73, 2, 69, 73, 3, 63, + 73, 3, 57, 73, 4, 50, 73, 6, 42, 73, 7, 34, + 74, 9, 25, 74, 11, 17, 74, 13, 8, 75, 16, 0, + 75, 19, -8, 76, 22, -17, 76, 25, -25, 77, 29, -33, + 74, -1, 75, 74, 0, 73, 74, 0, 70, 74, 0, 64, + 74, 1, 58, 74, 2, 51, 74, 3, 43, 74, 5, 35, + 75, 7, 26, 75, 9, 18, 75, 11, 9, 76, 14, 1, + 76, 17, -7, 77, 20, -15, 77, 23, -23, 78, 26, -31, + 75, -3, 76, 75, -2, 74, 75, -2, 70, 75, -1, 65, + 75, 0, 59, 75, 0, 52, 75, 1, 44, 75, 3, 36, + 76, 5, 28, 76, 7, 20, 76, 9, 11, 77, 12, 3, + 77, 15, -5, 78, 18, -14, 78, 21, -22, 79, 24, -30, + 76, -5, 77, 76, -4, 75, 76, -4, 71, 76, -3, 66, + 76, -3, 60, 76, -1, 53, 76, 0, 45, 76, 1, 38, + 77, 2, 29, 77, 4, 21, 77, 7, 12, 78, 9, 4, + 78, 12, -4, 79, 15, -12, 79, 19, -20, 80, 22, -28, + 77, -7, 77, 77, -7, 75, 77, -6, 72, 77, -5, 67, + 77, -5, 61, 77, -4, 54, 77, -2, 47, 77, -1, 39, + 78, 0, 30, 78, 2, 22, 78, 5, 14, 79, 7, 6, + 79, 10, -2, 80, 13, -11, 80, 17, -19, 81, 20, -27, + 78, -9, 78, 78, -9, 76, 78, -8, 73, 78, -8, 68, + 78, -7, 62, 78, -6, 55, 78, -4, 48, 79, -3, 40, + 79, -1, 32, 79, 0, 24, 79, 3, 15, 80, 5, 7, + 80, 8, -1, 81, 11, -9, 81, 14, -17, 82, 18, -25, + 79, -11, 79, 79, -11, 77, 79, -10, 74, 79, -10, 69, + 79, -9, 63, 79, -8, 57, 79, -6, 49, 80, -5, 41, + 80, -3, 33, 80, -1, 25, 80, 1, 17, 81, 3, 9, + 81, 6, 0, 82, 9, -8, 82, 12, -16, 83, 16, -24, + 80, -13, 80, 80, -13, 78, 80, -12, 75, 80, -12, 70, + 80, -11, 64, 80, -10, 58, 80, -8, 50, 81, -7, 43, + 81, -5, 34, 81, -3, 27, 81, -1, 18, 82, 1, 10, + 82, 4, 1, 83, 7, -6, 83, 10, -14, 84, 13, -22, + 81, -15, 80, 81, -15, 79, 81, -14, 76, 81, -14, 71, + 81, -13, 65, 81, -12, 59, 81, -10, 51, 82, -9, 44, + 82, -7, 36, 82, -5, 28, 82, -3, 19, 83, 0, 11, + 83, 2, 3, 84, 5, -5, 84, 8, -13, 85, 11, -21, + 82, -17, 81, 82, -17, 79, 82, -16, 77, 82, -16, 72, + 82, -15, 66, 82, -14, 60, 83, -12, 53, 83, -11, 45, + 83, -9, 37, 83, -7, 29, 83, -5, 21, 84, -2, 13, + 84, 0, 4, 85, 3, -3, 85, 6, -11, 86, 9, -19, + 83, -19, 82, 83, -19, 80, 83, -19, 78, 84, -18, 73, + 84, -17, 67, 84, -16, 61, 84, -15, 54, 84, -13, 47, + 84, -12, 39, 85, -10, 31, 85, -7, 23, 85, -5, 15, + 85, -2, 6, 86, 0, -2, 86, 3, -9, 87, 7, -18, + 84, -21, 83, 84, -21, 81, 85, -21, 79, 85, -20, 74, + 85, -19, 69, 85, -18, 62, 85, -17, 55, 85, -15, 48, + 85, -14, 40, 86, -12, 32, 86, -9, 24, 86, -7, 16, + 86, -5, 8, 87, -2, 0, 87, 1, -8, 88, 4, -16, + 86, -23, 84, 86, -23, 82, 86, -23, 80, 86, -22, 75, + 86, -21, 70, 86, -20, 64, 86, -19, 57, 86, -17, 49, + 86, -16, 41, 87, -14, 34, 87, -11, 25, 87, -9, 18, + 88, -7, 9, 88, -4, 1, 88, -1, -6, 89, 2, -15, + 87, -25, 85, 87, -25, 83, 87, -24, 80, 87, -24, 76, + 87, -23, 71, 87, -22, 65, 87, -21, 58, 87, -19, 51, + 87, -18, 43, 88, -16, 35, 88, -13, 27, 88, -11, 19, + 89, -9, 11, 89, -6, 3, 89, -3, -5, 90, 0, -13, + 88, -27, 86, 88, -27, 84, 88, -26, 81, 88, -26, 77, + 88, -25, 72, 88, -24, 66, 88, -23, 59, 88, -21, 52, + 89, -20, 44, 89, -18, 37, 89, -15, 28, 89, -13, 21, + 90, -11, 12, 90, -8, 4, 90, -5, -3, 91, -1, -12, + 89, -29, 86, 89, -29, 85, 89, -28, 82, 89, -28, 78, + 89, -27, 73, 89, -26, 67, 89, -25, 60, 89, -23, 53, + 90, -21, 45, 90, -20, 38, 90, -17, 30, 90, -15, 22, + 91, -13, 14, 91, -10, 6, 91, -7, -2, 92, -4, -10, + 90, -31, 87, 90, -31, 85, 90, -30, 83, 90, -30, 79, + 90, -29, 74, 90, -28, 68, 90, -27, 61, 90, -25, 55, + 91, -23, 47, 91, -21, 39, 91, -19, 31, 91, -17, 23, + 92, -15, 15, 92, -12, 7, 92, -9, 0, 93, -6, -8, + 91, -33, 88, 91, -32, 86, 91, -32, 84, 91, -31, 80, + 91, -31, 75, 91, -30, 69, 91, -28, 63, 92, -27, 56, + 92, -25, 48, 92, -23, 41, 92, -21, 33, 92, -19, 25, + 93, -17, 17, 93, -14, 9, 93, -11, 1, 94, -8, -7, + 92, -34, 89, 92, -34, 87, 92, -34, 85, 92, -33, 81, + 92, -33, 76, 92, -32, 70, 92, -30, 64, 93, -29, 57, + 93, -27, 49, 93, -25, 42, 93, -23, 34, 94, -21, 26, + 94, -19, 18, 94, -16, 10, 95, -13, 2, 95, -10, -5, + 93, -36, 90, 93, -36, 88, 93, -36, 86, 93, -35, 82, + 93, -34, 77, 93, -33, 71, 94, -32, 65, 94, -31, 58, + 94, -29, 51, 94, -27, 43, 94, -25, 35, 95, -23, 28, + 95, -20, 20, 95, -18, 12, 96, -15, 4, 96, -12, -4, + 94, -38, 90, 94, -38, 89, 94, -37, 87, 94, -37, 83, + 94, -36, 78, 94, -35, 73, 95, -34, 66, 95, -33, 60, + 95, -31, 52, 95, -29, 45, 95, -27, 37, 96, -25, 29, + 96, -22, 21, 96, -20, 13, 97, -17, 5, 97, -14, -2, + 46, 72, 59, 46, 72, 54, 46, 73, 46, 47, 73, 37, + 47, 74, 27, 47, 74, 18, 48, 75, 8, 48, 76, -1, + 49, 78, -10, 49, 79, -19, 50, 81, -28, 51, 83, -37, + 51, 85, -45, 52, 87, -53, 53, 90, -61, 54, 92, -68, + 46, 72, 59, 47, 72, 54, 47, 72, 46, 47, 73, 37, + 47, 73, 27, 47, 74, 18, 48, 75, 8, 48, 76, -1, + 49, 78, -10, 49, 79, -19, 50, 81, -28, 51, 83, -36, + 51, 85, -45, 52, 87, -53, 53, 90, -60, 54, 92, -68, + 47, 71, 59, 47, 72, 54, 47, 72, 46, 47, 72, 37, + 47, 73, 27, 47, 74, 18, 48, 75, 8, 48, 76, -1, + 49, 77, -10, 49, 79, -19, 50, 81, -28, 51, 82, -36, + 52, 85, -44, 52, 87, -52, 53, 89, -60, 54, 92, -68, + 47, 71, 59, 47, 71, 54, 47, 72, 47, 47, 72, 37, + 47, 73, 28, 47, 73, 18, 48, 74, 8, 48, 76, 0, + 49, 77, -10, 49, 78, -19, 50, 80, -28, 51, 82, -36, + 52, 84, -44, 53, 87, -52, 53, 89, -60, 55, 92, -68, + 47, 71, 59, 47, 71, 54, 47, 71, 47, 47, 72, 37, + 47, 72, 28, 48, 73, 18, 48, 74, 8, 48, 75, 0, + 49, 77, -10, 50, 78, -19, 50, 80, -28, 51, 82, -36, + 52, 84, -44, 53, 86, -52, 54, 89, -60, 55, 91, -67, + 47, 70, 59, 47, 70, 54, 47, 71, 47, 47, 71, 37, + 48, 72, 28, 48, 72, 19, 48, 73, 9, 49, 75, 0, + 49, 76, -10, 50, 78, -18, 50, 79, -27, 51, 81, -36, + 52, 84, -44, 53, 86, -52, 54, 88, -60, 55, 91, -67, + 47, 70, 60, 47, 70, 54, 47, 70, 47, 48, 70, 38, + 48, 71, 28, 48, 72, 19, 48, 73, 9, 49, 74, 0, + 49, 76, -9, 50, 77, -18, 51, 79, -27, 51, 81, -35, + 52, 83, -44, 53, 85, -52, 54, 88, -59, 55, 90, -67, + 47, 69, 60, 47, 69, 54, 48, 69, 47, 48, 70, 38, + 48, 70, 29, 48, 71, 19, 49, 72, 9, 49, 73, 0, + 50, 75, -9, 50, 76, -18, 51, 78, -27, 51, 80, -35, + 52, 83, -43, 53, 85, -51, 54, 87, -59, 55, 90, -67, + 48, 68, 60, 48, 68, 55, 48, 69, 47, 48, 69, 38, + 48, 70, 29, 48, 71, 19, 49, 72, 10, 49, 73, 0, + 50, 74, -9, 50, 76, -17, 51, 78, -26, 52, 80, -35, + 53, 82, -43, 53, 84, -51, 54, 87, -59, 55, 89, -66, + 48, 67, 60, 48, 68, 55, 48, 68, 48, 48, 68, 38, + 48, 69, 29, 49, 70, 20, 49, 71, 10, 50, 72, 1, + 50, 73, -8, 51, 75, -17, 51, 77, -26, 52, 79, -34, + 53, 81, -42, 54, 83, -51, 55, 86, -58, 56, 89, -66, + 48, 67, 60, 48, 67, 55, 48, 67, 48, 49, 67, 39, + 49, 68, 30, 49, 69, 20, 49, 70, 10, 50, 71, 1, + 50, 73, -8, 51, 74, -17, 52, 76, -26, 52, 78, -34, + 53, 80, -42, 54, 83, -50, 55, 85, -58, 56, 88, -66, + 49, 65, 60, 49, 65, 55, 49, 66, 48, 49, 66, 39, + 49, 67, 30, 50, 68, 21, 50, 69, 11, 50, 70, 2, + 51, 71, -7, 51, 73, -16, 52, 75, -25, 53, 77, -33, + 53, 79, -41, 54, 82, -50, 55, 84, -57, 56, 87, -65, + 49, 64, 61, 49, 64, 55, 49, 65, 49, 49, 65, 40, + 50, 66, 31, 50, 66, 21, 50, 68, 12, 51, 69, 2, + 51, 70, -7, 52, 72, -15, 52, 74, -24, 53, 76, -33, + 54, 78, -41, 55, 81, -49, 56, 83, -57, 57, 86, -64, + 50, 63, 61, 50, 63, 56, 50, 63, 49, 50, 64, 40, + 50, 64, 31, 50, 65, 22, 51, 66, 12, 51, 68, 3, + 52, 69, -6, 52, 71, -15, 53, 73, -24, 53, 75, -32, + 54, 77, -40, 55, 80, -48, 56, 82, -56, 57, 85, -64, + 50, 62, 61, 50, 62, 56, 50, 62, 49, 50, 63, 41, + 51, 63, 32, 51, 64, 23, 51, 65, 13, 51, 66, 4, + 52, 68, -5, 53, 70, -14, 53, 72, -23, 54, 74, -32, + 55, 76, -40, 55, 78, -48, 56, 81, -56, 57, 84, -63, + 51, 60, 61, 51, 60, 56, 51, 61, 50, 51, 61, 41, + 51, 62, 32, 51, 63, 23, 52, 64, 13, 52, 65, 4, + 52, 67, -5, 53, 68, -13, 54, 70, -23, 54, 72, -31, + 55, 75, -39, 56, 77, -47, 57, 80, -55, 58, 83, -63, + 51, 59, 61, 51, 59, 57, 51, 59, 50, 51, 60, 42, + 52, 60, 33, 52, 61, 24, 52, 62, 14, 52, 64, 5, + 53, 65, -4, 53, 67, -13, 54, 69, -22, 55, 71, -30, + 55, 74, -38, 56, 76, -47, 57, 79, -54, 58, 82, -62, + 52, 57, 62, 52, 58, 57, 52, 58, 51, 52, 58, 42, + 52, 59, 34, 52, 60, 25, 53, 61, 15, 53, 62, 6, + 53, 64, -3, 54, 66, -12, 55, 68, -21, 55, 70, -29, + 56, 72, -37, 57, 75, -46, 58, 78, -54, 59, 80, -61, + 52, 56, 62, 52, 56, 57, 52, 56, 51, 52, 57, 43, + 53, 57, 34, 53, 58, 25, 53, 59, 16, 54, 61, 7, + 54, 62, -2, 54, 64, -11, 55, 66, -20, 56, 68, -29, + 56, 71, -37, 57, 73, -45, 58, 76, -53, 59, 79, -61, + 53, 54, 62, 53, 54, 58, 53, 55, 52, 53, 55, 44, + 53, 56, 35, 53, 57, 26, 54, 58, 16, 54, 59, 7, + 55, 61, -2, 55, 63, -10, 56, 65, -19, 56, 67, -28, + 57, 69, -36, 58, 72, -44, 59, 75, -52, 59, 78, -60, + 53, 53, 63, 53, 53, 58, 54, 53, 52, 54, 53, 44, + 54, 54, 36, 54, 55, 27, 54, 56, 17, 55, 58, 8, + 55, 59, -1, 56, 61, -9, 56, 63, -18, 57, 65, -27, + 58, 68, -35, 58, 70, -43, 59, 73, -51, 60, 76, -59, + 54, 51, 63, 54, 51, 59, 54, 51, 53, 54, 52, 45, + 54, 52, 37, 55, 53, 28, 55, 54, 18, 55, 56, 9, + 56, 57, 0, 56, 59, -8, 57, 61, -18, 57, 64, -26, + 58, 66, -34, 59, 69, -42, 60, 72, -50, 61, 75, -58, + 55, 49, 63, 55, 49, 59, 55, 49, 53, 55, 50, 46, + 55, 51, 37, 55, 52, 29, 56, 53, 19, 56, 54, 10, + 56, 56, 1, 57, 57, -8, 57, 60, -17, 58, 62, -25, + 59, 65, -33, 59, 67, -42, 60, 70, -49, 61, 73, -57, + 55, 47, 64, 55, 47, 60, 56, 48, 54, 56, 48, 47, + 56, 49, 38, 56, 50, 29, 56, 51, 20, 57, 52, 11, + 57, 54, 2, 58, 56, -7, 58, 58, -16, 59, 60, -24, + 59, 63, -32, 60, 65, -41, 61, 68, -48, 62, 71, -56, + 56, 45, 64, 56, 45, 60, 56, 46, 55, 56, 46, 47, + 57, 47, 39, 57, 48, 30, 57, 49, 21, 57, 50, 12, + 58, 52, 3, 58, 54, -6, 59, 56, -15, 59, 58, -23, + 60, 61, -31, 61, 64, -40, 61, 67, -47, 62, 70, -55, + 57, 43, 65, 57, 43, 61, 57, 44, 55, 57, 44, 48, + 57, 45, 40, 58, 46, 31, 58, 47, 22, 58, 48, 13, + 59, 50, 4, 59, 52, -4, 59, 54, -14, 60, 57, -22, + 61, 59, -30, 61, 62, -39, 62, 65, -46, 63, 68, -54, + 58, 41, 65, 58, 42, 62, 58, 42, 56, 58, 42, 49, + 58, 43, 41, 58, 44, 32, 59, 45, 23, 59, 47, 14, + 59, 48, 5, 60, 50, -3, 60, 52, -12, 61, 55, -21, + 61, 57, -29, 62, 60, -37, 63, 63, -45, 64, 66, -53, + 58, 39, 65, 59, 40, 62, 59, 40, 57, 59, 40, 50, + 59, 41, 42, 59, 42, 33, 59, 43, 24, 60, 45, 15, + 60, 46, 6, 60, 48, -2, 61, 50, -11, 61, 53, -20, + 62, 56, -28, 63, 58, -36, 63, 61, -44, 64, 64, -52, + 59, 37, 66, 59, 38, 63, 59, 38, 57, 59, 38, 51, + 60, 39, 43, 60, 40, 34, 60, 41, 25, 60, 43, 16, + 61, 44, 7, 61, 46, -1, 62, 48, -10, 62, 51, -19, + 63, 54, -27, 64, 56, -35, 64, 59, -43, 65, 62, -51, + 60, 35, 66, 60, 35, 63, 60, 36, 58, 60, 36, 52, + 60, 37, 44, 61, 38, 35, 61, 39, 26, 61, 41, 17, + 62, 42, 8, 62, 44, 0, 62, 47, -9, 63, 49, -17, + 64, 52, -26, 64, 54, -34, 65, 57, -42, 66, 61, -50, + 61, 33, 67, 61, 33, 64, 61, 34, 59, 61, 34, 52, + 61, 35, 45, 61, 36, 36, 62, 37, 27, 62, 39, 19, + 62, 40, 9, 63, 42, 1, 63, 44, -8, 64, 47, -16, + 64, 50, -24, 65, 52, -33, 66, 56, -41, 66, 59, -49, + 62, 31, 68, 62, 31, 65, 62, 32, 59, 62, 32, 53, + 62, 33, 46, 62, 34, 37, 63, 35, 28, 63, 36, 20, + 63, 38, 11, 64, 40, 2, 64, 42, -7, 65, 45, -15, + 65, 48, -23, 66, 50, -32, 66, 54, -40, 67, 57, -48, + 63, 29, 68, 63, 29, 66, 63, 29, 60, 63, 29, 54, + 63, 30, 47, 63, 31, 39, 64, 32, 30, 64, 34, 21, + 64, 36, 12, 65, 37, 4, 65, 40, -5, 66, 42, -13, + 66, 45, -22, 67, 48, -30, 67, 51, -38, 68, 54, -46, + 64, 26, 69, 64, 27, 66, 64, 27, 61, 64, 27, 55, + 64, 28, 48, 64, 29, 40, 65, 30, 31, 65, 32, 22, + 65, 33, 13, 66, 35, 5, 66, 38, -4, 66, 40, -12, + 67, 43, -20, 68, 46, -29, 68, 49, -37, 69, 52, -45, + 65, 24, 69, 65, 24, 67, 65, 25, 62, 65, 25, 56, + 65, 26, 49, 65, 27, 41, 65, 28, 32, 66, 30, 24, + 66, 31, 15, 66, 33, 6, 67, 36, -3, 67, 38, -11, + 68, 41, -19, 68, 44, -28, 69, 47, -36, 70, 50, -44, + 66, 22, 70, 66, 22, 68, 66, 23, 63, 66, 23, 57, + 66, 24, 50, 66, 25, 42, 66, 26, 33, 67, 27, 25, + 67, 29, 16, 67, 31, 7, 68, 34, -1, 68, 36, -10, + 69, 39, -18, 69, 42, -26, 70, 45, -34, 71, 48, -42, + 67, 20, 71, 67, 20, 68, 67, 20, 64, 67, 21, 58, + 67, 22, 51, 67, 23, 43, 67, 24, 34, 68, 25, 26, + 68, 27, 17, 68, 29, 9, 69, 31, 0, 69, 34, -8, + 70, 37, -17, 70, 40, -25, 71, 43, -33, 71, 46, -41, + 68, 18, 71, 68, 18, 69, 68, 18, 65, 68, 19, 59, + 68, 20, 52, 68, 21, 44, 68, 22, 36, 68, 23, 27, + 69, 25, 18, 69, 27, 10, 70, 29, 1, 70, 32, -7, + 70, 35, -15, 71, 37, -24, 72, 41, -32, 72, 44, -40, + 68, 16, 72, 69, 16, 70, 69, 16, 65, 69, 17, 60, + 69, 17, 53, 69, 18, 45, 69, 20, 37, 69, 21, 28, + 70, 23, 20, 70, 25, 11, 70, 27, 2, 71, 30, -6, + 71, 32, -14, 72, 35, -22, 72, 38, -30, 73, 42, -38, + 69, 14, 72, 69, 14, 70, 70, 14, 66, 70, 15, 60, + 70, 15, 54, 70, 16, 46, 70, 17, 38, 70, 19, 30, + 71, 21, 21, 71, 23, 13, 71, 25, 4, 72, 27, -4, + 72, 30, -13, 73, 33, -21, 73, 36, -29, 74, 40, -37, + 70, 11, 73, 70, 12, 71, 71, 12, 67, 71, 12, 61, + 71, 13, 55, 71, 14, 47, 71, 15, 39, 71, 17, 31, + 72, 18, 22, 72, 20, 14, 72, 23, 5, 73, 25, -3, + 73, 28, -11, 74, 31, -20, 74, 34, -28, 75, 37, -36, + 71, 9, 74, 71, 9, 72, 71, 10, 68, 72, 10, 62, + 72, 11, 56, 72, 12, 49, 72, 13, 40, 72, 15, 32, + 73, 16, 24, 73, 18, 15, 73, 21, 6, 74, 23, -2, + 74, 26, -10, 75, 29, -18, 75, 32, -26, 76, 35, -34, + 72, 7, 75, 72, 7, 73, 72, 8, 69, 73, 8, 63, + 73, 9, 57, 73, 10, 50, 73, 11, 42, 73, 12, 34, + 74, 14, 25, 74, 16, 17, 74, 18, 8, 75, 21, 0, + 75, 24, -8, 76, 27, -17, 76, 30, -25, 77, 33, -33, + 73, 5, 75, 73, 5, 73, 73, 6, 70, 74, 6, 64, + 74, 7, 58, 74, 8, 51, 74, 9, 43, 74, 10, 35, + 74, 12, 26, 75, 14, 18, 75, 16, 9, 76, 19, 1, + 76, 22, -7, 76, 25, -15, 77, 28, -23, 78, 31, -32, + 74, 3, 76, 74, 3, 74, 74, 3, 71, 75, 4, 65, + 75, 5, 59, 75, 6, 52, 75, 7, 44, 75, 8, 36, + 75, 10, 27, 76, 12, 19, 76, 14, 11, 77, 17, 2, + 77, 19, -6, 77, 22, -14, 78, 26, -22, 79, 29, -30, + 75, 1, 77, 75, 1, 75, 76, 1, 71, 76, 2, 66, + 76, 3, 60, 76, 3, 53, 76, 5, 45, 76, 6, 37, + 76, 8, 29, 77, 10, 21, 77, 12, 12, 77, 14, 4, + 78, 17, -4, 78, 20, -13, 79, 23, -21, 79, 27, -29, + 76, -1, 77, 76, -1, 76, 77, -1, 72, 77, 0, 67, + 77, 0, 61, 77, 1, 54, 77, 3, 46, 77, 4, 39, + 77, 6, 30, 78, 8, 22, 78, 10, 13, 78, 12, 5, + 79, 15, -3, 79, 18, -11, 80, 21, -19, 80, 25, -27, + 77, -3, 78, 78, -3, 76, 78, -3, 73, 78, -2, 68, + 78, -1, 62, 78, 0, 55, 78, 0, 48, 78, 2, 40, + 78, 4, 31, 79, 5, 23, 79, 8, 15, 79, 10, 7, + 80, 13, -1, 80, 16, -10, 81, 19, -18, 81, 22, -26, + 79, -5, 79, 79, -5, 77, 79, -5, 74, 79, -4, 69, + 79, -3, 63, 79, -2, 56, 79, -1, 49, 79, 0, 41, + 79, 1, 33, 80, 3, 25, 80, 6, 16, 80, 8, 8, + 81, 11, 0, 81, 14, -8, 82, 17, -16, 82, 20, -24, + 80, -7, 80, 80, -7, 78, 80, -7, 75, 80, -6, 70, + 80, -5, 64, 80, -4, 57, 80, -3, 50, 80, -2, 42, + 81, 0, 34, 81, 1, 26, 81, 4, 18, 81, 6, 10, + 82, 9, 1, 82, 12, -7, 83, 15, -15, 83, 18, -23, + 81, -9, 81, 81, -9, 79, 81, -9, 76, 81, -8, 71, + 81, -7, 65, 81, -6, 59, 81, -5, 51, 81, -4, 44, + 82, -2, 35, 82, 0, 28, 82, 1, 19, 82, 4, 11, + 83, 7, 3, 83, 9, -5, 84, 13, -13, 84, 16, -21, + 82, -11, 81, 82, -11, 79, 82, -11, 77, 82, -10, 72, + 82, -9, 66, 82, -8, 60, 82, -7, 52, 82, -6, 45, + 83, -4, 37, 83, -2, 29, 83, 0, 20, 83, 2, 12, + 84, 5, 4, 84, 7, -4, 85, 11, -12, 85, 14, -20, + 83, -13, 82, 83, -13, 80, 83, -13, 77, 83, -12, 73, + 83, -11, 67, 83, -10, 61, 83, -9, 54, 83, -8, 46, + 84, -6, 38, 84, -4, 30, 84, -2, 22, 84, 0, 14, + 85, 2, 5, 85, 5, -2, 86, 8, -10, 86, 12, -18, + 84, -16, 83, 84, -16, 81, 84, -15, 79, 84, -15, 74, + 84, -14, 68, 84, -13, 62, 84, -12, 55, 85, -10, 48, + 85, -8, 40, 85, -7, 32, 85, -4, 24, 86, -2, 16, + 86, 0, 7, 86, 3, -1, 87, 6, -9, 87, 9, -17, + 85, -18, 84, 85, -18, 82, 85, -17, 79, 85, -17, 75, + 85, -16, 69, 85, -15, 63, 86, -14, 56, 86, -12, 49, + 86, -10, 41, 86, -9, 33, 86, -6, 25, 87, -4, 17, + 87, -2, 9, 88, 1, 1, 88, 4, -7, 88, 7, -15, + 86, -20, 85, 86, -20, 83, 86, -19, 80, 86, -19, 76, + 86, -18, 70, 86, -17, 64, 87, -16, 57, 87, -14, 50, + 87, -13, 42, 87, -11, 35, 87, -8, 26, 88, -6, 19, + 88, -4, 10, 89, -1, 2, 89, 2, -6, 89, 5, -14, + 87, -22, 85, 87, -22, 84, 87, -21, 81, 87, -21, 77, + 87, -20, 72, 88, -19, 66, 88, -18, 59, 88, -16, 52, + 88, -14, 44, 88, -13, 36, 89, -10, 28, 89, -8, 20, + 89, -6, 12, 90, -3, 4, 90, 0, -4, 90, 3, -12, + 88, -24, 86, 88, -23, 85, 88, -23, 82, 88, -23, 78, + 88, -22, 73, 89, -21, 67, 89, -20, 60, 89, -18, 53, + 89, -16, 45, 89, -15, 37, 90, -12, 29, 90, -10, 21, + 90, -8, 13, 91, -5, 5, 91, -2, -3, 91, 1, -11, + 89, -26, 87, 89, -25, 85, 89, -25, 83, 89, -24, 79, + 90, -24, 74, 90, -23, 68, 90, -22, 61, 90, -20, 54, + 90, -18, 46, 90, -17, 39, 91, -14, 31, 91, -12, 23, + 91, -10, 15, 92, -7, 7, 92, -4, -1, 92, -1, -9, + 90, -27, 88, 90, -27, 86, 90, -27, 84, 91, -26, 80, + 91, -26, 75, 91, -25, 69, 91, -23, 62, 91, -22, 55, + 91, -20, 48, 91, -19, 40, 92, -16, 32, 92, -14, 24, + 92, -12, 16, 93, -9, 8, 93, -6, 0, 93, -3, -8, + 92, -29, 89, 92, -29, 87, 92, -29, 85, 92, -28, 81, + 92, -28, 76, 92, -27, 70, 92, -25, 63, 92, -24, 57, + 92, -22, 49, 92, -20, 41, 93, -18, 33, 93, -16, 26, + 93, -14, 18, 94, -11, 10, 94, -8, 2, 94, -5, -6, + 93, -31, 89, 93, -31, 88, 93, -31, 86, 93, -30, 82, + 93, -29, 77, 93, -28, 71, 93, -27, 65, 93, -26, 58, + 93, -24, 50, 94, -22, 43, 94, -20, 35, 94, -18, 27, + 94, -16, 19, 95, -13, 11, 95, -10, 3, 95, -7, -5, + 94, -33, 90, 94, -33, 89, 94, -32, 86, 94, -32, 83, + 94, -31, 78, 94, -30, 72, 94, -29, 66, 94, -28, 59, + 94, -26, 52, 95, -24, 44, 95, -22, 36, 95, -20, 29, + 95, -18, 20, 96, -15, 13, 96, -12, 5, 97, -9, -3, + 95, -35, 91, 95, -35, 89, 95, -34, 87, 95, -34, 84, + 95, -33, 79, 95, -32, 73, 95, -31, 67, 95, -30, 60, + 95, -28, 53, 96, -26, 46, 96, -24, 38, 96, -22, 30, + 96, -20, 22, 97, -17, 14, 97, -14, 6, 98, -11, -2, + 48, 74, 61, 48, 74, 56, 48, 74, 48, 48, 75, 39, + 49, 75, 30, 49, 76, 20, 49, 77, 10, 50, 78, 1, + 50, 80, -8, 51, 81, -17, 51, 83, -26, 52, 85, -34, + 53, 87, -42, 54, 89, -50, 55, 91, -58, 56, 94, -66, + 48, 74, 61, 48, 74, 56, 48, 74, 48, 48, 75, 39, + 49, 75, 30, 49, 76, 20, 49, 77, 10, 50, 78, 1, + 50, 79, -8, 51, 81, -17, 51, 82, -26, 52, 84, -34, + 53, 86, -42, 54, 88, -50, 55, 91, -58, 56, 93, -66, + 48, 73, 61, 48, 74, 56, 48, 74, 49, 49, 74, 39, + 49, 75, 30, 49, 76, 20, 49, 77, 11, 50, 78, 1, + 50, 79, -8, 51, 80, -16, 52, 82, -26, 52, 84, -34, + 53, 86, -42, 54, 88, -50, 55, 91, -58, 56, 93, -66, + 48, 73, 61, 48, 73, 56, 49, 74, 49, 49, 74, 39, + 49, 74, 30, 49, 75, 21, 49, 76, 11, 50, 77, 2, + 50, 79, -7, 51, 80, -16, 52, 82, -25, 52, 84, -34, + 53, 86, -42, 54, 88, -50, 55, 90, -58, 56, 93, -65, + 49, 73, 61, 49, 73, 56, 49, 73, 49, 49, 74, 39, + 49, 74, 30, 49, 75, 21, 50, 76, 11, 50, 77, 2, + 51, 78, -7, 51, 80, -16, 52, 82, -25, 52, 83, -34, + 53, 85, -42, 54, 88, -50, 55, 90, -58, 56, 92, -65, + 49, 72, 61, 49, 72, 56, 49, 73, 49, 49, 73, 40, + 49, 74, 30, 49, 74, 21, 50, 75, 11, 50, 76, 2, + 51, 78, -7, 51, 79, -16, 52, 81, -25, 53, 83, -33, + 53, 85, -41, 54, 87, -50, 55, 90, -57, 56, 92, -65, + 49, 72, 61, 49, 72, 56, 49, 72, 49, 49, 73, 40, + 49, 73, 31, 50, 74, 21, 50, 75, 11, 50, 76, 2, + 51, 77, -7, 51, 79, -16, 52, 81, -25, 53, 82, -33, + 54, 85, -41, 54, 87, -49, 55, 89, -57, 56, 92, -65, + 49, 71, 61, 49, 71, 56, 49, 71, 49, 49, 72, 40, + 50, 72, 31, 50, 73, 22, 50, 74, 12, 51, 75, 3, + 51, 77, -7, 52, 78, -15, 52, 80, -24, 53, 82, -33, + 54, 84, -41, 55, 86, -49, 55, 89, -57, 56, 91, -64, + 49, 70, 62, 49, 71, 56, 49, 71, 49, 50, 71, 40, + 50, 72, 31, 50, 73, 22, 50, 74, 12, 51, 75, 3, + 51, 76, -6, 52, 78, -15, 52, 79, -24, 53, 81, -32, + 54, 83, -41, 55, 86, -49, 56, 88, -57, 57, 91, -64, + 50, 70, 62, 50, 70, 56, 50, 70, 50, 50, 70, 41, + 50, 71, 32, 50, 72, 22, 51, 73, 12, 51, 74, 3, + 52, 75, -6, 52, 77, -15, 53, 79, -24, 53, 80, -32, + 54, 83, -40, 55, 85, -48, 56, 87, -56, 57, 90, -64, + 50, 69, 62, 50, 69, 57, 50, 69, 50, 50, 70, 41, + 50, 70, 32, 51, 71, 23, 51, 72, 13, 51, 73, 4, + 52, 74, -5, 52, 76, -14, 53, 78, -23, 54, 80, -32, + 54, 82, -40, 55, 84, -48, 56, 87, -56, 57, 89, -63, + 50, 68, 62, 50, 68, 57, 50, 68, 50, 51, 68, 41, + 51, 69, 32, 51, 70, 23, 51, 71, 13, 52, 72, 4, + 52, 73, -5, 53, 75, -14, 53, 77, -23, 54, 79, -31, + 55, 81, -39, 56, 83, -47, 56, 86, -55, 57, 88, -63, + 51, 66, 62, 51, 67, 57, 51, 67, 51, 51, 67, 42, + 51, 68, 33, 51, 69, 24, 52, 70, 14, 52, 71, 5, + 53, 72, -4, 53, 74, -13, 54, 76, -22, 54, 78, -30, + 55, 80, -39, 56, 82, -47, 57, 85, -55, 58, 87, -62, + 51, 65, 62, 51, 65, 57, 51, 66, 51, 51, 66, 42, + 52, 67, 33, 52, 68, 24, 52, 69, 14, 53, 70, 5, + 53, 71, -4, 54, 73, -12, 54, 75, -22, 55, 77, -30, + 55, 79, -38, 56, 81, -46, 57, 84, -54, 58, 86, -62, + 52, 64, 63, 52, 64, 58, 52, 65, 51, 52, 65, 43, + 52, 66, 34, 52, 66, 25, 53, 67, 15, 53, 69, 6, + 53, 70, -3, 54, 72, -12, 55, 73, -21, 55, 75, -29, + 56, 78, -37, 57, 80, -46, 58, 83, -54, 58, 85, -61, + 52, 63, 63, 52, 63, 58, 52, 63, 52, 52, 64, 43, + 52, 64, 34, 53, 65, 25, 53, 66, 16, 53, 67, 7, + 54, 69, -2, 54, 70, -11, 55, 72, -20, 56, 74, -29, + 56, 77, -37, 57, 79, -45, 58, 82, -53, 59, 84, -61, + 53, 61, 63, 53, 62, 58, 53, 62, 52, 53, 62, 44, + 53, 63, 35, 53, 64, 26, 53, 65, 16, 54, 66, 7, + 54, 67, -2, 55, 69, -11, 55, 71, -20, 56, 73, -28, + 57, 75, -36, 58, 78, -44, 58, 80, -52, 59, 83, -60, + 53, 60, 63, 53, 60, 59, 53, 60, 53, 53, 61, 44, + 53, 61, 36, 54, 62, 27, 54, 63, 17, 54, 65, 8, + 55, 66, -1, 55, 68, -10, 56, 70, -19, 57, 72, -27, + 57, 74, -35, 58, 76, -44, 59, 79, -52, 60, 82, -59, + 54, 58, 64, 54, 59, 59, 54, 59, 53, 54, 59, 45, + 54, 60, 36, 54, 61, 27, 55, 62, 18, 55, 63, 9, + 55, 65, 0, 56, 66, -9, 56, 68, -18, 57, 70, -26, + 58, 73, -35, 58, 75, -43, 59, 78, -51, 60, 80, -59, + 54, 57, 64, 54, 57, 60, 54, 57, 53, 54, 58, 46, + 55, 58, 37, 55, 59, 28, 55, 60, 18, 55, 62, 10, + 56, 63, 0, 56, 65, -8, 57, 67, -17, 58, 69, -26, + 58, 71, -34, 59, 74, -42, 60, 76, -50, 61, 79, -58, + 55, 55, 64, 55, 55, 60, 55, 56, 54, 55, 56, 46, + 55, 57, 38, 55, 58, 29, 56, 59, 19, 56, 60, 10, + 56, 62, 1, 57, 63, -7, 57, 65, -16, 58, 67, -25, + 59, 70, -33, 59, 72, -41, 60, 75, -49, 61, 78, -57, + 55, 54, 64, 55, 54, 60, 56, 54, 55, 56, 54, 47, + 56, 55, 38, 56, 56, 30, 56, 57, 20, 57, 58, 11, + 57, 60, 2, 58, 62, -6, 58, 64, -16, 59, 66, -24, + 59, 68, -32, 60, 71, -41, 61, 73, -48, 62, 76, -56, + 56, 52, 65, 56, 52, 61, 56, 52, 55, 56, 53, 48, + 56, 53, 39, 57, 54, 30, 57, 55, 21, 57, 57, 12, + 58, 58, 3, 58, 60, -6, 59, 62, -15, 59, 64, -23, + 60, 67, -31, 61, 69, -40, 61, 72, -47, 62, 75, -55, + 57, 50, 65, 57, 50, 61, 57, 51, 56, 57, 51, 48, + 57, 52, 40, 57, 52, 31, 58, 54, 22, 58, 55, 13, + 58, 56, 4, 59, 58, -5, 59, 60, -14, 60, 62, -22, + 61, 65, -30, 61, 67, -39, 62, 70, -47, 63, 73, -54, + 57, 48, 66, 57, 48, 62, 58, 49, 56, 58, 49, 49, + 58, 50, 41, 58, 51, 32, 58, 52, 23, 59, 53, 14, + 59, 55, 5, 59, 56, -4, 60, 58, -13, 61, 61, -21, + 61, 63, -29, 62, 66, -38, 63, 69, -46, 63, 71, -53, + 58, 46, 66, 58, 47, 62, 58, 47, 57, 58, 47, 50, + 59, 48, 42, 59, 49, 33, 59, 50, 24, 59, 51, 15, + 60, 53, 6, 60, 55, -3, 61, 57, -12, 61, 59, -20, + 62, 61, -28, 62, 64, -37, 63, 67, -45, 64, 70, -52, + 59, 44, 66, 59, 45, 63, 59, 45, 57, 59, 45, 51, + 59, 46, 43, 59, 47, 34, 60, 48, 25, 60, 49, 16, + 60, 51, 7, 61, 53, -2, 61, 55, -11, 62, 57, -19, + 62, 60, -27, 63, 62, -36, 64, 65, -44, 65, 68, -51, + 60, 43, 67, 60, 43, 64, 60, 43, 58, 60, 43, 51, + 60, 44, 43, 60, 45, 35, 60, 46, 26, 61, 47, 17, + 61, 49, 8, 62, 51, -1, 62, 53, -10, 63, 55, -18, + 63, 58, -26, 64, 60, -35, 65, 63, -43, 65, 66, -50, + 60, 41, 67, 60, 41, 64, 61, 41, 59, 61, 41, 52, + 61, 42, 44, 61, 43, 36, 61, 44, 27, 62, 45, 18, + 62, 47, 9, 62, 49, 0, 63, 51, -8, 63, 53, -17, + 64, 56, -25, 65, 59, -34, 65, 62, -41, 66, 64, -49, + 61, 39, 68, 61, 39, 65, 61, 39, 59, 61, 39, 53, + 62, 40, 45, 62, 41, 37, 62, 42, 28, 62, 44, 19, + 63, 45, 10, 63, 47, 1, 64, 49, -7, 64, 51, -16, + 65, 54, -24, 65, 57, -32, 66, 60, -40, 67, 63, -48, + 62, 36, 68, 62, 37, 65, 62, 37, 60, 62, 37, 54, + 62, 38, 46, 63, 39, 38, 63, 40, 29, 63, 41, 20, + 63, 43, 11, 64, 45, 3, 64, 47, -6, 65, 49, -15, + 65, 52, -23, 66, 55, -31, 67, 58, -39, 67, 61, -47, + 63, 34, 69, 63, 35, 66, 63, 35, 61, 63, 35, 55, + 63, 36, 47, 63, 37, 39, 64, 38, 30, 64, 39, 21, + 64, 41, 12, 65, 43, 4, 65, 45, -5, 66, 47, -13, + 66, 50, -22, 67, 53, -30, 67, 56, -38, 68, 59, -46, + 64, 32, 69, 64, 32, 67, 64, 32, 62, 64, 33, 56, + 64, 33, 48, 64, 34, 40, 65, 36, 31, 65, 37, 23, + 65, 39, 14, 66, 40, 5, 66, 43, -4, 67, 45, -12, + 67, 48, -20, 68, 50, -29, 68, 53, -37, 69, 56, -45, + 65, 30, 70, 65, 30, 68, 65, 30, 63, 65, 31, 57, + 65, 31, 49, 65, 32, 41, 66, 33, 32, 66, 35, 24, + 66, 36, 15, 67, 38, 6, 67, 41, -2, 67, 43, -11, + 68, 46, -19, 69, 48, -27, 69, 51, -35, 70, 54, -43, + 66, 28, 71, 66, 28, 68, 66, 28, 63, 66, 29, 57, + 66, 29, 50, 66, 30, 42, 66, 31, 34, 67, 33, 25, + 67, 34, 16, 67, 36, 8, 68, 38, -1, 68, 41, -9, + 69, 43, -18, 69, 46, -26, 70, 49, -34, 71, 52, -42, + 67, 26, 71, 67, 26, 69, 67, 26, 64, 67, 26, 58, + 67, 27, 51, 67, 28, 43, 67, 29, 35, 68, 31, 26, + 68, 32, 17, 68, 34, 9, 69, 36, 0, 69, 39, -8, + 70, 41, -16, 70, 44, -25, 71, 47, -33, 71, 50, -41, + 68, 23, 72, 68, 24, 69, 68, 24, 65, 68, 24, 59, + 68, 25, 52, 68, 26, 44, 68, 27, 36, 68, 28, 27, + 69, 30, 18, 69, 32, 10, 70, 34, 1, 70, 37, -7, + 70, 39, -15, 71, 42, -24, 72, 45, -32, 72, 48, -40, + 68, 21, 72, 69, 21, 70, 69, 22, 66, 69, 22, 60, + 69, 23, 53, 69, 24, 45, 69, 25, 37, 69, 26, 29, + 70, 28, 20, 70, 30, 11, 70, 32, 3, 71, 34, -6, + 71, 37, -14, 72, 40, -22, 72, 43, -30, 73, 46, -38, + 69, 19, 73, 69, 19, 71, 70, 20, 67, 70, 20, 61, + 70, 21, 54, 70, 22, 47, 70, 23, 38, 70, 24, 30, + 71, 26, 21, 71, 28, 13, 71, 30, 4, 72, 32, -4, + 72, 35, -12, 73, 38, -21, 73, 41, -29, 74, 44, -37, + 70, 17, 74, 70, 17, 71, 70, 17, 67, 71, 18, 62, + 71, 19, 55, 71, 20, 48, 71, 21, 39, 71, 22, 31, + 72, 24, 22, 72, 26, 14, 72, 28, 5, 73, 30, -3, + 73, 33, -11, 74, 36, -20, 74, 39, -28, 75, 42, -36, + 71, 15, 74, 71, 15, 72, 71, 15, 68, 71, 16, 63, + 72, 17, 56, 72, 17, 49, 72, 19, 40, 72, 20, 32, + 72, 22, 24, 73, 23, 15, 73, 26, 6, 74, 28, -2, + 74, 31, -10, 75, 34, -18, 75, 37, -26, 76, 40, -34, + 72, 13, 75, 72, 13, 73, 72, 13, 69, 72, 14, 63, + 73, 14, 57, 73, 15, 50, 73, 16, 42, 73, 18, 34, + 73, 19, 25, 74, 21, 17, 74, 24, 8, 74, 26, 0, + 75, 29, -8, 75, 31, -17, 76, 35, -25, 77, 38, -33, + 73, 11, 76, 73, 11, 74, 73, 11, 70, 73, 12, 64, + 74, 12, 58, 74, 13, 51, 74, 14, 43, 74, 16, 35, + 74, 17, 26, 75, 19, 18, 75, 21, 9, 75, 24, 1, + 76, 27, -7, 76, 29, -16, 77, 32, -24, 77, 36, -32, + 74, 9, 76, 74, 9, 74, 74, 9, 71, 74, 9, 65, + 75, 10, 59, 75, 11, 52, 75, 12, 44, 75, 14, 36, + 75, 15, 27, 76, 17, 19, 76, 19, 10, 76, 22, 2, + 77, 24, -6, 77, 27, -14, 78, 30, -22, 78, 33, -30, + 75, 6, 77, 75, 7, 75, 75, 7, 72, 75, 7, 66, + 75, 8, 60, 76, 9, 53, 76, 10, 45, 76, 11, 37, + 76, 13, 29, 77, 15, 21, 77, 17, 12, 77, 20, 4, + 78, 22, -4, 78, 25, -13, 79, 28, -21, 79, 31, -29, + 76, 4, 78, 76, 4, 76, 76, 5, 72, 76, 5, 67, + 76, 6, 61, 77, 7, 54, 77, 8, 46, 77, 9, 39, + 77, 11, 30, 78, 13, 22, 78, 15, 13, 78, 17, 5, + 79, 20, -3, 79, 23, -11, 80, 26, -19, 80, 29, -27, + 77, 2, 78, 77, 2, 76, 77, 3, 73, 77, 3, 68, + 77, 4, 62, 78, 5, 55, 78, 6, 47, 78, 7, 40, + 78, 9, 31, 79, 11, 23, 79, 13, 15, 79, 15, 6, + 80, 18, -2, 80, 21, -10, 81, 24, -18, 81, 27, -26, + 78, 0, 79, 78, 0, 77, 78, 1, 74, 78, 1, 69, + 78, 2, 63, 79, 3, 56, 79, 4, 49, 79, 5, 41, + 79, 7, 33, 79, 9, 25, 80, 11, 16, 80, 13, 8, + 81, 16, 0, 81, 19, -9, 81, 22, -17, 82, 25, -25, + 79, -2, 80, 79, -2, 78, 79, -1, 75, 79, -1, 70, + 79, 0, 64, 80, 1, 57, 80, 2, 50, 80, 3, 42, + 80, 5, 34, 80, 6, 26, 81, 9, 17, 81, 11, 9, + 82, 14, 1, 82, 16, -7, 82, 20, -15, 83, 23, -23, + 80, -4, 81, 80, -4, 79, 80, -3, 76, 80, -3, 71, + 81, -2, 65, 81, -1, 58, 81, 0, 51, 81, 1, 43, + 81, 3, 35, 81, 4, 27, 82, 7, 19, 82, 9, 11, + 83, 12, 2, 83, 14, -6, 83, 17, -14, 84, 21, -22, + 81, -6, 81, 81, -6, 80, 81, -5, 77, 81, -5, 72, + 82, -4, 66, 82, -3, 60, 82, -2, 52, 82, 0, 45, + 82, 0, 37, 82, 2, 29, 83, 4, 20, 83, 7, 12, + 83, 9, 4, 84, 12, -4, 84, 15, -12, 85, 18, -20, + 82, -8, 82, 82, -8, 80, 82, -7, 77, 82, -7, 73, + 83, -6, 67, 83, -5, 61, 83, -4, 53, 83, -2, 46, + 83, -1, 38, 83, 0, 30, 84, 2, 21, 84, 5, 13, + 84, 7, 5, 85, 10, -3, 85, 13, -11, 86, 16, -19, + 83, -10, 83, 83, -10, 81, 83, -9, 78, 84, -9, 74, + 84, -8, 68, 84, -7, 62, 84, -6, 55, 84, -5, 47, + 84, -3, 39, 85, -1, 31, 85, 0, 23, 85, 3, 15, + 85, 5, 6, 86, 8, -1, 86, 11, -9, 87, 14, -17, + 85, -13, 84, 85, -12, 82, 85, -12, 79, 85, -11, 75, + 85, -11, 69, 85, -10, 63, 85, -8, 56, 85, -7, 49, + 86, -5, 41, 86, -4, 33, 86, -1, 25, 86, 0, 17, + 87, 3, 8, 87, 5, 0, 88, 8, -8, 88, 12, -16, + 86, -14, 85, 86, -14, 83, 86, -14, 80, 86, -13, 76, + 86, -13, 70, 86, -12, 64, 86, -10, 57, 86, -9, 50, + 87, -7, 42, 87, -6, 34, 87, -3, 26, 87, -1, 18, + 88, 1, 10, 88, 3, 2, 89, 6, -6, 89, 9, -14, + 87, -16, 85, 87, -16, 84, 87, -16, 81, 87, -15, 77, + 87, -15, 71, 87, -14, 65, 87, -12, 58, 87, -11, 51, + 88, -9, 43, 88, -8, 36, 88, -5, 27, 88, -3, 19, + 89, -1, 11, 89, 1, 3, 90, 4, -5, 90, 7, -13, + 88, -18, 86, 88, -18, 84, 88, -18, 82, 88, -17, 78, + 88, -17, 72, 88, -16, 66, 88, -14, 59, 88, -13, 52, + 89, -11, 45, 89, -10, 37, 89, -7, 29, 89, -5, 21, + 90, -3, 13, 90, 0, 5, 91, 2, -3, 91, 5, -11, + 89, -20, 87, 89, -20, 85, 89, -20, 83, 89, -19, 79, + 89, -19, 73, 89, -18, 67, 89, -16, 61, 89, -15, 54, + 90, -13, 46, 90, -12, 38, 90, -9, 30, 90, -7, 22, + 91, -5, 14, 91, -2, 6, 92, 0, -2, 92, 3, -10, + 90, -22, 88, 90, -22, 86, 90, -22, 84, 90, -21, 80, + 90, -20, 74, 90, -20, 69, 90, -18, 62, 91, -17, 55, + 91, -15, 47, 91, -14, 40, 91, -11, 31, 91, -9, 24, + 92, -7, 16, 92, -4, 8, 93, -2, 0, 93, 1, -8, + 91, -24, 89, 91, -24, 87, 91, -24, 84, 91, -23, 81, + 91, -22, 76, 91, -21, 70, 91, -20, 63, 92, -19, 56, + 92, -17, 48, 92, -16, 41, 92, -13, 33, 93, -11, 25, + 93, -9, 17, 93, -6, 9, 94, -4, 1, 94, 0, -7, + 92, -26, 89, 92, -26, 88, 92, -26, 85, 92, -25, 82, + 92, -24, 77, 92, -23, 71, 92, -22, 64, 93, -21, 57, + 93, -19, 50, 93, -18, 42, 93, -15, 34, 94, -13, 27, + 94, -11, 18, 94, -8, 11, 95, -6, 2, 95, -3, -5, + 93, -28, 90, 93, -28, 88, 93, -27, 86, 93, -27, 83, + 93, -26, 78, 93, -25, 72, 94, -24, 65, 94, -23, 59, + 94, -21, 51, 94, -19, 44, 94, -17, 36, 95, -15, 28, + 95, -13, 20, 95, -10, 12, 96, -8, 4, 96, -5, -4, + 94, -30, 91, 94, -30, 89, 94, -29, 87, 94, -29, 83, + 94, -28, 79, 94, -27, 73, 95, -26, 66, 95, -25, 60, + 95, -23, 52, 95, -21, 45, 95, -19, 37, 96, -17, 29, + 96, -15, 21, 96, -12, 13, 97, -10, 5, 97, -7, -2, + 95, -32, 92, 95, -31, 90, 95, -31, 88, 95, -31, 84, + 95, -30, 80, 96, -29, 74, 96, -28, 68, 96, -27, 61, + 96, -25, 54, 96, -23, 46, 96, -21, 38, 97, -19, 31, + 97, -17, 23, 97, -14, 15, 98, -12, 7, 98, -9, -1, + 50, 76, 63, 50, 76, 58, 50, 77, 51, 50, 77, 42, + 50, 78, 32, 51, 78, 23, 51, 79, 13, 51, 80, 4, + 52, 81, -5, 52, 83, -14, 53, 84, -23, 54, 86, -31, + 54, 88, -40, 55, 90, -48, 56, 93, -56, 57, 95, -63, + 50, 76, 63, 50, 76, 58, 50, 76, 51, 50, 77, 42, + 50, 77, 32, 51, 78, 23, 51, 79, 13, 51, 80, 4, + 52, 81, -5, 52, 83, -14, 53, 84, -23, 54, 86, -31, + 55, 88, -39, 55, 90, -48, 56, 92, -56, 57, 95, -63, + 50, 76, 63, 50, 76, 58, 50, 76, 51, 50, 76, 42, + 51, 77, 33, 51, 78, 23, 51, 79, 13, 52, 80, 4, + 52, 81, -5, 53, 82, -14, 53, 84, -23, 54, 86, -31, + 55, 88, -39, 55, 90, -48, 56, 92, -55, 57, 94, -63, + 50, 75, 63, 50, 75, 58, 50, 76, 51, 50, 76, 42, + 51, 77, 33, 51, 77, 23, 51, 78, 13, 52, 79, 4, + 52, 81, -5, 53, 82, -14, 53, 84, -23, 54, 85, -31, + 55, 87, -39, 56, 89, -47, 56, 92, -55, 57, 94, -63, + 50, 75, 63, 50, 75, 58, 50, 75, 51, 51, 76, 42, + 51, 76, 33, 51, 77, 24, 51, 78, 14, 52, 79, 5, + 52, 80, -5, 53, 82, -13, 53, 83, -22, 54, 85, -31, + 55, 87, -39, 56, 89, -47, 57, 91, -55, 57, 94, -63, + 51, 75, 63, 51, 75, 58, 51, 75, 51, 51, 75, 42, + 51, 76, 33, 51, 77, 24, 52, 77, 14, 52, 79, 5, + 52, 80, -4, 53, 81, -13, 54, 83, -22, 54, 85, -31, + 55, 87, -39, 56, 89, -47, 57, 91, -55, 58, 93, -63, + 51, 74, 63, 51, 74, 58, 51, 74, 51, 51, 75, 42, + 51, 75, 33, 51, 76, 24, 52, 77, 14, 52, 78, 5, + 53, 79, -4, 53, 81, -13, 54, 82, -22, 54, 84, -30, + 55, 86, -39, 56, 88, -47, 57, 91, -55, 58, 93, -62, + 51, 73, 63, 51, 74, 58, 51, 74, 52, 51, 74, 43, + 51, 75, 33, 52, 75, 24, 52, 76, 14, 52, 77, 5, + 53, 79, -4, 53, 80, -13, 54, 82, -22, 55, 84, -30, + 55, 86, -38, 56, 88, -47, 57, 90, -54, 58, 93, -62, + 51, 73, 63, 51, 73, 58, 51, 73, 52, 51, 74, 43, + 52, 74, 34, 52, 75, 25, 52, 76, 15, 53, 77, 6, + 53, 78, -3, 54, 80, -12, 54, 81, -21, 55, 83, -30, + 55, 85, -38, 56, 87, -46, 57, 90, -54, 58, 92, -62, + 51, 72, 64, 51, 72, 58, 52, 72, 52, 52, 73, 43, + 52, 73, 34, 52, 74, 25, 52, 75, 15, 53, 76, 6, + 53, 77, -3, 54, 79, -12, 54, 81, -21, 55, 82, -29, + 56, 84, -38, 57, 87, -46, 57, 89, -54, 58, 91, -61, + 52, 71, 64, 52, 71, 59, 52, 72, 52, 52, 72, 43, + 52, 73, 34, 52, 73, 25, 53, 74, 15, 53, 75, 6, + 54, 77, -3, 54, 78, -11, 55, 80, -21, 55, 82, -29, + 56, 84, -37, 57, 86, -45, 58, 88, -53, 59, 91, -61, + 52, 70, 64, 52, 70, 59, 52, 70, 52, 52, 71, 44, + 53, 71, 35, 53, 72, 26, 53, 73, 16, 53, 74, 7, + 54, 76, -2, 54, 77, -11, 55, 79, -20, 56, 81, -28, + 56, 83, -37, 57, 85, -45, 58, 87, -53, 59, 90, -60, + 52, 69, 64, 52, 69, 59, 53, 69, 53, 53, 70, 44, + 53, 70, 35, 53, 71, 26, 53, 72, 16, 54, 73, 7, + 54, 75, -2, 55, 76, -10, 55, 78, -20, 56, 80, -28, + 57, 82, -36, 57, 84, -44, 58, 86, -52, 59, 89, -60, + 53, 68, 64, 53, 68, 59, 53, 68, 53, 53, 69, 45, + 53, 69, 36, 53, 70, 27, 54, 71, 17, 54, 72, 8, + 55, 74, -1, 55, 75, -10, 56, 77, -19, 56, 79, -27, + 57, 81, -36, 58, 83, -44, 59, 85, -52, 60, 88, -60, + 53, 67, 64, 53, 67, 60, 53, 67, 53, 53, 68, 45, + 54, 68, 36, 54, 69, 27, 54, 70, 18, 55, 71, 8, + 55, 72, -1, 55, 74, -9, 56, 76, -18, 57, 78, -27, + 57, 80, -35, 58, 82, -43, 59, 84, -51, 60, 87, -59, + 54, 66, 65, 54, 66, 60, 54, 66, 54, 54, 66, 46, + 54, 67, 37, 54, 68, 28, 55, 69, 18, 55, 70, 9, + 55, 71, 0, 56, 73, -9, 56, 74, -18, 57, 76, -26, + 58, 79, -34, 59, 81, -43, 59, 83, -51, 60, 86, -58, + 54, 64, 65, 54, 64, 60, 54, 65, 54, 54, 65, 46, + 55, 66, 37, 55, 66, 28, 55, 67, 19, 55, 69, 10, + 56, 70, 0, 56, 71, -8, 57, 73, -17, 58, 75, -26, + 58, 77, -34, 59, 80, -42, 60, 82, -50, 61, 85, -58, + 55, 63, 65, 55, 63, 61, 55, 63, 55, 55, 64, 47, + 55, 64, 38, 55, 65, 29, 56, 66, 19, 56, 67, 10, + 56, 69, 1, 57, 70, -7, 57, 72, -16, 58, 74, -25, + 59, 76, -33, 59, 78, -41, 60, 81, -49, 61, 84, -57, + 55, 61, 65, 55, 62, 61, 55, 62, 55, 55, 62, 47, + 56, 63, 39, 56, 64, 30, 56, 65, 20, 56, 66, 11, + 57, 67, 2, 57, 69, -7, 58, 71, -16, 58, 73, -24, + 59, 75, -32, 60, 77, -41, 61, 80, -49, 62, 82, -56, + 56, 60, 65, 56, 60, 61, 56, 60, 55, 56, 61, 48, + 56, 61, 39, 56, 62, 30, 57, 63, 21, 57, 64, 12, + 57, 66, 3, 58, 67, -6, 58, 69, -15, 59, 71, -23, + 60, 73, -32, 60, 76, -40, 61, 78, -48, 62, 81, -56, + 56, 58, 66, 56, 58, 62, 56, 59, 56, 57, 59, 48, + 57, 60, 40, 57, 61, 31, 57, 62, 22, 58, 63, 13, + 58, 64, 3, 58, 66, -5, 59, 68, -14, 59, 70, -23, + 60, 72, -31, 61, 74, -39, 62, 77, -47, 62, 80, -55, + 57, 57, 66, 57, 57, 62, 57, 57, 56, 57, 58, 49, + 57, 58, 41, 58, 59, 32, 58, 60, 22, 58, 61, 14, + 59, 63, 4, 59, 64, -4, 59, 66, -13, 60, 68, -22, + 61, 70, -30, 61, 73, -38, 62, 76, -46, 63, 78, -54, + 58, 55, 66, 58, 55, 63, 58, 55, 57, 58, 56, 50, + 58, 56, 41, 58, 57, 33, 58, 58, 23, 59, 60, 14, + 59, 61, 5, 60, 63, -3, 60, 65, -12, 61, 67, -21, + 61, 69, -29, 62, 71, -37, 63, 74, -45, 64, 77, -53, + 58, 53, 67, 58, 53, 63, 58, 54, 57, 58, 54, 50, + 59, 55, 42, 59, 56, 33, 59, 57, 24, 59, 58, 15, + 60, 59, 6, 60, 61, -2, 61, 63, -11, 61, 65, -20, + 62, 67, -28, 63, 70, -37, 63, 72, -44, 64, 75, -52, + 59, 52, 67, 59, 52, 64, 59, 52, 58, 59, 52, 51, + 59, 53, 43, 59, 54, 34, 60, 55, 25, 60, 56, 16, + 60, 58, 7, 61, 59, -1, 61, 61, -11, 62, 63, -19, + 62, 66, -27, 63, 68, -36, 64, 71, -44, 65, 74, -51, + 60, 50, 67, 60, 50, 64, 60, 50, 59, 60, 51, 52, + 60, 51, 44, 60, 52, 35, 60, 53, 26, 61, 54, 17, + 61, 56, 8, 61, 57, -1, 62, 59, -10, 63, 62, -18, + 63, 64, -26, 64, 66, -35, 64, 69, -43, 65, 72, -50, + 60, 48, 68, 60, 48, 65, 60, 48, 59, 60, 49, 53, + 61, 49, 44, 61, 50, 36, 61, 51, 27, 61, 52, 18, + 62, 54, 9, 62, 56, 0, 63, 58, -9, 63, 60, -17, + 64, 62, -25, 64, 65, -34, 65, 67, -42, 66, 70, -50, + 61, 46, 68, 61, 46, 65, 61, 46, 60, 61, 47, 53, + 61, 47, 45, 62, 48, 37, 62, 49, 28, 62, 51, 19, + 62, 52, 10, 63, 54, 1, 63, 56, -8, 64, 58, -16, + 64, 60, -24, 65, 63, -33, 66, 66, -41, 67, 68, -49, + 62, 44, 69, 62, 44, 66, 62, 44, 60, 62, 45, 54, + 62, 46, 46, 62, 46, 38, 63, 47, 29, 63, 49, 20, + 63, 50, 11, 64, 52, 2, 64, 54, -6, 65, 56, -15, + 65, 59, -23, 66, 61, -32, 66, 64, -39, 67, 67, -47, + 63, 42, 69, 63, 42, 66, 63, 42, 61, 63, 43, 55, + 63, 44, 47, 63, 44, 39, 63, 46, 30, 64, 47, 21, + 64, 48, 12, 64, 50, 3, 65, 52, -5, 65, 54, -14, + 66, 57, -22, 66, 59, -30, 67, 62, -38, 68, 65, -46, + 63, 40, 70, 63, 40, 67, 63, 40, 62, 64, 41, 56, + 64, 42, 48, 64, 42, 40, 64, 44, 31, 64, 45, 22, + 65, 46, 13, 65, 48, 4, 66, 50, -4, 66, 52, -13, + 67, 55, -21, 67, 57, -29, 68, 60, -37, 69, 63, -45, + 64, 38, 70, 64, 38, 68, 64, 38, 62, 64, 39, 56, + 64, 40, 49, 65, 40, 41, 65, 42, 32, 65, 43, 23, + 66, 44, 14, 66, 46, 6, 66, 48, -3, 67, 50, -12, + 67, 53, -20, 68, 55, -28, 69, 58, -36, 69, 61, -44, + 65, 35, 71, 65, 36, 68, 65, 36, 63, 65, 36, 57, + 66, 37, 50, 66, 38, 42, 66, 39, 33, 66, 40, 25, + 67, 42, 15, 67, 44, 7, 67, 46, -2, 68, 48, -10, + 68, 50, -18, 69, 53, -27, 69, 56, -35, 70, 59, -43, + 66, 33, 71, 66, 34, 69, 66, 34, 64, 66, 34, 58, + 66, 35, 51, 67, 36, 43, 67, 37, 34, 67, 38, 26, + 67, 40, 17, 68, 42, 8, 68, 44, -1, 69, 46, -9, + 69, 48, -17, 70, 51, -26, 70, 54, -34, 71, 57, -42, + 67, 31, 72, 67, 32, 70, 67, 32, 65, 67, 32, 59, + 67, 33, 52, 67, 34, 44, 68, 35, 35, 68, 36, 27, + 68, 38, 18, 69, 39, 9, 69, 42, 0, 69, 44, -8, + 70, 46, -16, 70, 49, -24, 71, 52, -32, 72, 55, -40, + 68, 29, 72, 68, 29, 70, 68, 30, 66, 68, 30, 60, + 68, 31, 53, 68, 32, 45, 68, 33, 36, 69, 34, 28, + 69, 36, 19, 69, 37, 11, 70, 39, 2, 70, 42, -6, + 71, 44, -15, 71, 47, -23, 72, 50, -31, 73, 53, -39, + 69, 27, 73, 69, 27, 71, 69, 28, 66, 69, 28, 60, + 69, 29, 53, 69, 30, 46, 69, 31, 37, 70, 32, 29, + 70, 34, 20, 70, 35, 12, 71, 37, 3, 71, 40, -5, + 72, 42, -13, 72, 45, -22, 73, 48, -30, 73, 51, -38, + 70, 25, 74, 70, 25, 71, 70, 26, 67, 70, 26, 61, + 70, 27, 54, 70, 27, 47, 70, 29, 39, 70, 30, 30, + 71, 31, 21, 71, 33, 13, 71, 35, 4, 72, 38, -4, + 72, 40, -12, 73, 43, -21, 73, 46, -29, 74, 49, -37, + 70, 23, 74, 71, 23, 72, 71, 23, 68, 71, 24, 62, + 71, 25, 55, 71, 25, 48, 71, 26, 40, 71, 28, 31, + 72, 29, 23, 72, 31, 14, 72, 33, 5, 73, 35, -3, + 73, 38, -11, 74, 41, -19, 74, 44, -27, 75, 47, -35, + 71, 21, 75, 71, 21, 73, 71, 21, 69, 72, 22, 63, + 72, 22, 56, 72, 23, 49, 72, 24, 41, 72, 26, 33, + 73, 27, 24, 73, 29, 16, 73, 31, 7, 74, 33, -1, + 74, 36, -10, 75, 39, -18, 75, 42, -26, 76, 45, -34, + 72, 19, 75, 72, 19, 73, 72, 19, 69, 73, 20, 64, + 73, 20, 57, 73, 21, 50, 73, 22, 42, 73, 24, 34, + 73, 25, 25, 74, 27, 17, 74, 29, 8, 75, 31, 0, + 75, 34, -8, 75, 37, -17, 76, 39, -25, 77, 42, -33, + 73, 17, 76, 73, 17, 74, 73, 17, 70, 73, 18, 65, + 74, 18, 58, 74, 19, 51, 74, 20, 43, 74, 21, 35, + 74, 23, 26, 75, 25, 18, 75, 27, 9, 75, 29, 1, + 76, 32, -7, 76, 34, -15, 77, 37, -23, 77, 40, -32, + 74, 15, 77, 74, 15, 75, 74, 15, 71, 74, 15, 66, + 75, 16, 59, 75, 17, 52, 75, 18, 44, 75, 19, 36, + 75, 21, 28, 76, 23, 19, 76, 25, 11, 76, 27, 2, + 77, 30, -6, 77, 32, -14, 78, 35, -22, 78, 38, -30, + 75, 12, 77, 75, 13, 75, 75, 13, 72, 75, 13, 66, + 75, 14, 60, 76, 15, 53, 76, 16, 45, 76, 17, 37, + 76, 19, 29, 77, 21, 21, 77, 23, 12, 77, 25, 4, + 78, 28, -4, 78, 30, -13, 79, 33, -21, 79, 36, -29, + 76, 10, 78, 76, 10, 76, 76, 11, 73, 76, 11, 67, + 76, 12, 61, 77, 13, 54, 77, 14, 46, 77, 15, 39, + 77, 17, 30, 77, 18, 22, 78, 21, 13, 78, 23, 5, + 79, 25, -3, 79, 28, -11, 80, 31, -19, 80, 34, -27, + 77, 8, 79, 77, 8, 77, 77, 9, 74, 77, 9, 68, + 77, 10, 62, 78, 11, 55, 78, 12, 48, 78, 13, 40, + 78, 15, 31, 78, 16, 23, 79, 18, 15, 79, 21, 6, + 80, 23, -2, 80, 26, -10, 80, 29, -18, 81, 32, -26, + 78, 6, 79, 78, 6, 78, 78, 7, 74, 78, 7, 69, + 78, 8, 63, 79, 8, 56, 79, 10, 49, 79, 11, 41, + 79, 12, 33, 79, 14, 25, 80, 16, 16, 80, 19, 8, + 80, 21, 0, 81, 24, -9, 81, 27, -17, 82, 30, -25, + 79, 4, 80, 79, 4, 78, 79, 4, 75, 79, 5, 70, + 79, 6, 64, 79, 6, 57, 80, 7, 50, 80, 9, 42, + 80, 10, 34, 80, 12, 26, 81, 14, 17, 81, 16, 9, + 81, 19, 1, 82, 22, -7, 82, 25, -15, 83, 28, -23, + 80, 2, 81, 80, 2, 79, 80, 2, 76, 80, 3, 71, + 80, 3, 65, 80, 4, 59, 81, 5, 51, 81, 7, 43, + 81, 8, 35, 81, 10, 27, 82, 12, 19, 82, 14, 11, + 82, 17, 2, 83, 20, -6, 83, 23, -14, 84, 26, -22, + 81, 0, 82, 81, 0, 80, 81, 0, 77, 81, 1, 72, + 81, 1, 66, 81, 2, 60, 82, 3, 52, 82, 5, 45, + 82, 6, 36, 82, 8, 29, 83, 10, 20, 83, 12, 12, + 83, 15, 3, 84, 17, -4, 84, 20, -12, 85, 23, -21, + 82, -2, 82, 82, -2, 81, 82, -2, 78, 82, -1, 73, + 82, 0, 67, 82, 0, 61, 83, 1, 53, 83, 3, 46, + 83, 4, 38, 83, 6, 30, 84, 8, 21, 84, 10, 13, + 84, 13, 5, 85, 15, -3, 85, 18, -11, 86, 21, -19, + 83, -4, 83, 83, -4, 81, 83, -4, 78, 83, -3, 74, + 83, -2, 68, 83, -1, 62, 84, 0, 54, 84, 0, 47, + 84, 2, 39, 84, 4, 31, 85, 6, 23, 85, 8, 15, + 85, 11, 6, 86, 13, -2, 86, 16, -10, 87, 19, -18, + 84, -6, 84, 84, -6, 82, 84, -6, 79, 84, -5, 75, + 84, -4, 69, 85, -3, 63, 85, -2, 56, 85, -1, 48, + 85, 0, 40, 85, 2, 32, 86, 4, 24, 86, 6, 16, + 86, 8, 8, 87, 11, 0, 87, 14, -8, 88, 17, -16, + 85, -9, 85, 85, -8, 83, 86, -8, 80, 86, -8, 76, + 86, -7, 70, 86, -6, 64, 86, -5, 57, 86, -3, 50, + 86, -2, 42, 87, 0, 34, 87, 1, 26, 87, 3, 18, + 87, 6, 9, 88, 8, 2, 88, 11, -6, 89, 14, -14, + 87, -11, 86, 87, -10, 84, 87, -10, 81, 87, -10, 77, + 87, -9, 71, 87, -8, 65, 87, -7, 58, 87, -5, 51, + 87, -4, 43, 88, -2, 35, 88, 0, 27, 88, 1, 19, + 88, 4, 11, 89, 6, 3, 89, 9, -5, 90, 12, -13, + 88, -13, 86, 88, -12, 85, 88, -12, 82, 88, -12, 78, + 88, -11, 72, 88, -10, 66, 88, -9, 59, 88, -8, 52, + 88, -6, 44, 89, -4, 37, 89, -2, 28, 89, 0, 21, + 89, 2, 12, 90, 4, 4, 90, 7, -3, 91, 10, -12, + 89, -15, 87, 89, -14, 85, 89, -14, 83, 89, -14, 79, + 89, -13, 73, 89, -12, 67, 89, -11, 60, 89, -10, 53, + 89, -8, 46, 90, -6, 38, 90, -4, 30, 90, -2, 22, + 90, 0, 14, 91, 2, 6, 91, 5, -2, 92, 8, -10, + 90, -17, 88, 90, -16, 86, 90, -16, 84, 90, -16, 80, + 90, -15, 74, 90, -14, 68, 90, -13, 62, 90, -12, 55, + 90, -10, 47, 91, -8, 39, 91, -6, 31, 91, -4, 23, + 91, -2, 15, 92, 0, 7, 92, 3, -1, 93, 6, -9, + 91, -19, 89, 91, -18, 87, 91, -18, 84, 91, -17, 81, + 91, -17, 75, 91, -16, 69, 91, -15, 63, 91, -13, 56, + 91, -12, 48, 92, -10, 41, 92, -8, 33, 92, -6, 25, + 92, -4, 17, 93, -1, 9, 93, 1, 1, 94, 4, -7, + 92, -20, 89, 92, -20, 88, 92, -20, 85, 92, -19, 81, + 92, -19, 76, 92, -18, 71, 92, -17, 64, 92, -15, 57, + 92, -14, 49, 93, -12, 42, 93, -10, 34, 93, -8, 26, + 94, -6, 18, 94, -3, 10, 94, -1, 2, 95, 2, -6, + 93, -22, 90, 93, -22, 88, 93, -22, 86, 93, -21, 82, + 93, -21, 77, 93, -20, 72, 93, -19, 65, 93, -17, 58, + 94, -16, 51, 94, -14, 43, 94, -12, 35, 94, -10, 28, + 95, -8, 19, 95, -5, 12, 95, -3, 3, 96, 0, -4, + 94, -24, 91, 94, -24, 89, 94, -24, 87, 94, -23, 83, + 94, -23, 78, 94, -22, 73, 94, -21, 66, 94, -19, 60, + 95, -18, 52, 95, -16, 45, 95, -14, 37, 95, -12, 29, + 96, -10, 21, 96, -7, 13, 96, -5, 5, 97, -2, -3, + 95, -26, 92, 95, -26, 90, 95, -26, 88, 95, -25, 84, + 95, -24, 79, 95, -24, 74, 95, -23, 67, 95, -21, 61, + 96, -20, 53, 96, -18, 46, 96, -16, 38, 96, -14, 30, + 97, -12, 22, 97, -9, 14, 97, -7, 6, 98, -4, -1, + 96, -28, 93, 96, -28, 91, 96, -28, 89, 96, -27, 85, + 96, -26, 80, 96, -26, 75, 96, -24, 68, 96, -23, 62, + 97, -22, 55, 97, -20, 47, 97, -18, 39, 97, -16, 32, + 98, -14, 24, 98, -11, 16, 98, -9, 8, 99, -6, 0, + 52, 78, 65, 52, 78, 59, 52, 79, 53, 52, 79, 44, + 52, 79, 35, 52, 80, 25, 53, 81, 16, 53, 82, 6, + 53, 83, -3, 54, 84, -11, 55, 86, -21, 55, 88, -29, + 56, 90, -37, 57, 92, -46, 58, 94, -53, 58, 96, -61, + 52, 78, 65, 52, 78, 59, 52, 78, 53, 52, 79, 44, + 52, 79, 35, 52, 80, 26, 53, 81, 16, 53, 82, 7, + 54, 83, -3, 54, 84, -11, 55, 86, -20, 55, 87, -29, + 56, 89, -37, 57, 91, -45, 58, 94, -53, 59, 96, -61, + 52, 78, 65, 52, 78, 60, 52, 78, 53, 52, 78, 44, + 52, 79, 35, 52, 80, 26, 53, 80, 16, 53, 81, 7, + 54, 83, -2, 54, 84, -11, 55, 86, -20, 55, 87, -29, + 56, 89, -37, 57, 91, -45, 58, 93, -53, 59, 96, -61, + 52, 77, 65, 52, 77, 60, 52, 78, 53, 52, 78, 44, + 52, 79, 35, 53, 79, 26, 53, 80, 16, 53, 81, 7, + 54, 82, -2, 54, 84, -11, 55, 85, -20, 55, 87, -29, + 56, 89, -37, 57, 91, -45, 58, 93, -53, 59, 95, -61, + 52, 77, 65, 52, 77, 60, 52, 77, 53, 52, 78, 44, + 52, 78, 35, 53, 79, 26, 53, 80, 16, 53, 81, 7, + 54, 82, -2, 54, 83, -11, 55, 85, -20, 56, 87, -28, + 56, 88, -37, 57, 90, -45, 58, 93, -53, 59, 95, -61, + 52, 77, 65, 52, 77, 60, 52, 77, 53, 52, 77, 44, + 53, 78, 35, 53, 78, 26, 53, 79, 16, 54, 80, 7, + 54, 82, -2, 54, 83, -11, 55, 85, -20, 56, 86, -28, + 56, 88, -36, 57, 90, -45, 58, 92, -53, 59, 95, -60, + 52, 76, 65, 52, 76, 60, 52, 76, 53, 53, 77, 45, + 53, 77, 36, 53, 78, 26, 53, 79, 17, 54, 80, 7, + 54, 81, -2, 55, 82, -10, 55, 84, -20, 56, 86, -28, + 57, 88, -36, 57, 90, -45, 58, 92, -52, 59, 94, -60, + 53, 76, 65, 53, 76, 60, 53, 76, 53, 53, 76, 45, + 53, 77, 36, 53, 77, 27, 53, 78, 17, 54, 79, 8, + 54, 81, -1, 55, 82, -10, 55, 84, -19, 56, 85, -28, + 57, 87, -36, 58, 89, -44, 58, 92, -52, 59, 94, -60, + 53, 75, 65, 53, 75, 60, 53, 75, 54, 53, 76, 45, + 53, 76, 36, 53, 77, 27, 54, 78, 17, 54, 79, 8, + 55, 80, -1, 55, 81, -10, 56, 83, -19, 56, 85, -27, + 57, 87, -36, 58, 89, -44, 59, 91, -52, 59, 93, -60, + 53, 74, 65, 53, 74, 60, 53, 74, 54, 53, 75, 45, + 53, 75, 36, 54, 76, 27, 54, 77, 17, 54, 78, 8, + 55, 79, -1, 55, 81, -10, 56, 82, -19, 56, 84, -27, + 57, 86, -35, 58, 88, -44, 59, 90, -51, 60, 93, -59, + 53, 73, 65, 53, 73, 60, 53, 74, 54, 54, 74, 46, + 54, 75, 37, 54, 75, 28, 54, 76, 18, 55, 77, 9, + 55, 79, 0, 56, 80, -9, 56, 82, -18, 57, 83, -27, + 57, 85, -35, 58, 87, -43, 59, 90, -51, 60, 92, -59, + 54, 72, 65, 54, 72, 61, 54, 73, 54, 54, 73, 46, + 54, 74, 37, 54, 74, 28, 55, 75, 18, 55, 76, 9, + 55, 77, 0, 56, 79, -9, 56, 81, -18, 57, 82, -26, + 58, 84, -34, 59, 86, -43, 59, 89, -51, 60, 91, -58, + 54, 71, 66, 54, 71, 61, 54, 72, 55, 54, 72, 46, + 54, 73, 38, 55, 73, 28, 55, 74, 19, 55, 75, 10, + 56, 77, 0, 56, 78, -8, 57, 80, -17, 57, 81, -26, + 58, 83, -34, 59, 85, -42, 60, 88, -50, 61, 90, -58, + 54, 70, 66, 54, 70, 61, 54, 71, 55, 55, 71, 47, + 55, 72, 38, 55, 72, 29, 55, 73, 19, 56, 74, 10, + 56, 76, 1, 57, 77, -8, 57, 79, -17, 58, 80, -25, + 58, 82, -33, 59, 85, -42, 60, 87, -50, 61, 89, -57, + 55, 69, 66, 55, 69, 61, 55, 69, 55, 55, 70, 47, + 55, 70, 38, 55, 71, 29, 56, 72, 20, 56, 73, 11, + 56, 74, 1, 57, 76, -7, 57, 78, -16, 58, 79, -25, + 59, 81, -33, 59, 84, -41, 60, 86, -49, 61, 88, -57, + 55, 68, 66, 55, 68, 62, 55, 68, 56, 55, 69, 48, + 56, 69, 39, 56, 70, 30, 56, 71, 20, 56, 72, 11, + 57, 73, 2, 57, 75, -6, 58, 76, -16, 58, 78, -24, + 59, 80, -32, 60, 82, -41, 61, 85, -49, 62, 87, -56, + 56, 67, 66, 56, 67, 62, 56, 67, 56, 56, 67, 48, + 56, 68, 39, 56, 69, 31, 57, 70, 21, 57, 71, 12, + 57, 72, 3, 58, 74, -6, 58, 75, -15, 59, 77, -23, + 60, 79, -32, 60, 81, -40, 61, 84, -48, 62, 86, -56, + 56, 65, 66, 56, 65, 62, 56, 66, 56, 56, 66, 49, + 57, 67, 40, 57, 67, 31, 57, 68, 22, 57, 69, 13, + 58, 71, 3, 58, 72, -5, 59, 74, -14, 59, 76, -23, + 60, 78, -31, 61, 80, -39, 61, 83, -47, 62, 85, -55, + 57, 64, 67, 57, 64, 63, 57, 64, 57, 57, 65, 49, + 57, 65, 41, 57, 66, 32, 58, 67, 22, 58, 68, 13, + 58, 69, 4, 59, 71, -4, 59, 73, -14, 60, 75, -22, + 60, 77, -30, 61, 79, -39, 62, 81, -47, 63, 84, -54, + 57, 63, 67, 57, 63, 63, 57, 63, 57, 57, 63, 50, + 58, 64, 41, 58, 65, 32, 58, 66, 23, 58, 67, 14, + 59, 68, 5, 59, 70, -4, 60, 71, -13, 60, 73, -21, + 61, 75, -29, 62, 78, -38, 62, 80, -46, 63, 83, -54, + 58, 61, 67, 58, 61, 63, 58, 61, 58, 58, 62, 50, + 58, 62, 42, 58, 63, 33, 59, 64, 24, 59, 65, 15, + 59, 67, 5, 60, 68, -3, 60, 70, -12, 61, 72, -20, + 61, 74, -29, 62, 76, -37, 63, 79, -45, 64, 81, -53, + 58, 59, 68, 58, 60, 64, 58, 60, 58, 59, 60, 51, + 59, 61, 43, 59, 62, 34, 59, 63, 24, 59, 64, 16, + 60, 65, 6, 60, 67, -2, 61, 68, -11, 61, 70, -20, + 62, 72, -28, 63, 75, -36, 63, 77, -44, 64, 80, -52, + 59, 58, 68, 59, 58, 64, 59, 58, 59, 59, 59, 51, + 59, 59, 43, 59, 60, 35, 60, 61, 25, 60, 62, 16, + 60, 63, 7, 61, 65, -1, 61, 67, -10, 62, 69, -19, + 62, 71, -27, 63, 73, -36, 64, 76, -43, 65, 78, -51, + 60, 56, 68, 60, 56, 65, 60, 56, 59, 60, 57, 52, + 60, 58, 44, 60, 58, 35, 60, 59, 26, 61, 60, 17, + 61, 62, 8, 61, 63, 0, 62, 65, -10, 62, 67, -18, + 63, 69, -26, 64, 72, -35, 64, 74, -43, 65, 77, -50, + 60, 54, 68, 60, 55, 65, 60, 55, 60, 60, 55, 53, + 61, 56, 45, 61, 57, 36, 61, 58, 27, 61, 59, 18, + 62, 60, 9, 62, 62, 0, 63, 64, -9, 63, 65, -17, + 64, 68, -25, 64, 70, -34, 65, 73, -42, 66, 75, -50, + 61, 53, 69, 61, 53, 66, 61, 53, 60, 61, 53, 53, + 61, 54, 45, 61, 55, 37, 62, 56, 28, 62, 57, 19, + 62, 58, 10, 63, 60, 1, 63, 62, -8, 64, 64, -16, + 64, 66, -24, 65, 68, -33, 66, 71, -41, 66, 74, -49, + 62, 51, 69, 62, 51, 66, 62, 51, 61, 62, 52, 54, + 62, 52, 46, 62, 53, 38, 62, 54, 29, 63, 55, 20, + 63, 57, 11, 63, 58, 2, 64, 60, -7, 64, 62, -15, + 65, 64, -23, 66, 67, -32, 66, 69, -40, 67, 72, -48, + 62, 49, 70, 62, 49, 67, 62, 49, 61, 62, 50, 55, + 63, 50, 47, 63, 51, 39, 63, 52, 30, 63, 53, 21, + 64, 55, 12, 64, 56, 3, 65, 58, -6, 65, 60, -14, + 66, 63, -22, 66, 65, -31, 67, 68, -39, 68, 70, -47, + 63, 47, 70, 63, 47, 67, 63, 47, 62, 63, 48, 56, + 63, 48, 48, 64, 49, 40, 64, 50, 30, 64, 51, 22, + 64, 53, 13, 65, 55, 4, 65, 56, -5, 66, 58, -13, + 66, 61, -21, 67, 63, -30, 67, 66, -38, 68, 69, -46, + 64, 45, 70, 64, 45, 68, 64, 46, 63, 64, 46, 56, + 64, 47, 49, 64, 47, 40, 64, 48, 31, 65, 50, 23, + 65, 51, 14, 65, 53, 5, 66, 55, -4, 66, 57, -12, + 67, 59, -20, 68, 61, -29, 68, 64, -37, 69, 67, -45, + 65, 43, 71, 65, 43, 68, 65, 44, 63, 65, 44, 57, + 65, 45, 49, 65, 45, 41, 65, 46, 32, 66, 48, 24, + 66, 49, 15, 66, 51, 6, 67, 53, -3, 67, 55, -11, + 68, 57, -19, 68, 60, -28, 69, 62, -36, 70, 65, -44, + 65, 41, 71, 65, 41, 69, 65, 42, 64, 65, 42, 58, + 66, 43, 50, 66, 43, 42, 66, 45, 33, 66, 46, 25, + 67, 47, 16, 67, 49, 7, 67, 51, -1, 68, 53, -10, + 68, 55, -18, 69, 58, -27, 70, 60, -35, 70, 63, -43, + 66, 39, 72, 66, 39, 70, 66, 39, 65, 66, 40, 59, + 67, 40, 51, 67, 41, 44, 67, 42, 35, 67, 43, 26, + 68, 45, 17, 68, 46, 9, 68, 48, 0, 69, 50, -8, + 69, 53, -17, 70, 55, -25, 70, 58, -33, 71, 61, -41, + 67, 37, 73, 67, 37, 70, 67, 37, 65, 67, 38, 59, + 67, 38, 52, 68, 39, 45, 68, 40, 36, 68, 41, 27, + 68, 43, 18, 69, 44, 10, 69, 46, 1, 70, 48, -7, + 70, 51, -15, 71, 53, -24, 71, 56, -32, 72, 59, -40, + 68, 35, 73, 68, 35, 71, 68, 35, 66, 68, 35, 60, + 68, 36, 53, 68, 37, 45, 69, 38, 37, 69, 39, 28, + 69, 41, 19, 70, 42, 11, 70, 44, 2, 70, 46, -6, + 71, 49, -14, 71, 51, -23, 72, 54, -31, 73, 57, -39, + 69, 33, 74, 69, 33, 71, 69, 33, 67, 69, 33, 61, + 69, 34, 54, 69, 35, 46, 69, 36, 38, 70, 37, 29, + 70, 39, 21, 70, 40, 12, 71, 42, 3, 71, 44, -5, + 72, 47, -13, 72, 49, -22, 73, 52, -30, 73, 55, -38, + 70, 31, 74, 70, 31, 72, 70, 31, 68, 70, 31, 62, + 70, 32, 55, 70, 33, 47, 70, 34, 39, 71, 35, 31, + 71, 37, 22, 71, 38, 13, 72, 40, 4, 72, 42, -4, + 72, 45, -12, 73, 47, -20, 74, 50, -28, 74, 53, -36, + 71, 28, 75, 71, 29, 73, 71, 29, 68, 71, 29, 63, + 71, 30, 56, 71, 31, 48, 71, 32, 40, 71, 33, 32, + 72, 35, 23, 72, 36, 15, 72, 38, 6, 73, 40, -2, + 73, 43, -11, 74, 45, -19, 74, 48, -27, 75, 51, -35, + 71, 26, 75, 72, 26, 73, 72, 27, 69, 72, 27, 63, + 72, 28, 57, 72, 29, 49, 72, 30, 41, 72, 31, 33, + 73, 32, 24, 73, 34, 16, 73, 36, 7, 74, 38, -1, + 74, 41, -9, 75, 43, -18, 75, 46, -26, 76, 49, -34, + 72, 24, 76, 72, 24, 74, 72, 25, 70, 73, 25, 64, + 73, 26, 58, 73, 27, 50, 73, 28, 42, 73, 29, 34, + 73, 30, 25, 74, 32, 17, 74, 34, 8, 75, 36, 0, + 75, 39, -8, 76, 41, -17, 76, 44, -25, 77, 47, -33, + 73, 22, 76, 73, 22, 74, 73, 23, 71, 73, 23, 65, + 74, 24, 58, 74, 24, 51, 74, 26, 43, 74, 27, 35, + 74, 28, 26, 75, 30, 18, 75, 32, 9, 75, 34, 1, + 76, 37, -7, 76, 39, -15, 77, 42, -23, 77, 45, -31, + 74, 20, 77, 74, 20, 75, 74, 20, 71, 74, 21, 66, + 74, 22, 59, 75, 22, 52, 75, 23, 44, 75, 25, 36, + 75, 26, 28, 76, 28, 19, 76, 30, 11, 76, 32, 2, + 77, 34, -6, 77, 37, -14, 78, 40, -22, 78, 43, -30, + 75, 18, 78, 75, 18, 76, 75, 18, 72, 75, 19, 67, + 75, 19, 60, 76, 20, 53, 76, 21, 45, 76, 23, 38, + 76, 24, 29, 76, 26, 21, 77, 28, 12, 77, 30, 4, + 78, 32, -4, 78, 35, -13, 79, 38, -21, 79, 41, -29, + 76, 16, 78, 76, 16, 76, 76, 16, 73, 76, 17, 68, + 76, 17, 61, 76, 18, 54, 77, 19, 47, 77, 20, 39, + 77, 22, 30, 77, 24, 22, 78, 26, 13, 78, 28, 5, + 79, 30, -3, 79, 33, -11, 79, 36, -19, 80, 39, -28, + 77, 14, 79, 77, 14, 77, 77, 14, 74, 77, 15, 68, + 77, 15, 62, 77, 16, 55, 78, 17, 48, 78, 18, 40, + 78, 20, 31, 78, 21, 23, 79, 23, 15, 79, 26, 6, + 79, 28, -2, 80, 31, -10, 80, 34, -18, 81, 37, -26, + 78, 12, 80, 78, 12, 78, 78, 12, 75, 78, 12, 69, + 78, 13, 63, 78, 14, 57, 79, 15, 49, 79, 16, 41, + 79, 18, 33, 79, 19, 24, 80, 21, 16, 80, 24, 8, + 80, 26, 0, 81, 29, -9, 81, 32, -17, 82, 34, -25, + 79, 10, 80, 79, 10, 79, 79, 10, 75, 79, 10, 70, + 79, 11, 64, 79, 12, 58, 79, 13, 50, 80, 14, 42, + 80, 16, 34, 80, 17, 26, 81, 19, 17, 81, 21, 9, + 81, 24, 1, 82, 26, -7, 82, 29, -15, 83, 32, -23, + 80, 7, 81, 80, 8, 79, 80, 8, 76, 80, 8, 71, + 80, 9, 65, 80, 10, 59, 80, 11, 51, 81, 12, 43, + 81, 14, 35, 81, 15, 27, 81, 17, 18, 82, 19, 10, + 82, 22, 2, 83, 24, -6, 83, 27, -14, 84, 30, -22, + 81, 5, 82, 81, 6, 80, 81, 6, 77, 81, 6, 72, + 81, 7, 66, 81, 8, 60, 81, 9, 52, 82, 10, 45, + 82, 11, 36, 82, 13, 28, 82, 15, 20, 83, 17, 12, + 83, 20, 3, 84, 22, -5, 84, 25, -13, 85, 28, -21, + 82, 3, 83, 82, 3, 81, 82, 4, 78, 82, 4, 73, + 82, 5, 67, 82, 6, 61, 82, 7, 53, 83, 8, 46, + 83, 9, 38, 83, 11, 30, 83, 13, 21, 84, 15, 13, + 84, 18, 5, 84, 20, -3, 85, 23, -11, 85, 26, -19, + 83, 1, 83, 83, 1, 81, 83, 2, 79, 83, 2, 74, + 83, 3, 68, 83, 4, 62, 83, 5, 54, 84, 6, 47, + 84, 7, 39, 84, 9, 31, 84, 11, 22, 85, 13, 14, + 85, 15, 6, 85, 18, -2, 86, 21, -10, 86, 24, -18, + 84, -1, 84, 84, -1, 82, 84, 0, 79, 84, 0, 75, + 84, 1, 69, 84, 1, 63, 84, 3, 56, 85, 4, 48, + 85, 5, 40, 85, 7, 32, 85, 9, 24, 86, 11, 16, + 86, 13, 7, 86, 16, -1, 87, 19, -8, 87, 22, -17, + 85, -3, 85, 85, -3, 83, 85, -2, 80, 85, -2, 76, + 85, -1, 70, 85, 0, 64, 85, 0, 57, 86, 2, 49, + 86, 3, 41, 86, 5, 34, 86, 7, 25, 87, 9, 17, + 87, 11, 9, 87, 14, 1, 88, 17, -7, 88, 20, -15, + 86, -5, 86, 86, -5, 84, 86, -5, 81, 86, -4, 77, + 86, -4, 71, 86, -3, 65, 87, -2, 58, 87, 0, 51, + 87, 1, 43, 87, 2, 35, 88, 4, 27, 88, 6, 19, + 88, 9, 11, 89, 11, 3, 89, 14, -5, 89, 17, -13, + 87, -7, 86, 87, -7, 85, 87, -7, 82, 87, -6, 78, + 87, -6, 72, 88, -5, 66, 88, -4, 59, 88, -2, 52, + 88, -1, 44, 88, 0, 36, 89, 2, 28, 89, 4, 20, + 89, 7, 12, 90, 9, 4, 90, 12, -4, 90, 15, -12, + 88, -9, 87, 88, -9, 85, 88, -9, 83, 88, -8, 79, + 88, -8, 73, 89, -7, 67, 89, -6, 60, 89, -4, 53, + 89, -3, 45, 89, -1, 38, 90, 0, 29, 90, 2, 22, + 90, 5, 13, 91, 7, 5, 91, 10, -2, 91, 13, -11, + 89, -11, 88, 89, -11, 86, 89, -11, 84, 89, -10, 79, + 89, -10, 74, 90, -9, 68, 90, -8, 61, 90, -6, 54, + 90, -5, 47, 90, -3, 39, 91, -1, 31, 91, 0, 23, + 91, 2, 15, 91, 5, 7, 92, 8, -1, 92, 11, -9, + 90, -13, 89, 90, -13, 87, 90, -13, 84, 90, -12, 80, + 90, -12, 75, 91, -11, 69, 91, -10, 63, 91, -8, 56, + 91, -7, 48, 91, -5, 40, 92, -3, 32, 92, -1, 24, + 92, 0, 16, 92, 3, 8, 93, 6, 0, 93, 9, -8, + 91, -15, 89, 91, -15, 88, 91, -15, 85, 91, -14, 81, + 91, -14, 76, 92, -13, 70, 92, -12, 64, 92, -10, 57, + 92, -9, 49, 92, -7, 42, 93, -5, 33, 93, -3, 26, + 93, -1, 18, 93, 1, 10, 94, 4, 2, 94, 7, -6, + 92, -17, 90, 92, -17, 88, 92, -17, 86, 92, -16, 82, + 93, -15, 77, 93, -15, 71, 93, -14, 65, 93, -12, 58, + 93, -11, 50, 93, -9, 43, 94, -7, 35, 94, -5, 27, + 94, -3, 19, 94, 0, 11, 95, 2, 3, 95, 5, -5, + 93, -19, 91, 93, -19, 89, 93, -19, 87, 93, -18, 83, + 94, -17, 78, 94, -17, 72, 94, -15, 66, 94, -14, 59, + 94, -13, 52, 94, -11, 44, 95, -9, 36, 95, -7, 28, + 95, -5, 20, 95, -2, 12, 96, 0, 4, 96, 2, -3, + 94, -21, 92, 94, -21, 90, 94, -20, 88, 95, -20, 84, + 95, -19, 79, 95, -19, 74, 95, -17, 67, 95, -16, 60, + 95, -15, 53, 95, -13, 46, 96, -11, 37, 96, -9, 30, + 96, -7, 22, 96, -4, 14, 97, -2, 6, 97, 0, -2, + 95, -23, 92, 95, -23, 91, 96, -22, 89, 96, -22, 85, + 96, -21, 80, 96, -20, 75, 96, -19, 68, 96, -18, 62, + 96, -17, 54, 96, -15, 47, 97, -13, 39, 97, -11, 31, + 97, -9, 23, 97, -6, 15, 98, -4, 7, 98, -1, 0, + 97, -25, 93, 97, -25, 92, 97, -24, 89, 97, -24, 86, + 97, -23, 81, 97, -22, 76, 97, -21, 69, 97, -20, 63, + 97, -19, 55, 97, -17, 48, 98, -15, 40, 98, -13, 33, + 98, -11, 25, 98, -8, 17, 99, -6, 9, 99, -3, 1, + 53, 80, 66, 53, 80, 61, 53, 80, 55, 53, 81, 46, + 54, 81, 37, 54, 82, 28, 54, 83, 18, 55, 84, 9, + 55, 85, 0, 55, 86, -9, 56, 88, -18, 57, 89, -27, + 57, 91, -35, 58, 93, -43, 59, 95, -51, 60, 97, -59, + 53, 80, 66, 53, 80, 61, 53, 80, 55, 54, 80, 46, + 54, 81, 37, 54, 82, 28, 54, 82, 18, 55, 83, 9, + 55, 85, 0, 56, 86, -9, 56, 87, -18, 57, 89, -26, + 57, 91, -35, 58, 93, -43, 59, 95, -51, 60, 97, -59, + 53, 80, 66, 53, 80, 61, 54, 80, 55, 54, 80, 46, + 54, 81, 37, 54, 81, 28, 54, 82, 18, 55, 83, 9, + 55, 84, 0, 56, 86, -9, 56, 87, -18, 57, 89, -26, + 58, 90, -35, 58, 92, -43, 59, 95, -51, 60, 97, -59, + 54, 79, 66, 54, 79, 61, 54, 80, 55, 54, 80, 46, + 54, 80, 37, 54, 81, 28, 54, 82, 18, 55, 83, 9, + 55, 84, 0, 56, 85, -9, 56, 87, -18, 57, 88, -26, + 58, 90, -34, 58, 92, -43, 59, 94, -51, 60, 97, -58, + 54, 79, 66, 54, 79, 61, 54, 79, 55, 54, 80, 46, + 54, 80, 37, 54, 81, 28, 55, 82, 19, 55, 83, 9, + 55, 84, 0, 56, 85, -8, 56, 87, -18, 57, 88, -26, + 58, 90, -34, 58, 92, -43, 59, 94, -51, 60, 96, -58, + 54, 79, 66, 54, 79, 61, 54, 79, 55, 54, 79, 47, + 54, 80, 38, 54, 80, 28, 55, 81, 19, 55, 82, 10, + 56, 83, 0, 56, 85, -8, 57, 86, -17, 57, 88, -26, + 58, 90, -34, 59, 92, -42, 59, 94, -50, 60, 96, -58, + 54, 78, 66, 54, 78, 62, 54, 78, 55, 54, 79, 47, + 54, 79, 38, 55, 80, 29, 55, 81, 19, 55, 82, 10, + 56, 83, 0, 56, 84, -8, 57, 86, -17, 57, 87, -26, + 58, 89, -34, 59, 91, -42, 60, 93, -50, 60, 96, -58, + 54, 78, 67, 54, 78, 62, 54, 78, 55, 54, 78, 47, + 55, 79, 38, 55, 79, 29, 55, 80, 19, 55, 81, 10, + 56, 82, 1, 56, 84, -8, 57, 85, -17, 57, 87, -25, + 58, 89, -34, 59, 91, -42, 60, 93, -50, 61, 95, -58, + 54, 77, 67, 54, 77, 62, 54, 77, 56, 55, 78, 47, + 55, 78, 38, 55, 79, 29, 55, 80, 19, 56, 81, 10, + 56, 82, 1, 57, 83, -7, 57, 85, -17, 58, 86, -25, + 58, 88, -33, 59, 90, -42, 60, 92, -50, 61, 95, -57, + 55, 76, 67, 55, 76, 62, 55, 77, 56, 55, 77, 47, + 55, 77, 39, 55, 78, 29, 56, 79, 20, 56, 80, 11, + 56, 81, 1, 57, 82, -7, 57, 84, -16, 58, 86, -25, + 59, 87, -33, 59, 89, -41, 60, 92, -49, 61, 94, -57, + 55, 75, 67, 55, 76, 62, 55, 76, 56, 55, 76, 48, + 55, 77, 39, 55, 77, 30, 56, 78, 20, 56, 79, 11, + 57, 80, 2, 57, 82, -7, 58, 83, -16, 58, 85, -24, + 59, 87, -33, 60, 89, -41, 60, 91, -49, 61, 93, -57, + 55, 74, 67, 55, 75, 62, 55, 75, 56, 55, 75, 48, + 56, 76, 39, 56, 76, 30, 56, 77, 21, 56, 78, 12, + 57, 79, 2, 57, 81, -6, 58, 82, -15, 58, 84, -24, + 59, 86, -32, 60, 88, -40, 61, 90, -48, 62, 92, -56, + 56, 73, 67, 56, 74, 63, 56, 74, 57, 56, 74, 48, + 56, 75, 40, 56, 75, 31, 56, 76, 21, 57, 77, 12, + 57, 78, 3, 58, 80, -6, 58, 81, -15, 59, 83, -23, + 59, 85, -32, 60, 87, -40, 61, 89, -48, 62, 92, -56, + 56, 72, 67, 56, 73, 63, 56, 73, 57, 56, 73, 49, + 56, 74, 40, 57, 74, 31, 57, 75, 22, 57, 76, 13, + 58, 78, 3, 58, 79, -5, 59, 80, -14, 59, 82, -23, + 60, 84, -31, 60, 86, -40, 61, 88, -47, 62, 91, -55, + 56, 71, 67, 56, 72, 63, 56, 72, 57, 56, 72, 49, + 57, 73, 41, 57, 73, 32, 57, 74, 22, 57, 75, 13, + 58, 76, 4, 58, 78, -5, 59, 79, -14, 59, 81, -22, + 60, 83, -31, 61, 85, -39, 62, 88, -47, 62, 90, -55, + 57, 70, 68, 57, 70, 63, 57, 71, 57, 57, 71, 50, + 57, 72, 41, 57, 72, 32, 58, 73, 23, 58, 74, 14, + 58, 75, 4, 59, 77, -4, 59, 78, -13, 60, 80, -22, + 60, 82, -30, 61, 84, -38, 62, 87, -46, 63, 89, -54, + 57, 69, 68, 57, 69, 64, 57, 69, 58, 57, 70, 50, + 57, 70, 42, 58, 71, 33, 58, 72, 23, 58, 73, 14, + 59, 74, 5, 59, 76, -4, 60, 77, -13, 60, 79, -21, + 61, 81, -29, 62, 83, -38, 62, 85, -46, 63, 88, -54, + 58, 68, 68, 58, 68, 64, 58, 68, 58, 58, 69, 51, + 58, 69, 42, 58, 70, 33, 58, 71, 24, 59, 72, 15, + 59, 73, 5, 60, 74, -3, 60, 76, -12, 61, 78, -21, + 61, 80, -29, 62, 82, -37, 63, 84, -45, 64, 87, -53, + 58, 66, 68, 58, 67, 64, 58, 67, 58, 58, 67, 51, + 58, 68, 43, 59, 68, 34, 59, 69, 24, 59, 70, 15, + 60, 72, 6, 60, 73, -2, 61, 75, -11, 61, 77, -20, + 62, 79, -28, 62, 81, -37, 63, 83, -44, 64, 85, -52, + 59, 65, 68, 59, 65, 65, 59, 65, 59, 59, 66, 52, + 59, 66, 43, 59, 67, 34, 59, 68, 25, 60, 69, 16, + 60, 70, 7, 61, 72, -2, 61, 73, -11, 62, 75, -19, + 62, 77, -27, 63, 79, -36, 64, 82, -44, 64, 84, -52, + 59, 64, 69, 59, 64, 65, 59, 64, 59, 59, 64, 52, + 59, 65, 44, 60, 66, 35, 60, 67, 26, 60, 68, 17, + 61, 69, 8, 61, 70, -1, 62, 72, -10, 62, 74, -18, + 63, 76, -27, 63, 78, -35, 64, 81, -43, 65, 83, -51, + 60, 62, 69, 60, 62, 65, 60, 62, 60, 60, 63, 53, + 60, 63, 44, 60, 64, 36, 60, 65, 26, 61, 66, 18, + 61, 67, 8, 62, 69, 0, 62, 71, -9, 63, 72, -18, + 63, 74, -26, 64, 77, -34, 65, 79, -42, 65, 82, -50, + 60, 60, 69, 60, 61, 66, 60, 61, 60, 60, 61, 53, + 61, 62, 45, 61, 63, 37, 61, 63, 27, 61, 65, 18, + 62, 66, 9, 62, 67, 0, 63, 69, -8, 63, 71, -17, + 64, 73, -25, 64, 75, -34, 65, 78, -41, 66, 80, -49, + 61, 59, 70, 61, 59, 66, 61, 59, 61, 61, 60, 54, + 61, 60, 46, 61, 61, 37, 62, 62, 28, 62, 63, 19, + 62, 64, 10, 63, 66, 1, 63, 67, -8, 64, 69, -16, + 64, 71, -24, 65, 74, -33, 66, 76, -41, 66, 79, -49, + 61, 57, 70, 62, 57, 67, 62, 58, 61, 62, 58, 55, + 62, 59, 47, 62, 59, 38, 62, 60, 29, 63, 61, 20, + 63, 63, 11, 63, 64, 2, 64, 66, -7, 64, 68, -15, + 65, 70, -23, 65, 72, -32, 66, 75, -40, 67, 77, -48, + 62, 55, 70, 62, 56, 67, 62, 56, 62, 62, 56, 55, + 62, 57, 47, 63, 58, 39, 63, 58, 30, 63, 60, 21, + 64, 61, 12, 64, 62, 3, 64, 64, -6, 65, 66, -14, + 65, 68, -22, 66, 71, -31, 67, 73, -39, 67, 76, -47, + 63, 54, 71, 63, 54, 68, 63, 54, 62, 63, 54, 56, + 63, 55, 48, 63, 56, 40, 64, 57, 30, 64, 58, 22, + 64, 59, 13, 65, 61, 4, 65, 63, -5, 66, 64, -13, + 66, 67, -21, 67, 69, -30, 67, 71, -38, 68, 74, -46, + 64, 52, 71, 64, 52, 68, 64, 52, 63, 64, 53, 57, + 64, 53, 49, 64, 54, 40, 64, 55, 31, 65, 56, 23, + 65, 57, 14, 65, 59, 5, 66, 61, -4, 66, 63, -12, + 67, 65, -20, 67, 67, -29, 68, 70, -37, 69, 72, -45, + 64, 50, 71, 64, 50, 69, 64, 50, 63, 64, 51, 57, + 65, 51, 49, 65, 52, 41, 65, 53, 32, 65, 54, 24, + 66, 56, 14, 66, 57, 6, 66, 59, -3, 67, 61, -11, + 67, 63, -19, 68, 65, -28, 69, 68, -36, 69, 71, -44, + 65, 48, 72, 65, 48, 69, 65, 49, 64, 65, 49, 58, + 65, 50, 50, 65, 50, 42, 66, 51, 33, 66, 52, 25, + 66, 54, 15, 67, 55, 7, 67, 57, -2, 68, 59, -10, + 68, 61, -18, 69, 64, -27, 69, 66, -35, 70, 69, -43, + 66, 46, 72, 66, 46, 70, 66, 47, 65, 66, 47, 59, + 66, 48, 51, 66, 48, 43, 66, 49, 34, 67, 50, 26, + 67, 52, 16, 67, 53, 8, 68, 55, -1, 68, 57, -9, + 69, 60, -17, 69, 62, -26, 70, 65, -34, 71, 67, -42, + 66, 44, 73, 66, 44, 70, 67, 45, 65, 67, 45, 59, + 67, 46, 52, 67, 46, 44, 67, 47, 35, 67, 49, 27, + 68, 50, 18, 68, 52, 9, 68, 53, 0, 69, 55, -8, + 69, 58, -16, 70, 60, -25, 71, 63, -33, 71, 65, -41, + 67, 42, 73, 67, 42, 71, 68, 42, 66, 68, 43, 60, + 68, 43, 53, 68, 44, 45, 68, 45, 36, 68, 46, 28, + 69, 48, 19, 69, 49, 10, 69, 51, 1, 70, 53, -7, + 70, 55, -15, 71, 58, -23, 72, 60, -31, 72, 63, -40, + 68, 40, 74, 68, 40, 71, 68, 40, 67, 68, 41, 61, + 69, 41, 54, 69, 42, 46, 69, 43, 37, 69, 44, 29, + 69, 46, 20, 70, 47, 11, 70, 49, 3, 71, 51, -6, + 71, 53, -14, 72, 56, -22, 72, 58, -30, 73, 61, -38, + 69, 38, 74, 69, 38, 72, 69, 38, 67, 69, 39, 62, + 69, 39, 55, 70, 40, 47, 70, 41, 38, 70, 42, 30, + 70, 44, 21, 71, 45, 13, 71, 47, 4, 71, 49, -4, + 72, 51, -13, 72, 54, -21, 73, 57, -29, 74, 59, -37, + 70, 36, 75, 70, 36, 73, 70, 36, 68, 70, 37, 62, + 70, 37, 55, 70, 38, 48, 71, 39, 39, 71, 40, 31, + 71, 42, 22, 71, 43, 14, 72, 45, 5, 72, 47, -3, + 73, 49, -11, 73, 52, -20, 74, 55, -28, 74, 57, -36, + 71, 34, 75, 71, 34, 73, 71, 34, 69, 71, 35, 63, + 71, 35, 56, 71, 36, 49, 71, 37, 40, 72, 38, 32, + 72, 40, 23, 72, 41, 15, 73, 43, 6, 73, 45, -2, + 73, 47, -10, 74, 50, -19, 75, 53, -27, 75, 55, -35, + 72, 32, 76, 72, 32, 74, 72, 32, 70, 72, 33, 64, + 72, 33, 57, 72, 34, 50, 72, 35, 41, 72, 36, 33, + 73, 38, 24, 73, 39, 16, 73, 41, 7, 74, 43, -1, + 74, 45, -9, 75, 48, -18, 75, 51, -26, 76, 53, -34, + 72, 30, 76, 73, 30, 74, 73, 30, 70, 73, 30, 65, + 73, 31, 58, 73, 32, 51, 73, 33, 43, 73, 34, 34, + 74, 35, 26, 74, 37, 17, 74, 39, 8, 75, 41, 0, + 75, 43, -8, 76, 46, -16, 76, 49, -24, 77, 51, -33, + 73, 28, 77, 73, 28, 75, 73, 28, 71, 74, 28, 65, + 74, 29, 59, 74, 30, 52, 74, 31, 44, 74, 32, 35, + 74, 33, 27, 75, 35, 18, 75, 37, 10, 75, 39, 1, + 76, 41, -7, 76, 44, -15, 77, 47, -23, 78, 49, -31, + 74, 25, 78, 74, 26, 76, 74, 26, 72, 74, 26, 66, + 75, 27, 60, 75, 28, 53, 75, 29, 45, 75, 30, 37, + 75, 31, 28, 76, 33, 20, 76, 35, 11, 76, 37, 3, + 77, 39, -5, 77, 42, -14, 78, 44, -22, 78, 47, -30, + 75, 23, 78, 75, 24, 76, 75, 24, 73, 75, 24, 67, + 75, 25, 61, 76, 26, 54, 76, 27, 46, 76, 28, 38, + 76, 29, 29, 76, 31, 21, 77, 33, 12, 77, 35, 4, + 78, 37, -4, 78, 40, -13, 79, 42, -21, 79, 45, -29, + 76, 21, 79, 76, 21, 77, 76, 22, 73, 76, 22, 68, + 76, 23, 62, 76, 24, 55, 77, 25, 47, 77, 26, 39, + 77, 27, 30, 77, 29, 22, 78, 31, 13, 78, 33, 5, + 78, 35, -3, 79, 38, -11, 79, 40, -19, 80, 43, -27, + 77, 19, 79, 77, 19, 78, 77, 20, 74, 77, 20, 69, + 77, 21, 62, 77, 21, 56, 78, 22, 48, 78, 24, 40, + 78, 25, 31, 78, 27, 23, 79, 29, 15, 79, 31, 6, + 79, 33, -2, 80, 35, -10, 80, 38, -18, 81, 41, -26, + 78, 17, 80, 78, 17, 78, 78, 18, 75, 78, 18, 69, + 78, 19, 63, 78, 19, 57, 78, 20, 49, 79, 22, 41, + 79, 23, 33, 79, 25, 25, 80, 26, 16, 80, 29, 8, + 80, 31, 0, 81, 33, -9, 81, 36, -17, 82, 39, -25, + 79, 15, 81, 79, 15, 79, 79, 15, 76, 79, 16, 70, + 79, 16, 64, 79, 17, 58, 79, 18, 50, 80, 19, 42, + 80, 21, 34, 80, 22, 26, 80, 24, 17, 81, 26, 9, + 81, 29, 0, 82, 31, -7, 82, 34, -15, 83, 37, -24, + 80, 13, 81, 80, 13, 80, 80, 13, 76, 80, 14, 71, + 80, 14, 65, 80, 15, 59, 80, 16, 51, 81, 17, 43, + 81, 19, 35, 81, 20, 27, 81, 22, 18, 82, 24, 10, + 82, 27, 2, 83, 29, -6, 83, 32, -14, 83, 35, -22, + 81, 11, 82, 81, 11, 80, 81, 11, 77, 81, 12, 72, + 81, 12, 66, 81, 13, 60, 81, 14, 52, 81, 15, 45, + 82, 17, 36, 82, 18, 28, 82, 20, 20, 83, 22, 12, + 83, 25, 3, 83, 27, -5, 84, 30, -13, 84, 33, -21, + 82, 9, 83, 82, 9, 81, 82, 9, 78, 82, 10, 73, + 82, 10, 67, 82, 11, 61, 82, 12, 53, 82, 13, 46, + 83, 15, 38, 83, 16, 30, 83, 18, 21, 84, 20, 13, + 84, 23, 4, 84, 25, -3, 85, 28, -11, 85, 31, -20, + 83, 7, 83, 83, 7, 82, 83, 7, 79, 83, 8, 74, + 83, 8, 68, 83, 9, 62, 83, 10, 54, 83, 11, 47, + 84, 13, 39, 84, 14, 31, 84, 16, 22, 84, 18, 14, + 85, 20, 6, 85, 23, -2, 86, 26, -10, 86, 29, -18, + 84, 5, 84, 84, 5, 82, 84, 5, 80, 84, 5, 75, + 84, 6, 69, 84, 7, 63, 84, 8, 55, 84, 9, 48, + 85, 10, 40, 85, 12, 32, 85, 14, 24, 85, 16, 16, + 86, 18, 7, 86, 21, -1, 87, 24, -9, 87, 26, -17, + 85, 3, 85, 85, 3, 83, 85, 3, 80, 85, 3, 76, + 85, 4, 70, 85, 5, 64, 85, 6, 57, 85, 7, 49, + 86, 8, 41, 86, 10, 33, 86, 12, 25, 86, 14, 17, + 87, 16, 9, 87, 19, 1, 88, 22, -7, 88, 24, -15, + 86, 1, 86, 86, 1, 84, 86, 1, 81, 86, 1, 77, + 86, 2, 71, 86, 3, 65, 86, 4, 58, 86, 5, 50, + 87, 6, 42, 87, 8, 35, 87, 10, 26, 87, 12, 18, + 88, 14, 10, 88, 17, 2, 88, 19, -6, 89, 22, -14, + 87, -2, 86, 87, -2, 85, 87, -1, 82, 87, -1, 78, + 87, 0, 72, 87, 0, 66, 87, 1, 59, 88, 2, 52, + 88, 4, 44, 88, 5, 36, 88, 7, 28, 89, 9, 20, + 89, 12, 12, 89, 14, 4, 90, 17, -4, 90, 20, -12, + 88, -4, 87, 88, -4, 85, 88, -3, 83, 88, -3, 79, + 88, -2, 73, 88, -1, 67, 88, 0, 60, 89, 0, 53, + 89, 2, 45, 89, 3, 37, 89, 5, 29, 90, 7, 21, + 90, 9, 13, 90, 12, 5, 91, 15, -3, 91, 18, -11, + 89, -6, 88, 89, -6, 86, 89, -5, 84, 89, -5, 79, + 89, -4, 74, 89, -3, 68, 89, -2, 61, 90, -1, 54, + 90, 0, 46, 90, 1, 39, 90, 3, 31, 90, 5, 23, + 91, 7, 14, 91, 10, 6, 92, 13, -1, 92, 15, -9, + 90, -8, 89, 90, -8, 87, 90, -7, 84, 90, -7, 80, + 90, -6, 75, 90, -5, 69, 90, -4, 62, 91, -3, 55, + 91, -2, 48, 91, 0, 40, 91, 1, 32, 91, 3, 24, + 92, 5, 16, 92, 8, 8, 93, 11, 0, 93, 13, -8, + 91, -10, 89, 91, -10, 88, 91, -9, 85, 91, -9, 81, + 91, -8, 76, 91, -7, 70, 91, -6, 63, 92, -5, 57, + 92, -4, 49, 92, -2, 41, 92, 0, 33, 92, 1, 25, + 93, 3, 17, 93, 6, 9, 94, 8, 1, 94, 11, -7, + 92, -12, 90, 92, -12, 88, 92, -11, 86, 92, -11, 82, + 92, -10, 77, 92, -9, 71, 92, -8, 65, 93, -7, 58, + 93, -6, 50, 93, -4, 43, 93, -2, 34, 93, 0, 27, + 94, 1, 19, 94, 4, 11, 94, 6, 3, 95, 9, -5, + 93, -14, 91, 93, -14, 89, 93, -13, 87, 93, -13, 83, + 93, -12, 78, 93, -11, 72, 93, -10, 66, 94, -9, 59, + 94, -8, 51, 94, -6, 44, 94, -4, 36, 94, -2, 28, + 95, 0, 20, 95, 2, 12, 95, 4, 4, 96, 7, -4, + 94, -16, 92, 94, -16, 90, 94, -15, 88, 94, -15, 84, + 94, -14, 79, 94, -13, 73, 94, -12, 67, 95, -11, 60, + 95, -10, 53, 95, -8, 45, 95, -6, 37, 95, -4, 29, + 96, -2, 21, 96, 0, 13, 96, 2, 5, 97, 5, -2, + 95, -18, 92, 95, -18, 91, 95, -17, 89, 95, -17, 85, + 95, -16, 80, 95, -15, 74, 95, -14, 68, 96, -13, 61, + 96, -12, 54, 96, -10, 46, 96, -8, 38, 96, -6, 31, + 97, -4, 23, 97, -2, 15, 97, 0, 7, 98, 3, -1, + 96, -20, 93, 96, -19, 92, 96, -19, 89, 96, -19, 86, + 96, -18, 81, 96, -17, 75, 96, -16, 69, 97, -15, 62, + 97, -14, 55, 97, -12, 48, 97, -10, 40, 97, -8, 32, + 98, -6, 24, 98, -4, 16, 98, -1, 8, 99, 1, 0, + 97, -21, 94, 97, -21, 92, 97, -21, 90, 97, -21, 87, + 97, -20, 82, 97, -19, 76, 98, -18, 70, 98, -17, 64, + 98, -15, 56, 98, -14, 49, 98, -12, 41, 98, -10, 33, + 99, -8, 25, 99, -6, 18, 99, -3, 10, 100, 0, 2 +}; +#endif \ No newline at end of file diff --git a/github_source/minicv2/src/lbp.c b/github_source/minicv2/src/lbp.c new file mode 100644 index 0000000..d0f9463 --- /dev/null +++ b/github_source/minicv2/src/lbp.c @@ -0,0 +1,122 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * LBPu2 8,2 Operator. + * Note: The distance function uses weights optimized for face recognition. + * Note: See Timo Ahonen's "Face Recognition with Local Binary Patterns". + */ +#include +#include +#include + +#include "imlib.h" +#include "xalloc.h" +// #include "ff.h" +#include "ff_wrapper.h" +#ifdef IMLIB_ENABLE_FIND_LBP + +#define LBP_HIST_SIZE (59) //58 uniform hist + 1 +#define LBP_NUM_REGIONS (7) //7x7 regions +#define LBP_DESC_SIZE (LBP_NUM_REGIONS*LBP_NUM_REGIONS*LBP_HIST_SIZE) + +const static int8_t lbp_weights [49]= { + 2, 1, 1, 1, 1, 1, 2, + 2, 4, 4, 1, 4, 4, 2, + 1, 1, 1, 0, 1, 1, 1, + 0, 1, 1, 0, 1, 1, 0, + 0, 1, 1, 1, 1, 1, 0, + 0, 1, 1, 2, 1, 1, 0, + 0, 1, 1, 1, 1, 1, 0, +}; + +const static uint8_t uniform_tbl[256] = { + 0, 1, 2, 3, 4, 58, 5, 6, 7, 58, 58, 58, 8, 58, 9, 10, + 11, 58, 58, 58, 58, 58, 58, 58, 12, 58, 58, 58, 13, 58, 14, 15, + 16, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, + 17, 58, 58, 58, 58, 58, 58, 58, 18, 58, 58, 58, 19, 58, 20, 21, + 22, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, + 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, + 23, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, + 24, 58, 58, 58, 58, 58, 58, 58, 25, 58, 58, 58, 26, 58, 27, 28, + 29, 30, 58, 31, 58, 58, 58, 32, 58, 58, 58, 58, 58, 58, 58, 33, + 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 34, + 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, + 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 35, + 36, 37, 58, 38, 58, 58, 58, 39, 58, 58, 58, 58, 58, 58, 58, 40, + 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 58, 41, + 42, 43, 58, 44, 58, 58, 58, 45, 58, 58, 58, 58, 58, 58, 58, 46, + 47, 48, 58, 49, 58, 58, 58, 50, 51, 52, 58, 53, 54, 55, 56, 57 +}; + +uint8_t *imlib_lbp_desc(image_t *image, rectangle_t *roi) +{ + int s = image->w; //stride + int RX = roi->w/LBP_NUM_REGIONS; + int RY = roi->h/LBP_NUM_REGIONS; + uint8_t *data = image->data; + uint8_t *desc = xalloc0(LBP_DESC_SIZE); + + for (int y=roi->y; y<(roi->y+roi->h)-3; y++) { + int y_idx = ((y-roi->y)/RY)*LBP_NUM_REGIONS; + for (int x=roi->x; x<(roi->x+roi->w)-3; x++) { + uint8_t lbp=0; + uint8_t p = data[(y+1)*s+x+1]; + int hist_idx = y_idx+(x-roi->x)/RX; + + lbp |= (data[(y+0)*s+x+0] >= p) << 0; + lbp |= (data[(y+0)*s+x+1] >= p) << 1; + lbp |= (data[(y+0)*s+x+2] >= p) << 2; + lbp |= (data[(y+1)*s+x+2] >= p) << 3; + lbp |= (data[(y+2)*s+x+2] >= p) << 4; + lbp |= (data[(y+2)*s+x+1] >= p) << 5; + lbp |= (data[(y+2)*s+x+0] >= p) << 6; + lbp |= (data[(y+1)*s+x+0] >= p) << 7; + + desc[hist_idx*LBP_HIST_SIZE+uniform_tbl[lbp]]++; + } + } + return desc; +} + +int imlib_lbp_desc_distance(uint8_t *d0, uint8_t *d1) +{ + uint32_t sum = 0; + for (int i=0; i + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Line functions. + */ +#include "imlib.h" + +static void pixel_magnitude(image_t *ptr, int x, int y, int *theta, uint32_t *mag) +{ + switch (ptr->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y); + int pixel; // Sobel Algorithm Below + int x_acc = 0; + int y_acc = 0; + + if (y != 0) row_ptr -= ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, IM_MAX(x - 1, 0))); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, IM_MIN(x + 1, ptr->w - 1))); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + if (y != 0) row_ptr += ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, IM_MAX(x - 1, 0))); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, IM_MIN(x + 1, ptr->w - 1))); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + if (y != (ptr->h - 1)) row_ptr += ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, IM_MAX(x - 1, 0))); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, IM_MIN(x + 1, ptr->w - 1))); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + if (y != (ptr->h - 1)) row_ptr -= ((ptr->w + UINT32_T_MASK) >> UINT32_T_SHIFT); + + *theta = fast_roundf((x_acc ? fast_atan2f(y_acc, x_acc) : 1.570796f) * 57.295780) % 180; // * (180 / PI) + if (*theta < 0) *theta += 180; + *mag = fast_roundf(fast_sqrtf((x_acc * x_acc) + (y_acc * y_acc))); + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y); + int pixel; // Sobel Algorithm Below... w/ Scharr... + int x_acc = 0; + int y_acc = 0; + + if (y != 0) row_ptr -= ptr->w; + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, IM_MAX(x - 1, 0)); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, IM_MIN(x + 1, ptr->w - 1)); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + if (y != 0) row_ptr += ptr->w; + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, IM_MAX(x - 1, 0)); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, IM_MIN(x + 1, ptr->w - 1)); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + if (y != (ptr->h - 1)) row_ptr += ptr->w; + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, IM_MAX(x - 1, 0)); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, IM_MIN(x + 1, ptr->w - 1)); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + if (y != (ptr->h - 1)) row_ptr -= ptr->w; + + *theta = fast_roundf((x_acc ? fast_atan2f(y_acc, x_acc) : 1.570796f) * 57.295780) % 180; // * (180 / PI) + if (*theta < 0) *theta += 180; + *mag = fast_roundf(fast_sqrtf((x_acc * x_acc) + (y_acc * y_acc))); + break; + } + case PIXFORMAT_RGB565: { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y); + int pixel; // Sobel Algorithm Below... w/ Scharr... + int x_acc = 0; + int y_acc = 0; + + if (y != 0) row_ptr -= ptr->w; + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, IM_MAX(x - 1, 0))); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, IM_MIN(x + 1, ptr->w - 1))); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + if (y != 0) row_ptr += ptr->w; + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, IM_MAX(x - 1, 0))); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, IM_MIN(x + 1, ptr->w - 1))); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + if (y != (ptr->h - 1)) row_ptr += ptr->w; + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, IM_MAX(x - 1, 0))); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, IM_MIN(x + 1, ptr->w - 1))); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + if (y != (ptr->h - 1)) row_ptr -= ptr->w; + + *theta = fast_roundf((x_acc ? fast_atan2f(y_acc, x_acc) : 1.570796f) * 57.295780) % 180; // * (180 / PI) + if (*theta < 0) *theta += 180; + *mag = fast_roundf(fast_sqrtf((x_acc * x_acc) + (y_acc * y_acc))); + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y); + int pixel; // Sobel Algorithm Below... w/ Scharr... + int x_acc = 0; + int y_acc = 0; + + if (y != 0) row_ptr -= ptr->w; + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, IM_MAX(x - 1, 0))); + x_acc += pixel * +1; // x[0,0] -> pixel * +1 + y_acc += pixel * +1; // y[0,0] -> pixel * +1 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + // x[0,1] -> pixel * 0 + y_acc += pixel * +2; // y[0,1] -> pixel * +2 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, IM_MIN(x + 1, ptr->w - 1))); + x_acc += pixel * -1; // x[0,2] -> pixel * -1 + y_acc += pixel * +1; // y[0,2] -> pixel * +1 + + if (y != 0) row_ptr += ptr->w; + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, IM_MAX(x - 1, 0))); + x_acc += pixel * +2; // x[1,0] -> pixel * +2 + // y[1,0] -> pixel * 0 + + // pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + // x[1,1] -> pixel * 0 + // y[1,1] -> pixel * 0 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, IM_MIN(x + 1, ptr->w - 1))); + x_acc += pixel * -2; // x[1,2] -> pixel * -2 + // y[1,2] -> pixel * 0 + + if (y != (ptr->h - 1)) row_ptr += ptr->w; + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, IM_MAX(x - 1, 0))); + x_acc += pixel * +1; // x[2,0] -> pixel * +1 + y_acc += pixel * -1; // y[2,0] -> pixel * -1 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x)); + // x[2,1] -> pixel * 0 + y_acc += pixel * -2; // y[2,1] -> pixel * -2 + + pixel = COLOR_RGB888_TO_GRAYSCALE(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, IM_MIN(x + 1, ptr->w - 1))); + x_acc += pixel * -1; // x[2,2] -> pixel * -1 + y_acc += pixel * -1; // y[2,2] -> pixel * -1 + + if (y != (ptr->h - 1)) row_ptr -= ptr->w; + + *theta = fast_roundf((x_acc ? fast_atan2f(y_acc, x_acc) : 1.570796f) * 57.295780) % 180; // * (180 / PI) + if (*theta < 0) *theta += 180; + *mag = fast_roundf(fast_sqrtf((x_acc * x_acc) + (y_acc * y_acc))); + break; + } + default: { + break; + } + } +} + +// http://www.brackeen.com/vga/source/djgpp20/lines.c.html +// http://www.brackeen.com/vga/source/bc31/lines.c.html +static bool merge_line(line_t *big, line_t *small, unsigned int threshold) +{ + int dx = big->x2 - big->x1; // the horizontal distance of the line + int dy = big->y2 - big->y1; // the vertical distance of the line + int dxabs = abs(dx); + int dyabs = abs(dy); + int sdx = (dx < 0) ? -1 :((dx > 0) ? 1 : 0); + int sdy = (dy < 0) ? -1 :((dy > 0) ? 1 : 0); + int x = dyabs >> 1; // correct + int y = dxabs >> 1; // correct + int px = big->x1; + int py = big->y1; + + int x_diff_0 = (px - small->x1); + int y_diff_0 = (py - small->y1); + if (fast_roundf(fast_sqrtf((x_diff_0 * x_diff_0) + (y_diff_0 * y_diff_0))) <= threshold) return true; + int x_diff_1 = (px - small->x2); + int y_diff_1 = (py - small->y2); + if (fast_roundf(fast_sqrtf((x_diff_1 * x_diff_1) + (y_diff_1 * y_diff_1))) <= threshold) return true; + + if (dxabs >= dyabs) { // the line is more horizontal than vertical + for(int i = 0; i < dxabs; i++) { + y += dyabs; + + if (y >= dxabs) { + y -= dxabs; + py += sdy; + } + + px += sdx; + + x_diff_0 = (px - small->x1); + y_diff_0 = (py - small->y1); + if (fast_roundf(fast_sqrtf((x_diff_0 * x_diff_0) + (y_diff_0 * y_diff_0))) <= threshold) return true; + x_diff_1 = (px - small->x2); + y_diff_1 = (py - small->y2); + if (fast_roundf(fast_sqrtf((x_diff_1 * x_diff_1) + (y_diff_1 * y_diff_1))) <= threshold) return true; + } + } else { // the line is more vertical than horizontal + for(int i = 0; i < dyabs; i++) { + x += dxabs; + + if (x >= dyabs) { + x -= dyabs; + px += sdx; + } + + py += sdy; + + x_diff_0 = (px - small->x1); + y_diff_0 = (py - small->y1); + if (fast_roundf(fast_sqrtf((x_diff_0 * x_diff_0) + (y_diff_0 * y_diff_0))) <= threshold) return true; + x_diff_1 = (px - small->x2); + y_diff_1 = (py - small->y2); + if (fast_roundf(fast_sqrtf((x_diff_1 * x_diff_1) + (y_diff_1 * y_diff_1))) <= threshold) return true; + } + } + + return false; +} + +void merge_alot(list_t *out, int threshold, int theta_threshold) +{ + for (;;) { + bool merge_occured = false; + + list_t out_temp; + imlib_list_init(&out_temp, sizeof(find_lines_list_lnk_data_t)); + + while (list_size(out)) { + find_lines_list_lnk_data_t lnk_line; + list_pop_front(out, &lnk_line); + + for (size_t k = 0, l = list_size(out); k < l; k++) { + find_lines_list_lnk_data_t tmp_line; + list_pop_front(out, &tmp_line); + + int x_diff_0 = (lnk_line.line.x2 - lnk_line.line.x1); + int y_diff_0 = (lnk_line.line.y2 - lnk_line.line.y1); + int length_0 = fast_roundf(fast_sqrtf((x_diff_0 * x_diff_0) + (y_diff_0 * y_diff_0))); + + int x_diff_1 = (tmp_line.line.x2 - tmp_line.line.x1); + int y_diff_1 = (tmp_line.line.y2 - tmp_line.line.y1); + int length_1 = fast_roundf(fast_sqrtf((x_diff_1 * x_diff_1) + (y_diff_1 * y_diff_1))); + + int theta_diff = abs(lnk_line.theta - tmp_line.theta); + int theta_diff_2 = (theta_diff >= 90) ? (180 - theta_diff) : theta_diff; + + if ((theta_diff_2 <= theta_threshold) && merge_line((length_0 > length_1) ? + &lnk_line.line : &tmp_line.line, (length_0 <= length_1) ? &lnk_line.line : &tmp_line.line, threshold)) { + + if (abs(x_diff_0) >= abs(y_diff_0)) { // the line is more horizontal than vertical + if (x_diff_0 < 0) { // Make sure x slope is positive for the next part. + int temp_x = lnk_line.line.x1; + lnk_line.line.x1 = lnk_line.line.x2; + lnk_line.line.x2 = temp_x; + int temp_y = lnk_line.line.y1; + lnk_line.line.y1 = lnk_line.line.y2; + lnk_line.line.y2 = temp_y; + x_diff_0 = (lnk_line.line.x2 - lnk_line.line.x1); + y_diff_0 = (lnk_line.line.y2 - lnk_line.line.y1); + } + + if (x_diff_1 < 0) { // Make sure x slope is positive for the next part. + int temp_x = tmp_line.line.x1; + tmp_line.line.x1 = tmp_line.line.x2; + tmp_line.line.x2 = temp_x; + int temp_y = tmp_line.line.y1; + tmp_line.line.y1 = tmp_line.line.y2; + tmp_line.line.y2 = temp_y; + x_diff_1 = (tmp_line.line.x2 - tmp_line.line.x1); + y_diff_1 = (tmp_line.line.y2 - tmp_line.line.y1); + } + + if (length_0 > length_1) { + int x_min = IM_MIN(lnk_line.line.x1, tmp_line.line.x1); + int x_max = IM_MAX(lnk_line.line.x2, tmp_line.line.x2); + lnk_line.line.y1 = lnk_line.line.y1 - ((y_diff_0 * (lnk_line.line.x1 - x_min)) / x_diff_0); + lnk_line.line.x1 = x_min; + lnk_line.line.y2 = lnk_line.line.y2 + ((y_diff_0 * (x_max - lnk_line.line.x2)) / x_diff_0); + lnk_line.line.x2 = x_max; + } else { + int x_min = IM_MIN(tmp_line.line.x1, lnk_line.line.x1); + int x_max = IM_MAX(tmp_line.line.x2, lnk_line.line.x2); + lnk_line.line.y1 = tmp_line.line.y1 - ((y_diff_0 * (tmp_line.line.x1 - x_min)) / x_diff_0); + lnk_line.line.x1 = x_min; + lnk_line.line.y2 = tmp_line.line.y2 + ((y_diff_0 * (x_max - tmp_line.line.x2)) / x_diff_0); + lnk_line.line.x2 = x_max; + } + } else { // the line is more vertical than horizontal + if (y_diff_0 < 0) { // Make sure y slope is positive for the next part. + int temp_x = lnk_line.line.x1; + lnk_line.line.x1 = lnk_line.line.x2; + lnk_line.line.x2 = temp_x; + int temp_y = lnk_line.line.y1; + lnk_line.line.y1 = lnk_line.line.y2; + lnk_line.line.y2 = temp_y; + x_diff_0 = (lnk_line.line.x2 - lnk_line.line.x1); + y_diff_0 = (lnk_line.line.y2 - lnk_line.line.y1); + } + + if (y_diff_1 < 0) { // Make sure y slope is positive for the next part. + int temp_x = tmp_line.line.x1; + tmp_line.line.x1 = tmp_line.line.x2; + tmp_line.line.x2 = temp_x; + int temp_y = tmp_line.line.y1; + tmp_line.line.y1 = tmp_line.line.y2; + tmp_line.line.y2 = temp_y; + x_diff_1 = (tmp_line.line.x2 - tmp_line.line.x1); + y_diff_1 = (tmp_line.line.y2 - tmp_line.line.y1); + } + + if (length_0 > length_1) { + int y_min = IM_MIN(lnk_line.line.y1, tmp_line.line.y1); + int y_max = IM_MAX(lnk_line.line.y2, tmp_line.line.y2); + lnk_line.line.x1 = lnk_line.line.x1 - ((x_diff_0 * (lnk_line.line.y1 - y_min)) / y_diff_0); + lnk_line.line.y1 = y_min; + lnk_line.line.x2 = lnk_line.line.x2 + ((x_diff_0 * (y_max - lnk_line.line.y2)) / y_diff_0); + lnk_line.line.y2 = y_max; + } else { + int y_min = IM_MIN(tmp_line.line.y1, lnk_line.line.y1); + int y_max = IM_MAX(tmp_line.line.y2, lnk_line.line.y2); + lnk_line.line.x1 = tmp_line.line.x1 - ((x_diff_0 * (tmp_line.line.y1 - y_min)) / y_diff_0); + lnk_line.line.y1 = y_min; + lnk_line.line.x2 = tmp_line.line.x2 + ((x_diff_0 * (y_max - tmp_line.line.y2)) / y_diff_0); + lnk_line.line.y2 = y_max; + } + } + + merge_occured = true; + } else { + list_push_back(out, &tmp_line); + } + } + + list_push_back(&out_temp, &lnk_line); + } + + list_copy(out, &out_temp); + + if (!merge_occured) { + break; + } + } +} + +// http://www.brackeen.com/vga/source/djgpp20/lines.c.html +// http://www.brackeen.com/vga/source/bc31/lines.c.html +size_t trace_line(image_t *ptr, line_t *l, int *theta_buffer, uint32_t *mag_buffer, point_t *point_buffer) +{ + int dx = l->x2 - l->x1; // the horizontal distance of the line + int dy = l->y2 - l->y1; // the vertical distance of the line + int dxabs = abs(dx); + int dyabs = abs(dy); + int sdx = (dx < 0) ? -1 :((dx > 0) ? 1 : 0); + int sdy = (dy < 0) ? -1 :((dy > 0) ? 1 : 0); + int x = dyabs >> 1; // correct + int y = dxabs >> 1; // correct + int px = l->x1; + int py = l->y1; + + size_t index = 0; + + pixel_magnitude(ptr, px, py, theta_buffer + index, mag_buffer + index); + point_buffer[index++] = (point_t) {.x = px, .y = py}; + + if (dxabs >= dyabs) { // the line is more horizontal than vertical + for(int i = 0; i < dxabs; i++) { + y += dyabs; + + if (y >= dxabs) { + y -= dxabs; + py += sdy; + } + + px += sdx; + + pixel_magnitude(ptr, px, py, theta_buffer + index, mag_buffer + index); + point_buffer[index++] = (point_t) {.x = px, .y = py}; + } + } else { // the line is more vertical than horizontal + for(int i = 0; i < dyabs; i++) { + x += dxabs; + + if (x >= dyabs) { + x -= dyabs; + px += sdx; + } + + py += sdy; + + pixel_magnitude(ptr, px, py, theta_buffer + index, mag_buffer + index); + point_buffer[index++] = (point_t) {.x = px, .y = py}; + } + } + + return index; +} diff --git a/github_source/minicv2/src/lodepng.c b/github_source/minicv2/src/lodepng.c new file mode 100644 index 0000000..6b3a465 --- /dev/null +++ b/github_source/minicv2/src/lodepng.c @@ -0,0 +1,6503 @@ +/* +LodePNG version 20220109 + +Copyright (c) 2005-2022 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +The manual and changelog are in the header file "lodepng.h" +Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. +*/ + +#include "imlib.h" +#if defined(IMLIB_ENABLE_PNG_ENCODER) || defined(IMLIB_ENABLE_PNG_DECODER) +#undef CRC +#include "lodepng.h" + +#ifdef LODEPNG_COMPILE_DISK +#include /* LONG_MAX */ +#include /* file handling */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +#include /* allocations */ +#endif /* LODEPNG_COMPILE_ALLOCATORS */ + +#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ +#pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ +#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ +#endif /*_MSC_VER */ + +const char* LODEPNG_VERSION_STRING = "20220109"; + +/* +This source file is built up in the following large parts. The code sections +with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. +-Tools for C and common code for PNG and Zlib +-C Code for Zlib (huffman, deflate, ...) +-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) +-The C++ wrapper around all of the above +*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Tools for C, and common code for PNG and Zlib. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*The malloc, realloc and free functions defined here with "lodepng_" in front +of the name, so that you can easily change them to others related to your +platform if needed. Everything else in the code calls these. Pass +-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out +#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and +define them in your own project's source files without needing to change +lodepng source code. Don't forget to remove "static" if you copypaste them +from here.*/ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +static void* lodepng_malloc(size_t size) { +#ifdef LODEPNG_MAX_ALLOC + if(size > LODEPNG_MAX_ALLOC) return 0; +#endif + return malloc(size); +} + +/* NOTE: when realloc returns NULL, it leaves the original memory untouched */ +static void* lodepng_realloc(void* ptr, size_t new_size) { +#ifdef LODEPNG_MAX_ALLOC + if(new_size > LODEPNG_MAX_ALLOC) return 0; +#endif + return realloc(ptr, new_size); +} + +static void lodepng_free(void* ptr) { + free(ptr); +} +#else /*LODEPNG_COMPILE_ALLOCATORS*/ +/* TODO: support giving additional void* payload to the custom allocators */ +void* lodepng_malloc(size_t size); +void* lodepng_realloc(void* ptr, size_t new_size); +void lodepng_free(void* ptr); +#endif /*LODEPNG_COMPILE_ALLOCATORS*/ + +/* convince the compiler to inline a function, for use when this measurably improves performance */ +/* inline is not available in C90, but use it when supported by the compiler */ +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || (defined(__cplusplus) && (__cplusplus >= 199711L)) +#define LODEPNG_INLINE inline +#else +#define LODEPNG_INLINE /* not available */ +#endif + +/* restrict is not available in C90, but use it when supported by the compiler */ +#if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\ + (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \ + (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus)) +#define LODEPNG_RESTRICT __restrict +#else +#define LODEPNG_RESTRICT /* not available */ +#endif + +/* Replacements for C library functions such as memcpy and strlen, to support platforms +where a full C library is not available. The compiler can recognize them and compile +to something as fast. */ + +static void lodepng_memcpy(void* LODEPNG_RESTRICT dst, + const void* LODEPNG_RESTRICT src, size_t size) { + size_t i; + for(i = 0; i < size; i++) ((char*)dst)[i] = ((const char*)src)[i]; +} + +static void lodepng_memset(void* LODEPNG_RESTRICT dst, + int value, size_t num) { + size_t i; + for(i = 0; i < num; i++) ((char*)dst)[i] = (char)value; +} + +/* does not check memory out of bounds, do not use on untrusted data */ +static size_t lodepng_strlen(const char* a) { + const char* orig = a; + /* avoid warning about unused function in case of disabled COMPILE... macros */ + (void)(&lodepng_strlen); + while(*a) a++; + return (size_t)(a - orig); +} + +#define LODEPNG_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define LODEPNG_ABS(x) ((x) < 0 ? -(x) : (x)) + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER) +/* Safely check if adding two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_addofl(size_t a, size_t b, size_t* result) { + *result = a + b; /* Unsigned addition is well defined and safe in C90 */ + return *result < a; +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER)*/ + +#ifdef LODEPNG_COMPILE_DECODER +/* Safely check if multiplying two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_mulofl(size_t a, size_t b, size_t* result) { + *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */ + return (a != 0 && *result / a != b); +} + +#ifdef LODEPNG_COMPILE_ZLIB +/* Safely check if a + b > c, even if overflow could happen. */ +static int lodepng_gtofl(size_t a, size_t b, size_t c) { + size_t d; + if(lodepng_addofl(a, b, &d)) return 1; + return d > c; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +/* +Often in case of an error a value is assigned to a variable and then it breaks +out of a loop (to go to the cleanup phase of a function). This macro does that. +It makes the error handling code shorter and more readable. + +Example: if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83); +*/ +#define CERROR_BREAK(errorvar, code){\ + errorvar = code;\ + break;\ +} + +/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/*Set error var to the error code, and return it.*/ +#define CERROR_RETURN_ERROR(errorvar, code){\ + errorvar = code;\ + return code;\ +} + +/*Try the code, if it returns error, also return the error.*/ +#define CERROR_TRY_RETURN(call){\ + unsigned error = call;\ + if(error) return error;\ +} + +/*Set error var to the error code, and return from the void function.*/ +#define CERROR_RETURN(errorvar, code){\ + errorvar = code;\ + return;\ +} + +/* +About uivector, ucvector and string: +-All of them wrap dynamic arrays or text strings in a similar way. +-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. +-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. +-They're not used in the interface, only internally in this file as static functions. +-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. +*/ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER +/*dynamic vector of unsigned ints*/ +typedef struct uivector { + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) { + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + lodepng_free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_resize(uivector* p, size_t size) { + size_t allocsize = size * sizeof(unsigned); + if(allocsize > p->allocsize) { + size_t newsize = allocsize + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned*)data; + } + else return 0; /*error: not enough memory*/ + } + p->size = size; + return 1; /*success*/ +} + +static void uivector_init(uivector* p) { + p->data = NULL; + p->size = p->allocsize = 0; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_push_back(uivector* p, unsigned c) { + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +/*dynamic vector of unsigned chars*/ +typedef struct ucvector { + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_reserve(ucvector* p, size_t size) { + if(size > p->allocsize) { + size_t newsize = size + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned char*)data; + } + else return 0; /*error: not enough memory*/ + } + return 1; /*success*/ +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_resize(ucvector* p, size_t size) { + p->size = size; + return ucvector_reserve(p, size); +} + +static ucvector ucvector_init(unsigned char* buffer, size_t size) { + ucvector v; + v.data = buffer; + v.allocsize = v.size = size; + return v; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +/*free string pointer and set it to NULL*/ +static void string_cleanup(char** out) { + lodepng_free(*out); + *out = NULL; +} + +/*also appends null termination character*/ +static char* alloc_string_sized(const char* in, size_t insize) { + char* out = (char*)lodepng_malloc(insize + 1); + if(out) { + lodepng_memcpy(out, in, insize); + out[insize] = 0; + } + return out; +} + +/* dynamically allocates a new string with a copy of the null terminated input text */ +static char* alloc_string(const char* in) { + return alloc_string_sized(in, lodepng_strlen(in)); +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG) +static unsigned lodepng_read32bitInt(const unsigned char* buffer) { + return (((unsigned)buffer[0] << 24u) | ((unsigned)buffer[1] << 16u) | + ((unsigned)buffer[2] << 8u) | (unsigned)buffer[3]); +} +#endif /*defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG)*/ + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) +/*buffer must have at least 4 allocated bytes available*/ +static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) { + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +/* returns negative value on error. This should be pure C compatible, so no fstat. */ +static long lodepng_filesize(const char* filename) { + FILE* file; + long size; + file = fopen(filename, "rb"); + if(!file) return -1; + + if(fseek(file, 0, SEEK_END) != 0) { + fclose(file); + return -1; + } + + size = ftell(file); + /* It may give LONG_MAX as directory size, this is invalid for us. */ + if(size == LONG_MAX) size = -1; + + fclose(file); + return size; +} + +/* load file into buffer that already has the correct allocated size. Returns error code.*/ +static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename) { + FILE* file; + size_t readsize; + file = fopen(filename, "rb"); + if(!file) return 78; + + readsize = fread(out, 1, size, file); + fclose(file); + + if(readsize != size) return 78; + return 0; +} + +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) { + long size = lodepng_filesize(filename); + if(size < 0) return 78; + *outsize = (size_t)size; + + *out = (unsigned char*)lodepng_malloc((size_t)size); + if(!(*out) && size > 0) return 83; /*the above malloc failed*/ + + return lodepng_buffer_file(*out, (size_t)size, filename); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) { + FILE* file; + file = fopen(filename, "wb" ); + if(!file) return 79; + fwrite(buffer, 1, buffersize, file); + fclose(file); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER + +typedef struct { + ucvector* data; + unsigned char bp; /*ok to overflow, indicates bit pos inside byte*/ +} LodePNGBitWriter; + +static void LodePNGBitWriter_init(LodePNGBitWriter* writer, ucvector* data) { + writer->data = data; + writer->bp = 0; +} + +/*TODO: this ignores potential out of memory errors*/ +#define WRITEBIT(writer, bit){\ + /* append new byte */\ + if(((writer->bp) & 7u) == 0) {\ + if(!ucvector_resize(writer->data, writer->data->size + 1)) return;\ + writer->data->data[writer->data->size - 1] = 0;\ + }\ + (writer->data->data[writer->data->size - 1]) |= (bit << ((writer->bp) & 7u));\ + ++writer->bp;\ +} + +/* LSB of value is written first, and LSB of bytes is used first */ +static void writeBits(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + if(nbits == 1) { /* compiler should statically compile this case if nbits == 1 */ + WRITEBIT(writer, value); + } else { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + size_t i; + for(i = 0; i != nbits; ++i) { + WRITEBIT(writer, (unsigned char)((value >> i) & 1)); + } + } +} + +/* This one is to use for adding huffman symbol, the value bits are written MSB first */ +static void writeBitsReversed(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + size_t i; + for(i = 0; i != nbits; ++i) { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + WRITEBIT(writer, (unsigned char)((value >> (nbits - 1u - i)) & 1u)); + } +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +typedef struct { + const unsigned char* data; + size_t size; /*size of data in bytes*/ + size_t bitsize; /*size of data in bits, end of valid bp values, should be 8*size*/ + size_t bp; + unsigned buffer; /*buffer for reading bits. NOTE: 'unsigned' must support at least 32 bits*/ +} LodePNGBitReader; + +/* data size argument is in bytes. Returns error if size too large causing overflow */ +static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const unsigned char* data, size_t size) { + size_t temp; + reader->data = data; + reader->size = size; + /* size in bits, return error if overflow (if size_t is 32 bit this supports up to 500MB) */ + if(lodepng_mulofl(size, 8u, &reader->bitsize)) return 105; + /*ensure incremented bp can be compared to bitsize without overflow even when it would be incremented 32 too much and + trying to ensure 32 more bits*/ + if(lodepng_addofl(reader->bitsize, 64u, &temp)) return 105; + reader->bp = 0; + reader->buffer = 0; + return 0; /*ok*/ +} + +/* +ensureBits functions: +Ensures the reader can at least read nbits bits in one or more readBits calls, +safely even if not enough bits are available. +The nbits parameter is unused but is given for documentation purposes, error +checking for amount of bits must be done beforehand. +*/ + +/*See ensureBits documentation above. This one ensures up to 9 bits */ +static LODEPNG_INLINE void ensureBits9(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 1u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer = reader->data[start + 0]; + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/*See ensureBits documentation above. This one ensures up to 17 bits */ +static LODEPNG_INLINE void ensureBits17(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 2u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/*See ensureBits documentation above. This one ensures up to 25 bits */ +static LODEPNG_INLINE void ensureBits25(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 3u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/*See ensureBits documentation above. This one ensures up to 32 bits */ +static LODEPNG_INLINE void ensureBits32(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 4u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + reader->buffer |= (((unsigned)reader->data[start + 4] << 24u) << (8u - (reader->bp & 7u))); + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + if(start + 3u < size) reader->buffer |= ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + } + (void)nbits; +} + +/* Get bits without advancing the bit pointer. Must have enough bits available with ensureBits. Max nbits is 31. */ +static LODEPNG_INLINE unsigned peekBits(LodePNGBitReader* reader, size_t nbits) { + /* The shift allows nbits to be only up to 31. */ + return reader->buffer & ((1u << nbits) - 1u); +} + +/* Must have enough bits available with ensureBits */ +static LODEPNG_INLINE void advanceBits(LodePNGBitReader* reader, size_t nbits) { + reader->buffer >>= nbits; + reader->bp += nbits; +} + +/* Must have enough bits available with ensureBits */ +static LODEPNG_INLINE unsigned readBits(LodePNGBitReader* reader, size_t nbits) { + unsigned result = peekBits(reader, nbits); + advanceBits(reader, nbits); + return result; +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +static unsigned reverseBits(unsigned bits, unsigned num) { + /*TODO: implement faster lookup table based version when needed*/ + unsigned i, result = 0; + for(i = 0; i < num; i++) result |= ((bits >> (num - i - 1u)) & 1u) << i; + return result; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {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}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {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}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {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}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {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}; + +/*the order in which "code length alphabet code lengths" are stored as specified by deflate, out of this the huffman +tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +typedef struct HuffmanTree { + unsigned* codes; /*the huffman codes (bit patterns representing the symbols)*/ + unsigned* lengths; /*the lengths of the huffman codes*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ + /* for reading only */ + unsigned char* table_len; /*length of symbol from lookup table, or max length if secondary lookup needed*/ + unsigned short* table_value; /*value of symbol from lookup table, or pointer to secondary table if needed*/ +} HuffmanTree; + +static void HuffmanTree_init(HuffmanTree* tree) { + tree->codes = 0; + tree->lengths = 0; + tree->table_len = 0; + tree->table_value = 0; +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) { + lodepng_free(tree->codes); + lodepng_free(tree->lengths); + lodepng_free(tree->table_len); + lodepng_free(tree->table_value); +} + +/* amount of bits for first huffman table lookup (aka root bits), see HuffmanTree_makeTable and huffmanDecodeSymbol.*/ +/* values 8u and 9u work the fastest */ +#define FIRSTBITS 9u + +/* a symbol value too big to represent any valid symbol, to indicate reading disallowed huffman bits combination, +which is possible in case of only 0 or 1 present symbols. */ +#define INVALIDSYMBOL 65535u + +/* make table for huffman decoding */ +static unsigned HuffmanTree_makeTable(HuffmanTree* tree) { + static const unsigned headsize = 1u << FIRSTBITS; /*size of the first table*/ + static const unsigned mask = (1u << FIRSTBITS) /*headsize*/ - 1u; + size_t i, numpresent, pointer, size; /*total table size*/ + unsigned* maxlens = (unsigned*)lodepng_malloc(headsize * sizeof(unsigned)); + if(!maxlens) return 83; /*alloc fail*/ + + /* compute maxlens: max total bit length of symbols sharing prefix in the first table*/ + lodepng_memset(maxlens, 0, headsize * sizeof(*maxlens)); + for(i = 0; i < tree->numcodes; i++) { + unsigned symbol = tree->codes[i]; + unsigned l = tree->lengths[i]; + unsigned index; + if(l <= FIRSTBITS) continue; /*symbols that fit in first table don't increase secondary table size*/ + /*get the FIRSTBITS MSBs, the MSBs of the symbol are encoded first. See later comment about the reversing*/ + index = reverseBits(symbol >> (l - FIRSTBITS), FIRSTBITS); + maxlens[index] = LODEPNG_MAX(maxlens[index], l); + } + /* compute total table size: size of first table plus all secondary tables for symbols longer than FIRSTBITS */ + size = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l > FIRSTBITS) size += (1u << (l - FIRSTBITS)); + } + tree->table_len = (unsigned char*)lodepng_malloc(size * sizeof(*tree->table_len)); + tree->table_value = (unsigned short*)lodepng_malloc(size * sizeof(*tree->table_value)); + if(!tree->table_len || !tree->table_value) { + lodepng_free(maxlens); + /* freeing tree->table values is done at a higher scope */ + return 83; /*alloc fail*/ + } + /*initialize with an invalid length to indicate unused entries*/ + for(i = 0; i < size; ++i) tree->table_len[i] = 16; + + /*fill in the first table for long symbols: max prefix size and pointer to secondary tables*/ + pointer = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l <= FIRSTBITS) continue; + tree->table_len[i] = l; + tree->table_value[i] = pointer; + pointer += (1u << (l - FIRSTBITS)); + } + lodepng_free(maxlens); + + /*fill in the first table for short symbols, or secondary table for long symbols*/ + numpresent = 0; + for(i = 0; i < tree->numcodes; ++i) { + unsigned l = tree->lengths[i]; + unsigned symbol = tree->codes[i]; /*the huffman bit pattern. i itself is the value.*/ + /*reverse bits, because the huffman bits are given in MSB first order but the bit reader reads LSB first*/ + unsigned reverse = reverseBits(symbol, l); + if(l == 0) continue; + numpresent++; + + if(l <= FIRSTBITS) { + /*short symbol, fully in first table, replicated num times if l < FIRSTBITS*/ + unsigned num = 1u << (FIRSTBITS - l); + unsigned j; + for(j = 0; j < num; ++j) { + /*bit reader will read the l bits of symbol first, the remaining FIRSTBITS - l bits go to the MSB's*/ + unsigned index = reverse | (j << l); + if(tree->table_len[index] != 16) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + tree->table_len[index] = l; + tree->table_value[index] = i; + } + } else { + /*long symbol, shares prefix with other long symbols in first lookup table, needs second lookup*/ + /*the FIRSTBITS MSBs of the symbol are the first table index*/ + unsigned index = reverse & mask; + unsigned maxlen = tree->table_len[index]; + /*log2 of secondary table length, should be >= l - FIRSTBITS*/ + unsigned tablelen = maxlen - FIRSTBITS; + unsigned start = tree->table_value[index]; /*starting index in secondary table*/ + unsigned num = 1u << (tablelen - (l - FIRSTBITS)); /*amount of entries of this symbol in secondary table*/ + unsigned j; + if(maxlen < l) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + for(j = 0; j < num; ++j) { + unsigned reverse2 = reverse >> FIRSTBITS; /* l - FIRSTBITS bits */ + unsigned index2 = start + (reverse2 | (j << (l - FIRSTBITS))); + tree->table_len[index2] = l; + tree->table_value[index2] = i; + } + } + } + + if(numpresent < 2) { + /* In case of exactly 1 symbol, in theory the huffman symbol needs 0 bits, + but deflate uses 1 bit instead. In case of 0 symbols, no symbols can + appear at all, but such huffman tree could still exist (e.g. if distance + codes are never used). In both cases, not all symbols of the table will be + filled in. Fill them in with an invalid symbol value so returning them from + huffmanDecodeSymbol will cause error. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) { + /* As length, use a value smaller than FIRSTBITS for the head table, + and a value larger than FIRSTBITS for the secondary table, to ensure + valid behavior for advanceBits when reading this symbol. */ + tree->table_len[i] = (i < headsize) ? 1 : (FIRSTBITS + 1); + tree->table_value[i] = INVALIDSYMBOL; + } + } + } else { + /* A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + If that is not the case (due to too long length codes), the table will not + have been fully used, and this is an error (not all bit combinations can be + decoded): an oversubscribed huffman tree, indicated by error 55. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) return 55; + } + } + + return 0; +} + +/* +Second step for the ...makeFromLengths and ...makeFromFrequencies functions. +numcodes, lengths and maxbitlen must already be filled in correctly. return +value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) { + unsigned* blcount; + unsigned* nextcode; + unsigned error = 0; + unsigned bits, n; + + tree->codes = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); + blcount = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + nextcode = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + if(!tree->codes || !blcount || !nextcode) error = 83; /*alloc fail*/ + + if(!error) { + for(n = 0; n != tree->maxbitlen + 1; n++) blcount[n] = nextcode[n] = 0; + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits != tree->numcodes; ++bits) ++blcount[tree->lengths[bits]]; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; ++bits) { + nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1u; + } + /*step 3: generate all the codes*/ + for(n = 0; n != tree->numcodes; ++n) { + if(tree->lengths[n] != 0) { + tree->codes[n] = nextcode[tree->lengths[n]]++; + /*remove superfluous bits from the code*/ + tree->codes[n] &= ((1u << tree->lengths[n]) - 1u); + } + } + } + + lodepng_free(blcount); + lodepng_free(nextcode); + + if(!error) error = HuffmanTree_makeTable(tree); + return error; +} + +/* +given the code lengths (as stored in the PNG file), generate the tree as defined +by Deflate. maxbitlen is the maximum bits that a code in the tree can have. +return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, + size_t numcodes, unsigned maxbitlen) { + unsigned i; + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER + +/*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding", +Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ + +/*chain node for boundary package merge*/ +typedef struct BPMNode { + int weight; /*the sum of all weights in this chain*/ + unsigned index; /*index of this leaf node (called "count" in the paper)*/ + struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ + int in_use; +} BPMNode; + +/*lists of chains*/ +typedef struct BPMLists { + /*memory pool*/ + unsigned memsize; + BPMNode* memory; + unsigned numfree; + unsigned nextfree; + BPMNode** freelist; + /*two heads of lookahead chains per list*/ + unsigned listsize; + BPMNode** chains0; + BPMNode** chains1; +} BPMLists; + +/*creates a new chain node with the given parameters, from the memory in the lists */ +static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) { + unsigned i; + BPMNode* result; + + /*memory full, so garbage collect*/ + if(lists->nextfree >= lists->numfree) { + /*mark only those that are in use*/ + for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; + for(i = 0; i != lists->listsize; ++i) { + BPMNode* node; + for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; + for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; + } + /*collect those that are free*/ + lists->numfree = 0; + for(i = 0; i != lists->memsize; ++i) { + if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; + } + lists->nextfree = 0; + } + + result = lists->freelist[lists->nextfree++]; + result->weight = weight; + result->index = index; + result->tail = tail; + return result; +} + +/*sort the leaves with stable mergesort*/ +static void bpmnode_sort(BPMNode* leaves, size_t num) { + BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num); + size_t width, counter = 0; + for(width = 1; width < num; width *= 2) { + BPMNode* a = (counter & 1) ? mem : leaves; + BPMNode* b = (counter & 1) ? leaves : mem; + size_t p; + for(p = 0; p < num; p += 2 * width) { + size_t q = (p + width > num) ? num : (p + width); + size_t r = (p + 2 * width > num) ? num : (p + 2 * width); + size_t i = p, j = q, k; + for(k = p; k < r; k++) { + if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++]; + else b[k] = a[j++]; + } + } + counter++; + } + if(counter & 1) lodepng_memcpy(leaves, mem, sizeof(*leaves) * num); + lodepng_free(mem); +} + +/*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ +static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) { + unsigned lastindex = lists->chains1[c]->index; + + if(c == 0) { + if(lastindex >= numpresent) return; + lists->chains0[c] = lists->chains1[c]; + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); + } else { + /*sum of the weights of the head nodes of the previous lookahead chains.*/ + int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; + lists->chains0[c] = lists->chains1[c]; + if(lastindex < numpresent && sum > leaves[lastindex].weight) { + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); + return; + } + lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); + /*in the end we are only interested in the chain of the last list, so no + need to recurse if we're at the last one (this gives measurable speedup)*/ + if(num + 1 < (int)(2 * numpresent - 2)) { + boundaryPM(lists, leaves, numpresent, c - 1, num); + boundaryPM(lists, leaves, numpresent, c - 1, num); + } + } +} + +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + unsigned i; + size_t numpresent = 0; /*number of symbols with non-zero frequency*/ + BPMNode* leaves; /*the symbols, only those with > 0 frequency*/ + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + if((1u << maxbitlen) < (unsigned)numcodes) return 80; /*error: represent all symbols*/ + + leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); + if(!leaves) return 83; /*alloc fail*/ + + for(i = 0; i != numcodes; ++i) { + if(frequencies[i] > 0) { + leaves[numpresent].weight = (int)frequencies[i]; + leaves[numpresent].index = i; + ++numpresent; + } + } + + lodepng_memset(lengths, 0, numcodes * sizeof(*lengths)); + + /*ensure at least two present symbols. There should be at least one symbol + according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To + make these work as well ensure there are at least two symbols. The + Package-Merge code below also doesn't work correctly if there's only one + symbol, it'd give it the theoretical 0 bits but in practice zlib wants 1 bit*/ + if(numpresent == 0) { + lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ + } else if(numpresent == 1) { + lengths[leaves[0].index] = 1; + lengths[leaves[0].index == 0 ? 1 : 0] = 1; + } else { + BPMLists lists; + BPMNode* node; + + bpmnode_sort(leaves, numpresent); + + lists.listsize = maxbitlen; + lists.memsize = 2 * maxbitlen * (maxbitlen + 1); + lists.nextfree = 0; + lists.numfree = lists.memsize; + lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory)); + lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*)); + lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ + + if(!error) { + for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; + + bpmnode_create(&lists, leaves[0].weight, 1, 0); + bpmnode_create(&lists, leaves[1].weight, 2, 0); + + for(i = 0; i != lists.listsize; ++i) { + lists.chains0[i] = &lists.memory[0]; + lists.chains1[i] = &lists.memory[1]; + } + + /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ + for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i); + + for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) { + for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; + } + } + + lodepng_free(lists.memory); + lodepng_free(lists.freelist); + lodepng_free(lists.chains0); + lodepng_free(lists.chains1); + } + + lodepng_free(leaves); + return error; +} + +/*Create the Huffman tree given the symbol frequencies*/ +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, + size_t mincodes, size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + tree->maxbitlen = maxbitlen; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + + error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); + if(!error) error = HuffmanTree_makeFromLengths2(tree); + return error; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; ++i) bitlen[i] = 8; + for(i = 144; i <= 255; ++i) bitlen[i] = 9; + for(i = 256; i <= 279; ++i) bitlen[i] = 7; + for(i = 280; i <= 287; ++i) bitlen[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* +returns the code. The bit reader must already have been ensured at least 15 bits +*/ +static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const HuffmanTree* codetree) { + unsigned short code = peekBits(reader, FIRSTBITS); + unsigned short l = codetree->table_len[code]; + unsigned short value = codetree->table_value[code]; + if(l <= FIRSTBITS) { + advanceBits(reader, l); + return value; + } else { + advanceBits(reader, FIRSTBITS); + value += peekBits(reader, l - FIRSTBITS); + advanceBits(reader, codetree->table_len[value] - FIRSTBITS); + return codetree->table_value[value]; + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification +Returns error code.*/ +static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) { + unsigned error = generateFixedLitLenTree(tree_ll); + if(error) return error; + return generateFixedDistanceTree(tree_d); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, + LodePNGBitReader* reader) { + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + unsigned* bitlen_ll = 0; /*lit,len code lengths*/ + unsigned* bitlen_d = 0; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + unsigned* bitlen_cl = 0; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if(reader->bitsize - reader->bp < 14) return 49; /*error: the bit pointer is or will go past the memory*/ + ensureBits17(reader, 14); + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBits(reader, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBits(reader, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBits(reader, 4) + 4; + + bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); + if(!bitlen_cl) return 83 /*alloc fail*/; + + HuffmanTree_init(&tree_cl); + + while(!error) { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + if(lodepng_gtofl(reader->bp, HCLEN * 3, reader->bitsize)) { + ERROR_BREAK(50); /*error: the bit pointer is or will go past the memory*/ + } + for(i = 0; i != HCLEN; ++i) { + ensureBits9(reader, 3); /*out of bounds already checked above */ + bitlen_cl[CLCL_ORDER[i]] = readBits(reader, 3); + } + for(i = HCLEN; i != NUM_CODE_LENGTH_CODES; ++i) { + bitlen_cl[CLCL_ORDER[i]] = 0; + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); + lodepng_memset(bitlen_ll, 0, NUM_DEFLATE_CODE_SYMBOLS * sizeof(*bitlen_ll)); + lodepng_memset(bitlen_d, 0, NUM_DISTANCE_SYMBOLS * sizeof(*bitlen_d)); + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + i = 0; + while(i < HLIT + HDIST) { + unsigned code; + ensureBits25(reader, 22); /* up to 15 bits for huffman code, up to 7 extra bits below*/ + code = huffmanDecodeSymbol(reader, &tree_cl); + if(code <= 15) /*a length code*/ { + if(i < HLIT) bitlen_ll[i] = code; + else bitlen_d[i - HLIT] = code; + ++i; + } else if(code == 16) /*repeat previous*/ { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ + + replength += readBits(reader, 2); + + if(i < HLIT + 1) value = bitlen_ll[i - 1]; + else value = bitlen_d[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen_ll[i] = value; + else bitlen_d[i - HLIT] = value; + ++i; + } + } else if(code == 17) /*repeat "0" 3-10 times*/ { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else if(code == 18) /*repeat "0" 11-138 times*/ { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else /*if(code == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + } + } + if(error) break; + + if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); + if(error) break; + error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); + + break; /*end of error-while*/ + } + + lodepng_free(bitlen_cl); + lodepng_free(bitlen_ll); + lodepng_free(bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree. btype must be 1 or 2.*/ +static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* reader, + unsigned btype, size_t max_output_size) { + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + const size_t reserved_size = 260; /* must be at least 258 for max length, and a few extra for adding a few extra literals */ + int done = 0; + + if(!ucvector_reserve(out, out->size + reserved_size)) return 83; /*alloc fail*/ + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if(btype == 1) error = getTreeInflateFixed(&tree_ll, &tree_d); + else /*if(btype == 2)*/ error = getTreeInflateDynamic(&tree_ll, &tree_d, reader); + + + while(!error && !done) /*decode all symbols until end reached, breaks at end code*/ { + /*code_ll is literal, length or end code*/ + unsigned code_ll; + /* ensure enough bits for 2 huffman code reads (15 bits each): if the first is a literal, a second literal is read at once. This + appears to be slightly faster, than ensuring 20 bits here for 1 huffman symbol and the potential 5 extra bits for the length symbol.*/ + ensureBits32(reader, 30); + code_ll = huffmanDecodeSymbol(reader, &tree_ll); + if(code_ll <= 255) { + /*slightly faster code path if multiple literals in a row*/ + out->data[out->size++] = (unsigned char)code_ll; + code_ll = huffmanDecodeSymbol(reader, &tree_ll); + } + if(code_ll <= 255) /*literal symbol*/ { + out->data[out->size++] = (unsigned char)code_ll; + } else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if(numextrabits_l != 0) { + /* bits already ensured above */ + ensureBits25(reader, 5); + length += readBits(reader, numextrabits_l); + } + + /*part 3: get distance code*/ + ensureBits32(reader, 28); /* up to 15 for the huffman symbol, up to 13 for the extra bits */ + code_d = huffmanDecodeSymbol(reader, &tree_d); + if(code_d > 29) { + if(code_d <= 31) { + ERROR_BREAK(18); /*error: invalid distance code (30-31 are never used)*/ + } else /* if(code_d == INVALIDSYMBOL) */{ + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if(numextrabits_d != 0) { + /* bits already ensured above */ + distance += readBits(reader, numextrabits_d); + } + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = out->size; + if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ + backward = start - distance; + + out->size += length; + if(distance < length) { + size_t forward; + lodepng_memcpy(out->data + start, out->data + backward, distance); + start += distance; + for(forward = distance; forward < length; ++forward) { + out->data[start++] = out->data[backward++]; + } + } else { + lodepng_memcpy(out->data + start, out->data + backward, length); + } + } else if(code_ll == 256) { + done = 1; /*end code, finish the loop*/ + } else /*if(code_ll == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + if(out->allocsize - out->size < reserved_size) { + if(!ucvector_reserve(out, out->size + reserved_size)) ERROR_BREAK(83); /*alloc fail*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(51); /*error, bit pointer jumps past memory*/ + } + if(max_output_size && out->size > max_output_size) { + ERROR_BREAK(109); /*error, larger than max size*/ + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* reader, + const LodePNGDecompressSettings* settings) { + size_t bytepos; + size_t size = reader->size; + unsigned LEN, NLEN, error = 0; + + /*go to first boundary of byte*/ + bytepos = (reader->bp + 7u) >> 3u; + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(bytepos + 4 >= size) return 52; /*error, bit pointer will jump past memory*/ + LEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + NLEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(!settings->ignore_nlen && LEN + NLEN != 65535) { + return 21; /*error: NLEN is not one's complement of LEN*/ + } + + if(!ucvector_resize(out, out->size + LEN)) return 83; /*alloc fail*/ + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(bytepos + LEN > size) return 23; /*error: reading outside of in buffer*/ + + lodepng_memcpy(out->data + out->size - LEN, reader->data + bytepos, LEN); + bytepos += LEN; + + reader->bp = bytepos << 3u; + + return error; +} + +static unsigned lodepng_inflatev(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned BFINAL = 0; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, in, insize); + + if(error) return error; + + while(!BFINAL) { + unsigned BTYPE; + if(reader.bitsize - reader.bp < 3) return 52; /*error, bit pointer will jump past memory*/ + ensureBits9(&reader, 3); + BFINAL = readBits(&reader, 1); + BTYPE = readBits(&reader, 2); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, &reader, settings); /*no compression*/ + else error = inflateHuffmanBlock(out, &reader, BTYPE, settings->max_output_size); /*compression, BTYPE 01 or 10*/ + if(!error && settings->max_output_size && out->size > settings->max_output_size) error = 109; + if(error) break; + } + + return error; +} + +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_inflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned inflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + if(settings->custom_inflate) { + unsigned error = settings->custom_inflate(&out->data, &out->size, in, insize, settings); + out->allocsize = out->size; + if(error) { + /*the custom inflate is allowed to have its own error codes, however, we translate it to code 110*/ + error = 110; + /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ + if(settings->max_output_size && out->size > settings->max_output_size) error = 109; + } + return error; + } else { + return lodepng_inflatev(out, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator (Compressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*search the index in the array, that has the largest value smaller than or equal to the given value, +given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) { + /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/ + size_t left = 1; + size_t right = array_size - 1; + + while(left <= right) { + size_t mid = (left + right) >> 1; + if(array[mid] >= value) right = mid - 1; + else left = mid + 1; + } + if(left >= array_size || array[left] > value) left--; + return left; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) { + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + size_t pos = values->size; + /*TODO: return error when this fails (out of memory)*/ + unsigned ok = uivector_resize(values, values->size + 4); + if(ok) { + values->data[pos + 0] = length_code + FIRST_LENGTH_CODE_INDEX; + values->data[pos + 1] = extra_length; + values->data[pos + 2] = dist_code; + values->data[pos + 3] = extra_distance; + } +} + +/*3 bytes of data get encoded into two bytes. The hash cannot use more than 3 +bytes as input because 3 is the minimum match length for deflate*/ +static const unsigned HASH_NUM_VALUES = 65536; +static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ + +typedef struct Hash { + int* head; /*hash value to head circular pos - can be outdated if went around window*/ + /*circular pos to prev circular pos*/ + unsigned short* chain; + int* val; /*circular pos to hash value*/ + + /*TODO: do this not only for zeros but for any repeated byte. However for PNG + it's always going to be the zeros that dominate, so not important for PNG*/ + int* headz; /*similar to head, but for chainz*/ + unsigned short* chainz; /*those with same amount of zeros*/ + unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ +} Hash; + +static unsigned hash_init(Hash* hash, unsigned windowsize) { + unsigned i; + hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); + hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); + hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); + hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) { + return 83; /*alloc fail*/ + } + + /*initialize hash table*/ + for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1; + for(i = 0; i != windowsize; ++i) hash->val[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/ + + for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/ + + return 0; +} + +static void hash_cleanup(Hash* hash) { + lodepng_free(hash->head); + lodepng_free(hash->val); + lodepng_free(hash->chain); + + lodepng_free(hash->zeros); + lodepng_free(hash->headz); + lodepng_free(hash->chainz); +} + + + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) { + unsigned result = 0; + if(pos + 2 < size) { + /*A simple shift and xor hash is used. Since the data of PNGs is dominated + by zeroes due to the filters, a better hash does not have a significant + effect on speed in traversing the chain, and causes more time spend on + calculating the hash.*/ + result ^= ((unsigned)data[pos + 0] << 0u); + result ^= ((unsigned)data[pos + 1] << 4u); + result ^= ((unsigned)data[pos + 2] << 8u); + } else { + size_t amount, i; + if(pos >= size) return 0; + amount = size - pos; + for(i = 0; i != amount; ++i) result ^= ((unsigned)data[pos + i] << (i * 8u)); + } + return result & HASH_BIT_MASK; +} + +static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) { + const unsigned char* start = data + pos; + const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; + if(end > data + size) end = data + size; + data = start; + while(data != end && *data == 0) ++data; + /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ + return (unsigned)(data - start); +} + +/*wpos = pos & (windowsize - 1)*/ +static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) { + hash->val[wpos] = (int)hashval; + if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; + hash->head[hashval] = (int)wpos; + + hash->zeros[wpos] = numzeros; + if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros]; + hash->headz[numzeros] = (int)wpos; +} + +/* +LZ77-encode the data. Return value is error code. The input are raw bytes, the output +is in the form of unsigned integers with codes representing for example literal bytes, or +length/distance pairs. +It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a +sliding window (of windowsize) is used, and all past bytes in that window can be used as +the "dictionary". A brute force search through all possible distances would be slow, and +this hash technique is one out of several ways to speed this up. +*/ +static unsigned encodeLZ77(uivector* out, Hash* hash, + const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, + unsigned minmatch, unsigned nicematch, unsigned lazymatching) { + size_t pos; + unsigned i, error = 0; + /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ + unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8u; + unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; + + unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/ + unsigned numzeros = 0; + + unsigned offset; /*the offset represents the distance in LZ77 terminology*/ + unsigned length; + unsigned lazy = 0; + unsigned lazylength = 0, lazyoffset = 0; + unsigned hashval; + unsigned current_offset, current_length; + unsigned prev_offset; + const unsigned char *lastptr, *foreptr, *backptr; + unsigned hashpos; + + if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/ + if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/ + + if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; + + for(pos = inpos; pos < insize; ++pos) { + size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ + unsigned chainlength = 0; + + hashval = getHash(in, insize, pos); + + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + + updateHashChain(hash, wpos, hashval, numzeros); + + /*the length and offset found for the current position*/ + length = 0; + offset = 0; + + hashpos = hash->chain[wpos]; + + lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; + + /*search for the longest string*/ + prev_offset = 0; + for(;;) { + if(chainlength++ >= maxchainlength) break; + current_offset = (unsigned)(hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize); + + if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ + prev_offset = current_offset; + if(current_offset > 0) { + /*test the next characters*/ + foreptr = &in[pos]; + backptr = &in[pos - current_offset]; + + /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ + if(numzeros >= 3) { + unsigned skip = hash->zeros[hashpos]; + if(skip > numzeros) skip = numzeros; + backptr += skip; + foreptr += skip; + } + + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ { + ++backptr; + ++foreptr; + } + current_length = (unsigned)(foreptr - &in[pos]); + + if(current_length > length) { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*jump out once a length of max length is found (speed gain). This also jumps + out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/ + if(current_length >= nicematch) break; + } + } + + if(hashpos == hash->chain[hashpos]) break; + + if(numzeros >= 3 && length > numzeros) { + hashpos = hash->chainz[hashpos]; + if(hash->zeros[hashpos] != numzeros) break; + } else { + hashpos = hash->chain[hashpos]; + /*outdated hash value, happens if particular value was not encountered in whole last window*/ + if(hash->val[hashpos] != (int)hashval) break; + } + } + + if(lazymatching) { + if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) { + lazy = 1; + lazylength = length; + lazyoffset = offset; + continue; /*try the next byte*/ + } + if(lazy) { + lazy = 0; + if(pos == 0) ERROR_BREAK(81); + if(length > lazylength + 1) { + /*push the previous character as literal*/ + if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); + } else { + length = lazylength; + offset = lazyoffset; + hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ + hash->headz[numzeros] = -1; /*idem*/ + --pos; + } + } + } + if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); + + /*encode it as length/distance pair or literal value*/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ { + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else if(length < minmatch || (length == 3 && offset > 4096)) { + /*compensate for the fact that longer offsets have more extra bits, a + length of only 3 may be not worth it then*/ + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else { + addLengthDistance(out, length, offset); + for(i = 1; i < length; ++i) { + ++pos; + wpos = pos & (windowsize - 1); + hashval = getHash(in, insize, pos); + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + updateHashChain(hash, wpos, hashval, numzeros); + } + } + } /*end of the loop through each character of input*/ + + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) { + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, + 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, numdeflateblocks = (datasize + 65534u) / 65535u; + unsigned datapos = 0; + for(i = 0; i != numdeflateblocks; ++i) { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + size_t pos = out->size; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + LEN = 65535; + if(datasize - datapos < 65535u) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + if(!ucvector_resize(out, out->size + LEN + 5)) return 83; /*alloc fail*/ + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1u) << 1u) + ((BTYPE & 2u) << 1u)); + out->data[pos + 0] = firstbyte; + out->data[pos + 1] = (unsigned char)(LEN & 255); + out->data[pos + 2] = (unsigned char)(LEN >> 8u); + out->data[pos + 3] = (unsigned char)(NLEN & 255); + out->data[pos + 4] = (unsigned char)(NLEN >> 8u); + lodepng_memcpy(out->data + pos + 5, data + datapos, LEN); + datapos += LEN; + } + + return 0; +} + +/* +write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. +tree_ll: the tree for lit and len codes. +tree_d: the tree for distance codes. +*/ +static void writeLZ77data(LodePNGBitWriter* writer, const uivector* lz77_encoded, + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) { + size_t i = 0; + for(i = 0; i != lz77_encoded->size; ++i) { + unsigned val = lz77_encoded->data[i]; + writeBitsReversed(writer, tree_ll->codes[val], tree_ll->lengths[val]); + if(val > 256) /*for a length code, 3 more things have to be added*/ { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + writeBits(writer, length_extra_bits, n_length_extra_bits); + writeBitsReversed(writer, tree_d->codes[distance_code], tree_d->lengths[distance_code]); + writeBits(writer, distance_extra_bits, n_distance_extra_bits); + } + } +} + +/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ +static unsigned deflateDynamic(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + unsigned error = 0; + + /* + A block is compressed as follows: The PNG data is lz77 encoded, resulting in + literal bytes and length/distance pairs. This is then huffman compressed with + two huffman trees. One huffman tree is used for the lit and len values ("ll"), + another huffman tree is used for the dist values ("d"). These two trees are + stored using their code lengths, and to compress even more these code lengths + are also run-length encoded and huffman compressed. This gives a huffman tree + of code lengths "cl". The code lengths used to describe this third tree are + the code length code lengths ("clcl"). + */ + + /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ + uivector lz77_encoded; + HuffmanTree tree_ll; /*tree for lit,len values*/ + HuffmanTree tree_d; /*tree for distance codes*/ + HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ + unsigned* frequencies_ll = 0; /*frequency of lit,len codes*/ + unsigned* frequencies_d = 0; /*frequency of dist codes*/ + unsigned* frequencies_cl = 0; /*frequency of code length codes*/ + unsigned* bitlen_lld = 0; /*lit,len,dist code lengths (int bits), literally (without repeat codes).*/ + unsigned* bitlen_lld_e = 0; /*bitlen_lld encoded with repeat codes (this is a rudimentary run length compression)*/ + size_t datasize = dataend - datapos; + + /* + If we could call "bitlen_cl" the the code length code lengths ("clcl"), that is the bit lengths of codes to represent + tree_cl in CLCL_ORDER, then due to the huffman compression of huffman tree representations ("two levels"), there are + some analogies: + bitlen_lld is to tree_cl what data is to tree_ll and tree_d. + bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. + bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. + */ + + unsigned BFINAL = final; + size_t i; + size_t numcodes_ll, numcodes_d, numcodes_lld, numcodes_lld_e, numcodes_cl; + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + HuffmanTree_init(&tree_cl); + /* could fit on stack, but >1KB is on the larger side so allocate instead */ + frequencies_ll = (unsigned*)lodepng_malloc(286 * sizeof(*frequencies_ll)); + frequencies_d = (unsigned*)lodepng_malloc(30 * sizeof(*frequencies_d)); + frequencies_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(!frequencies_ll || !frequencies_d || !frequencies_cl) error = 83; /*alloc fail*/ + + /*This while loop never loops due to a break at the end, it is here to + allow breaking out of it to the cleanup phase on error conditions.*/ + while(!error) { + lodepng_memset(frequencies_ll, 0, 286 * sizeof(*frequencies_ll)); + lodepng_memset(frequencies_d, 0, 30 * sizeof(*frequencies_d)); + lodepng_memset(frequencies_cl, 0, NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(settings->use_lz77) { + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(error) break; + } else { + if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); + for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + /*Count the frequencies of lit, len and dist codes*/ + for(i = 0; i != lz77_encoded.size; ++i) { + unsigned symbol = lz77_encoded.data[i]; + ++frequencies_ll[symbol]; + if(symbol > 256) { + unsigned dist = lz77_encoded.data[i + 2]; + ++frequencies_d[dist]; + i += 3; + } + } + frequencies_ll[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ + error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll, 257, 286, 15); + if(error) break; + /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ + error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d, 2, 30, 15); + if(error) break; + + numcodes_ll = LODEPNG_MIN(tree_ll.numcodes, 286); + numcodes_d = LODEPNG_MIN(tree_d.numcodes, 30); + /*store the code lengths of both generated trees in bitlen_lld*/ + numcodes_lld = numcodes_ll + numcodes_d; + bitlen_lld = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld)); + /*numcodes_lld_e never needs more size than bitlen_lld*/ + bitlen_lld_e = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld_e)); + if(!bitlen_lld || !bitlen_lld_e) ERROR_BREAK(83); /*alloc fail*/ + numcodes_lld_e = 0; + + for(i = 0; i != numcodes_ll; ++i) bitlen_lld[i] = tree_ll.lengths[i]; + for(i = 0; i != numcodes_d; ++i) bitlen_lld[numcodes_ll + i] = tree_d.lengths[i]; + + /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), + 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i != numcodes_lld; ++i) { + unsigned j = 0; /*amount of repetitions*/ + while(i + j + 1 < numcodes_lld && bitlen_lld[i + j + 1] == bitlen_lld[i]) ++j; + + if(bitlen_lld[i] == 0 && j >= 2) /*repeat code for zeroes*/ { + ++j; /*include the first zero*/ + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ { + bitlen_lld_e[numcodes_lld_e++] = 17; + bitlen_lld_e[numcodes_lld_e++] = j - 3; + } else /*repeat code 18 supports max 138 zeroes*/ { + if(j > 138) j = 138; + bitlen_lld_e[numcodes_lld_e++] = 18; + bitlen_lld_e[numcodes_lld_e++] = j - 11; + } + i += (j - 1); + } else if(j >= 3) /*repeat code for value other than zero*/ { + size_t k; + unsigned num = j / 6u, rest = j % 6u; + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + for(k = 0; k < num; ++k) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = 6 - 3; + } + if(rest >= 3) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = rest - 3; + } + else j -= rest; + i += j; + } else /*too short to benefit from repeat code*/ { + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + } + } + + /*generate tree_cl, the huffmantree of huffmantrees*/ + for(i = 0; i != numcodes_lld_e; ++i) { + ++frequencies_cl[bitlen_lld_e[i]]; + /*after a repeat code come the bits that specify the number of repetitions, + those don't need to be in the frequencies_cl calculation*/ + if(bitlen_lld_e[i] >= 16) ++i; + } + + error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl, + NUM_CODE_LENGTH_CODES, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*compute amount of code-length-code-lengths to output*/ + numcodes_cl = NUM_CODE_LENGTH_CODES; + /*trim zeros at the end (using CLCL_ORDER), but minimum size must be 4 (see HCLEN below)*/ + while(numcodes_cl > 4u && tree_cl.lengths[CLCL_ORDER[numcodes_cl - 1u]] == 0) { + numcodes_cl--; + } + + /* + Write everything into the output + + After the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lengths of lit/length alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + /*Write block type*/ + writeBits(writer, BFINAL, 1); + writeBits(writer, 0, 1); /*first bit of BTYPE "dynamic"*/ + writeBits(writer, 1, 1); /*second bit of BTYPE "dynamic"*/ + + /*write the HLIT, HDIST and HCLEN values*/ + /*all three sizes take trimmed ending zeroes into account, done either by HuffmanTree_makeFromFrequencies + or in the loop for numcodes_cl above, which saves space. */ + HLIT = (unsigned)(numcodes_ll - 257); + HDIST = (unsigned)(numcodes_d - 1); + HCLEN = (unsigned)(numcodes_cl - 4); + writeBits(writer, HLIT, 5); + writeBits(writer, HDIST, 5); + writeBits(writer, HCLEN, 4); + + /*write the code lengths of the code length alphabet ("bitlen_cl")*/ + for(i = 0; i != numcodes_cl; ++i) writeBits(writer, tree_cl.lengths[CLCL_ORDER[i]], 3); + + /*write the lengths of the lit/len AND the dist alphabet*/ + for(i = 0; i != numcodes_lld_e; ++i) { + writeBitsReversed(writer, tree_cl.codes[bitlen_lld_e[i]], tree_cl.lengths[bitlen_lld_e[i]]); + /*extra bits of repeat codes*/ + if(bitlen_lld_e[i] == 16) writeBits(writer, bitlen_lld_e[++i], 2); + else if(bitlen_lld_e[i] == 17) writeBits(writer, bitlen_lld_e[++i], 3); + else if(bitlen_lld_e[i] == 18) writeBits(writer, bitlen_lld_e[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + /*error: the length of the end code 256 must be larger than 0*/ + if(tree_ll.lengths[256] == 0) ERROR_BREAK(64); + + /*write the end code*/ + writeBitsReversed(writer, tree_ll.codes[256], tree_ll.lengths[256]); + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + HuffmanTree_cleanup(&tree_cl); + lodepng_free(frequencies_ll); + lodepng_free(frequencies_d); + lodepng_free(frequencies_cl); + lodepng_free(bitlen_lld); + lodepng_free(bitlen_lld_e); + + return error; +} + +static unsigned deflateFixed(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, + size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + HuffmanTree tree_ll; /*tree for literal values and length codes*/ + HuffmanTree tree_d; /*tree for distance codes*/ + + unsigned BFINAL = final; + unsigned error = 0; + size_t i; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + error = generateFixedLitLenTree(&tree_ll); + if(!error) error = generateFixedDistanceTree(&tree_d); + + if(!error) { + writeBits(writer, BFINAL, 1); + writeBits(writer, 1, 1); /*first bit of BTYPE*/ + writeBits(writer, 0, 1); /*second bit of BTYPE*/ + + if(settings->use_lz77) /*LZ77 encoded*/ { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(!error) writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + uivector_cleanup(&lz77_encoded); + } else /*no LZ77, but still will be Huffman compressed*/ { + for(i = datapos; i < dataend; ++i) { + writeBitsReversed(writer, tree_ll.codes[data[i]], tree_ll.lengths[data[i]]); + } + } + /*add END code*/ + if(!error) writeBitsReversed(writer,tree_ll.codes[256], tree_ll.lengths[256]); + } + + /*cleanup*/ + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + unsigned error = 0; + size_t i, blocksize, numdeflateblocks; + Hash hash; + LodePNGBitWriter writer; + + LodePNGBitWriter_init(&writer, out); + + if(settings->btype > 2) return 61; + else if(settings->btype == 0) return deflateNoCompression(out, in, insize); + else if(settings->btype == 1) blocksize = insize; + else /*if(settings->btype == 2)*/ { + /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ + blocksize = insize / 8u + 8; + if(blocksize < 65536) blocksize = 65536; + if(blocksize > 262144) blocksize = 262144; + } + + numdeflateblocks = (insize + blocksize - 1) / blocksize; + if(numdeflateblocks == 0) numdeflateblocks = 1; + + error = hash_init(&hash, settings->windowsize); + + if(!error) { + for(i = 0; i != numdeflateblocks && !error; ++i) { + unsigned final = (i == numdeflateblocks - 1); + size_t start = i * blocksize; + size_t end = start + blocksize; + if(end > insize) end = insize; + + if(settings->btype == 1) error = deflateFixed(&writer, &hash, in, start, end, settings, final); + else if(settings->btype == 2) error = deflateDynamic(&writer, &hash, in, start, end, settings, final); + } + } + + hash_cleanup(&hash); + + return error; +} + +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_deflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + if(settings->custom_deflate) { + unsigned error = settings->custom_deflate(out, outsize, in, insize, settings); + /*the custom deflate is allowed to have its own error codes, however, we translate it to code 111*/ + return error ? 111 : 0; + } else { + return lodepng_deflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) { + unsigned s1 = adler & 0xffffu; + unsigned s2 = (adler >> 16u) & 0xffffu; + + while(len != 0u) { + unsigned i; + /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5552u ? 5552u : len; + len -= amount; + for(i = 0; i != amount; ++i) { + s1 += (*data++); + s2 += s1; + } + s1 %= 65521u; + s2 %= 65521u; + } + + return (s2 << 16u) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) { + return update_adler32(1u, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +static unsigned lodepng_zlib_decompressv(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned error = 0; + unsigned CM, CINFO, FDICT; + + if(insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if(CM != 8 || CINFO > 7) { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if(FDICT != 0) { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + error = inflatev(out, in + 2, insize - 2, settings); + if(error) return error; + + if(!settings->ignore_adler32) { + unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(out->data, (unsigned)(out->size)); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + + +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +/*expected_size is expected output size, to avoid intermediate allocations. Set to 0 if not known. */ +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + unsigned error; + if(settings->custom_zlib) { + error = settings->custom_zlib(out, outsize, in, insize, settings); + if(error) { + /*the custom zlib is allowed to have its own error codes, however, we translate it to code 110*/ + error = 110; + /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ + if(settings->max_output_size && *outsize > settings->max_output_size) error = 109; + } + } else { + ucvector v = ucvector_init(*out, *outsize); + if(expected_size) { + /*reserve the memory to avoid intermediate reallocations*/ + ucvector_resize(&v, *outsize + expected_size); + v.size = *outsize; + } + error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + } + return error; +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + size_t i; + unsigned error; + unsigned char* deflatedata = 0; + size_t deflatesize = 0; + + error = deflate(&deflatedata, &deflatesize, in, insize, settings); + + *out = NULL; + *outsize = 0; + if(!error) { + *outsize = deflatesize + 6; + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!*out) error = 83; /*alloc fail*/ + } + + if(!error) { + unsigned ADLER32 = adler32(in, (unsigned)insize); + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + (*out)[0] = (unsigned char)(CMFFLG >> 8); + (*out)[1] = (unsigned char)(CMFFLG & 255); + for(i = 0; i != deflatesize; ++i) (*out)[i + 2] = deflatedata[i]; + lodepng_set32bitInt(&(*out)[*outsize - 4], ADLER32); + } + + lodepng_free(deflatedata); + return error; +} + +/* compress using the default or custom zlib function */ +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(settings->custom_zlib) { + unsigned error = settings->custom_zlib(out, outsize, in, insize, settings); + /*the custom zlib is allowed to have its own error codes, however, we translate it to code 111*/ + return error ? 111 : 0; + } else { + return lodepng_zlib_compress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#else /*no LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + (void)expected_size; + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/*this is a good tradeoff between speed and compression ratio*/ +#define DEFAULT_WINDOWSIZE 2048 + +void lodepng_compress_settings_init(LodePNGCompressSettings* settings) { + /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->btype = 2; + settings->use_lz77 = 1; + settings->windowsize = DEFAULT_WINDOWSIZE; + settings->minmatch = 3; + settings->nicematch = 128; + settings->lazymatching = 1; + + settings->custom_zlib = 0; + settings->custom_deflate = 0; + settings->custom_context = 0; +} + +const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; + + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) { + settings->ignore_adler32 = 0; + settings->ignore_nlen = 0; + settings->max_output_size = 0; + + settings->custom_zlib = 0; + settings->custom_inflate = 0; + settings->custom_context = 0; +} + +const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0, 0, 0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +#ifndef LODEPNG_NO_COMPILE_CRC +/* CRC polynomial: 0xedb88320 */ +static unsigned lodepng_crc32_table[256] = { + 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, + 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, + 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, + 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, + 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, + 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, + 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, + 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, + 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, + 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, + 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, + 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, + 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, + 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, + 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, + 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, + 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, + 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, + 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, + 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, + 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, + 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, + 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, + 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, + 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, + 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, + 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, + 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, + 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, + 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, + 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, + 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u +}; + +/*Return the CRC of the bytes buf[0..len-1].*/ +unsigned lodepng_crc32(const unsigned char* data, size_t length) { + unsigned r = 0xffffffffu; + size_t i; + for(i = 0; i < length; ++i) { + r = lodepng_crc32_table[(r ^ data[i]) & 0xffu] ^ (r >> 8u); + } + return r ^ 0xffffffffu; +} +#else /* !LODEPNG_NO_COMPILE_CRC */ +unsigned lodepng_crc32(const unsigned char* data, size_t length); +#endif /* !LODEPNG_NO_COMPILE_CRC */ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing PNG color channel bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* The color channel bits of less-than-8-bit pixels are read with the MSB of bytes first, +so LodePNGBitWriter and LodePNGBitReader can't be used for those. */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) { + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> ((*bitpointer) & 0x7)) & 1); + ++(*bitpointer); + return result; +} + +/* TODO: make this faster */ +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) { + unsigned result = 0; + size_t i; + for(i = 0 ; i < nbits; ++i) { + result <<= 1u; + result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream); + } + return result; +} + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) { + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3u] &= (unsigned char)(~(1u << ((*bitpointer) & 7u))); + else bitstream[(*bitpointer) >> 3u] |= (1u << (7u - ((*bitpointer) & 7u))); + ++(*bitpointer); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_chunk_length(const unsigned char* chunk) { + return lodepng_read32bitInt(&chunk[0]); +} + +void lodepng_chunk_type(char type[5], const unsigned char* chunk) { + unsigned i; + for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) { + if(lodepng_strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) { + return((chunk[4] & 32) != 0); +} + +unsigned char lodepng_chunk_private(const unsigned char* chunk) { + return((chunk[6] & 32) != 0); +} + +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) { + return((chunk[7] & 32) != 0); +} + +unsigned char* lodepng_chunk_data(unsigned char* chunk) { + return &chunk[8]; +} + +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) { + return &chunk[8]; +} + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = lodepng_crc32(&chunk[4], length + 4); + if(CRC != checksum) return 1; + else return 0; +} + +void lodepng_chunk_generate_crc(unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_crc32(&chunk[4], length + 4); + lodepng_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; + } +} + +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + const unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; + } +} + +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next(chunk, end); + } +} + +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next_const(chunk, end); + } +} + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk) { + unsigned i; + size_t total_chunk_length, new_length; + unsigned char *chunk_start, *new_buffer; + + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return 77; + if(lodepng_addofl(*outsize, total_chunk_length, &new_length)) return 77; + + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outsize) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i]; + + return 0; +} + +/*Sets length and name and allocates the space for data and crc but does not +set data or crc yet. Returns the start of the chunk in chunk. The start of +the data is at chunk + 8. To finalize chunk, add the data, then use +lodepng_chunk_generate_crc */ +static unsigned lodepng_chunk_init(unsigned char** chunk, + ucvector* out, + unsigned length, const char* type) { + size_t new_length = out->size; + if(lodepng_addofl(new_length, length, &new_length)) return 77; + if(lodepng_addofl(new_length, 12, &new_length)) return 77; + if(!ucvector_resize(out, new_length)) return 83; /*alloc fail*/ + *chunk = out->data + new_length - length - 12u; + + /*1: length*/ + lodepng_set32bitInt(*chunk, length); + + /*2: chunk name (4 letters)*/ + lodepng_memcpy(*chunk + 4, type, 4); + + return 0; +} + +/* like lodepng_chunk_create but with custom allocsize */ +static unsigned lodepng_chunk_createv(ucvector* out, + unsigned length, const char* type, const unsigned char* data) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, length, type)); + + /*3: the data*/ + lodepng_memcpy(chunk + 8, data, length); + + /*4: CRC (of the chunkname characters and the data)*/ + lodepng_chunk_generate_crc(chunk); + + return 0; +} + +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, + unsigned length, const char* type, const unsigned char* data) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_chunk_createv(&v, length, type, data); + *out = v.data; + *outsize = v.size; + return error; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types, channels, bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*checks if the colortype is valid and the bitdepth bd is allowed for this colortype. +Return value is a LodePNG error code.*/ +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) { + switch(colortype) { + case LCT_GREY: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; + case LCT_RGB: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_PALETTE: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; + case LCT_GREY_ALPHA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_RGBA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_CUSTOM: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_MAX_OCTET_VALUE: return 31; /* invalid color type */ + default: return 31; /* invalid color type */ + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(LodePNGColorType colortype) { + switch(colortype) { + case LCT_GREY: return 1; + case LCT_RGB: return 3; + case LCT_PALETTE: return 1; + case LCT_GREY_ALPHA: return 2; + case LCT_RGBA: return 4; + case LCT_CUSTOM: return 1; + case LCT_MAX_OCTET_VALUE: return 0; /* invalid color type */ + default: return 0; /*invalid color type*/ + } +} + +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) { + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colortype) * bitdepth; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void lodepng_color_mode_init(LodePNGColorMode* info) { + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colortype = LCT_RGBA; + info->bitdepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +/*allocates palette memory if needed, and initializes all colors to black*/ +static void lodepng_color_mode_alloc_palette(LodePNGColorMode* info) { + size_t i; + /*if the palette is already allocated, it will have size 1024 so no reallocation needed in that case*/ + /*the palette must have room for up to 256 colors with 4 bytes each.*/ + if(!info->palette) info->palette = (unsigned char*)lodepng_malloc(1024); + if(!info->palette) return; /*alloc fail*/ + for(i = 0; i != 256; ++i) { + /*Initialize all unused colors with black, the value used for invalid palette indices. + This is an error according to the PNG spec, but common PNG decoders make it black instead. + That makes color conversion slightly faster due to no error handling needed.*/ + info->palette[i * 4 + 0] = 0; + info->palette[i * 4 + 1] = 0; + info->palette[i * 4 + 2] = 0; + info->palette[i * 4 + 3] = 255; + } +} + +void lodepng_color_mode_cleanup(LodePNGColorMode* info) { + lodepng_palette_clear(info); +} + +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) { + lodepng_color_mode_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGColorMode)); + if(source->palette) { + dest->palette = (unsigned char*)lodepng_malloc(1024); + if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ + lodepng_memcpy(dest->palette, source->palette, source->palettesize * 4); + } + return 0; +} + +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth) { + LodePNGColorMode result; + lodepng_color_mode_init(&result); + result.colortype = colortype; + result.bitdepth = bitdepth; + return result; +} + +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) { + size_t i; + if(a->colortype != b->colortype) return 0; + if(a->bitdepth != b->bitdepth) return 0; + if(a->key_defined != b->key_defined) return 0; + if(a->key_defined) { + if(a->key_r != b->key_r) return 0; + if(a->key_g != b->key_g) return 0; + if(a->key_b != b->key_b) return 0; + } + if(a->palettesize != b->palettesize) return 0; + for(i = 0; i != a->palettesize * 4; ++i) { + if(a->palette[i] != b->palette[i]) return 0; + } + return 1; +} + +void lodepng_palette_clear(LodePNGColorMode* info) { + if(info->palette) lodepng_free(info->palette); + info->palette = 0; + info->palettesize = 0; +} + +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(!info->palette) /*allocate palette if empty*/ { + lodepng_color_mode_alloc_palette(info); + if(!info->palette) return 83; /*alloc fail*/ + } + if(info->palettesize >= 256) { + return 108; /*too many palette values*/ + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + ++info->palettesize; + return 0; +} + +/*calculate bits per pixel out of colortype and bitdepth*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info) { + return lodepng_get_bpp_lct(info->colortype, info->bitdepth); +} + +unsigned lodepng_get_channels(const LodePNGColorMode* info) { + return getNumColorChannels(info->colortype); +} + +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) { + return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; +} + +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) { + return (info->colortype & 4) != 0; /*4 or 6*/ +} + +unsigned lodepng_is_palette_type(const LodePNGColorMode* info) { + return info->colortype == LCT_PALETTE; +} + +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) { + size_t i; + for(i = 0; i != info->palettesize; ++i) { + if(info->palette[i * 4 + 3] < 255) return 1; + } + return 0; +} + +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) { + return info->key_defined + || lodepng_is_alpha_type(info) + || lodepng_has_palette_alpha(info); +} + +static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); + size_t n = (size_t)w * (size_t)h; + return ((n / 8u) * bpp) + ((n & 7u) * bpp + 7u) / 8u; +} + +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) { + return lodepng_get_raw_size_lct(w, h, color->colortype, color->bitdepth); +} + + +#ifdef LODEPNG_COMPILE_PNG + +/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer, +and in addition has one extra byte per line: the filter byte. So this gives a larger +result than lodepng_get_raw_size. Set h to 1 to get the size of 1 row including filter byte. */ +static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, unsigned bpp) { + /* + 1 for the filter byte, and possibly plus padding bits per line. */ + /* Ignoring casts, the expression is equal to (w * bpp + 7) / 8 + 1, but avoids overflow of w * bpp */ + size_t line = ((size_t)(w / 8u) * bpp) + 1u + ((w & 7u) * bpp + 7u) / 8u; + return (size_t)h * line; +} + +#ifdef LODEPNG_COMPILE_DECODER +/*Safely checks whether size_t overflow can be caused due to amount of pixels. +This check is overcautious rather than precise. If this check indicates no overflow, +you can safely compute in a size_t (but not an unsigned): +-(size_t)w * (size_t)h * 8 +-amount of bytes in IDAT (including filter, padding and Adam7 bytes) +-amount of bytes in raw color model +Returns 1 if overflow possible, 0 if not. +*/ +static int lodepng_pixel_overflow(unsigned w, unsigned h, + const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor) { + size_t bpp = LODEPNG_MAX(lodepng_get_bpp(pngcolor), lodepng_get_bpp(rawcolor)); + size_t numpixels, total; + size_t line; /* bytes per line in worst case */ + + if(lodepng_mulofl((size_t)w, (size_t)h, &numpixels)) return 1; + if(lodepng_mulofl(numpixels, 8, &total)) return 1; /* bit pointer with 8-bit color, or 8 bytes per channel color */ + + /* Bytes per scanline with the expression "(w / 8u) * bpp) + ((w & 7u) * bpp + 7u) / 8u" */ + if(lodepng_mulofl((size_t)(w / 8u), bpp, &line)) return 1; + if(lodepng_addofl(line, ((w & 7u) * bpp + 7u) / 8u, &line)) return 1; + + if(lodepng_addofl(line, 5, &line)) return 1; /* 5 bytes overhead per line: 1 filterbyte, 4 for Adam7 worst case */ + if(lodepng_mulofl(line, h, &total)) return 1; /* Total bytes in worst case */ + + return 0; /* no overflow */ +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static void LodePNGUnknownChunks_init(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; + for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; +} + +static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); +} + +static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) { + unsigned i; + + LodePNGUnknownChunks_cleanup(dest); + + for(i = 0; i != 3; ++i) { + size_t j; + dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; + dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); + if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ + for(j = 0; j < src->unknown_chunks_size[i]; ++j) { + dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; + } + } + + return 0; +} + +/******************************************************************************/ + +static void LodePNGText_init(LodePNGInfo* info) { + info->text_num = 0; + info->text_keys = NULL; + info->text_strings = NULL; +} + +static void LodePNGText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->text_num; ++i) { + string_cleanup(&info->text_keys[i]); + string_cleanup(&info->text_strings[i]); + } + lodepng_free(info->text_keys); + lodepng_free(info->text_strings); +} + +static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->text_keys = NULL; + dest->text_strings = NULL; + dest->text_num = 0; + for(i = 0; i != source->text_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); + } + return 0; +} + +static unsigned lodepng_add_text_sized(LodePNGInfo* info, const char* key, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); + + if(new_keys) info->text_keys = new_keys; + if(new_strings) info->text_strings = new_strings; + + if(!new_keys || !new_strings) return 83; /*alloc fail*/ + + ++info->text_num; + info->text_keys[info->text_num - 1] = alloc_string(key); + info->text_strings[info->text_num - 1] = alloc_string_sized(str, size); + if(!info->text_keys[info->text_num - 1] || !info->text_strings[info->text_num - 1]) return 83; /*alloc fail*/ + + return 0; +} + +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) { + return lodepng_add_text_sized(info, key, str, lodepng_strlen(str)); +} + +void lodepng_clear_text(LodePNGInfo* info) { + LodePNGText_cleanup(info); +} + +/******************************************************************************/ + +static void LodePNGIText_init(LodePNGInfo* info) { + info->itext_num = 0; + info->itext_keys = NULL; + info->itext_langtags = NULL; + info->itext_transkeys = NULL; + info->itext_strings = NULL; +} + +static void LodePNGIText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->itext_num; ++i) { + string_cleanup(&info->itext_keys[i]); + string_cleanup(&info->itext_langtags[i]); + string_cleanup(&info->itext_transkeys[i]); + string_cleanup(&info->itext_strings[i]); + } + lodepng_free(info->itext_keys); + lodepng_free(info->itext_langtags); + lodepng_free(info->itext_transkeys); + lodepng_free(info->itext_strings); +} + +static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->itext_keys = NULL; + dest->itext_langtags = NULL; + dest->itext_transkeys = NULL; + dest->itext_strings = NULL; + dest->itext_num = 0; + for(i = 0; i != source->itext_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], + source->itext_transkeys[i], source->itext_strings[i])); + } + return 0; +} + +void lodepng_clear_itext(LodePNGInfo* info) { + LodePNGIText_cleanup(info); +} + +static unsigned lodepng_add_itext_sized(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); + char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); + char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); + + if(new_keys) info->itext_keys = new_keys; + if(new_langtags) info->itext_langtags = new_langtags; + if(new_transkeys) info->itext_transkeys = new_transkeys; + if(new_strings) info->itext_strings = new_strings; + + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) return 83; /*alloc fail*/ + + ++info->itext_num; + + info->itext_keys[info->itext_num - 1] = alloc_string(key); + info->itext_langtags[info->itext_num - 1] = alloc_string(langtag); + info->itext_transkeys[info->itext_num - 1] = alloc_string(transkey); + info->itext_strings[info->itext_num - 1] = alloc_string_sized(str, size); + + return 0; +} + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str) { + return lodepng_add_itext_sized(info, key, langtag, transkey, str, lodepng_strlen(str)); +} + +/* same as set but does not delete */ +static unsigned lodepng_assign_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(profile_size == 0) return 100; /*invalid ICC profile size*/ + + info->iccp_name = alloc_string(name); + info->iccp_profile = (unsigned char*)lodepng_malloc(profile_size); + + if(!info->iccp_name || !info->iccp_profile) return 83; /*alloc fail*/ + + lodepng_memcpy(info->iccp_profile, profile, profile_size); + info->iccp_profile_size = profile_size; + + return 0; /*ok*/ +} + +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(info->iccp_name) lodepng_clear_icc(info); + info->iccp_defined = 1; + + return lodepng_assign_icc(info, name, profile, profile_size); +} + +void lodepng_clear_icc(LodePNGInfo* info) { + string_cleanup(&info->iccp_name); + lodepng_free(info->iccp_profile); + info->iccp_profile = NULL; + info->iccp_profile_size = 0; + info->iccp_defined = 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void lodepng_info_init(LodePNGInfo* info) { + lodepng_color_mode_init(&info->color); + info->interlace_method = 0; + info->compression_method = 0; + info->filter_method = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNGText_init(info); + LodePNGIText_init(info); + + info->time_defined = 0; + info->phys_defined = 0; + + info->gama_defined = 0; + info->chrm_defined = 0; + info->srgb_defined = 0; + info->iccp_defined = 0; + info->iccp_name = NULL; + info->iccp_profile = NULL; + + LodePNGUnknownChunks_init(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void lodepng_info_cleanup(LodePNGInfo* info) { + lodepng_color_mode_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNGText_cleanup(info); + LodePNGIText_cleanup(info); + + lodepng_clear_icc(info); + + LodePNGUnknownChunks_cleanup(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + lodepng_info_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGInfo)); + lodepng_color_mode_init(&dest->color); + CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); + CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); + if(source->iccp_defined) { + CERROR_TRY_RETURN(lodepng_assign_icc(dest, source->iccp_name, source->iccp_profile, source->iccp_profile_size)); + } + + LodePNGUnknownChunks_init(dest); + CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) { + unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ + /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ + unsigned p = index & m; + in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ + in = in << (bits * (m - p)); + if(p == 0) out[index * bits / 8u] = in; + else out[index * bits / 8u] |= in; +} + +typedef struct ColorTree ColorTree; + +/* +One node of a color tree +This is the data structure used to count the number of unique colors and to get a palette +index for a color. It's like an octree, but because the alpha channel is used too, each +node has 16 instead of 8 children. +*/ +struct ColorTree { + ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ + int index; /*the payload. Only has a meaningful value if this is in the last level*/ +}; + +static void color_tree_init(ColorTree* tree) { + lodepng_memset(tree->children, 0, 16 * sizeof(*tree->children)); + tree->index = -1; +} + +static void color_tree_cleanup(ColorTree* tree) { + int i; + for(i = 0; i != 16; ++i) { + if(tree->children[i]) { + color_tree_cleanup(tree->children[i]); + lodepng_free(tree->children[i]); + } + } +} + +/*returns -1 if color not present, its index otherwise*/ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + int bit = 0; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) return -1; + else tree = tree->children[i]; + } + return tree ? tree->index : -1; +} + +#ifdef LODEPNG_COMPILE_ENCODER +static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + return color_tree_get(tree, r, g, b, a) >= 0; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*color is not allowed to already exist. +Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist") +Returns error code, or 0 if ok*/ +static unsigned color_tree_add(ColorTree* tree, + unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) { + int bit; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) { + tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); + if(!tree->children[i]) return 83; /*alloc fail*/ + color_tree_init(tree->children[i]); + } + tree = tree->children[i]; + } + tree->index = (int)index; + return 0; +} + +/*put a pixel, given its RGBA color, into image of any color type*/ +static unsigned rgba8ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(mode->colortype == LCT_GREY) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) out[i] = gray; + else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = gray; + else { + /*take the most significant bits of gray*/ + gray = ((unsigned)gray >> (8u - mode->bitdepth)) & ((1u << mode->bitdepth) - 1u); + addColorBits(out, i, mode->bitdepth, gray); + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } else { + out[i * 6 + 0] = out[i * 6 + 1] = r; + out[i * 6 + 2] = out[i * 6 + 3] = g; + out[i * 6 + 4] = out[i * 6 + 5] = b; + } + } else if(mode->colortype == LCT_PALETTE) { + int index = color_tree_get(tree, r, g, b, a); + if(index < 0) return 82; /*color not in palette*/ + if(mode->bitdepth == 8) out[i] = index; + else addColorBits(out, i, mode->bitdepth, (unsigned)index); + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) { + out[i * 2 + 0] = gray; + out[i * 2 + 1] = a; + } else if(mode->bitdepth == 16) { + out[i * 4 + 0] = out[i * 4 + 1] = gray; + out[i * 4 + 2] = out[i * 4 + 3] = a; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + out[i * 4 + 0] = r; + out[i * 4 + 1] = g; + out[i * 4 + 2] = b; + out[i * 4 + 3] = a; + } else { + out[i * 8 + 0] = out[i * 8 + 1] = r; + out[i * 8 + 2] = out[i * 8 + 3] = g; + out[i * 8 + 4] = out[i * 8 + 5] = b; + out[i * 8 + 6] = out[i * 8 + 7] = a; + } + } + + return 0; /*no error*/ +} + +/*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ +static void rgba16ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, + unsigned short r, unsigned short g, unsigned short b, unsigned short a) { + if(mode->colortype == LCT_GREY) { + unsigned short gray = ((unsigned)r + g + b) / 3u; + out[i * 2 + 0] = (gray >> 8) & 255; + out[i * 2 + 1] = gray & 255; + } else if(mode->colortype == LCT_RGB) { + out[i * 6 + 0] = (r >> 8) & 255; + out[i * 6 + 1] = r & 255; + out[i * 6 + 2] = (g >> 8) & 255; + out[i * 6 + 3] = g & 255; + out[i * 6 + 4] = (b >> 8) & 255; + out[i * 6 + 5] = b & 255; + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned short gray = ((unsigned)r + g + b) / 3u; + out[i * 4 + 0] = (gray >> 8) & 255; + out[i * 4 + 1] = gray & 255; + out[i * 4 + 2] = (a >> 8) & 255; + out[i * 4 + 3] = a & 255; + } else if(mode->colortype == LCT_RGBA) { + out[i * 8 + 0] = (r >> 8) & 255; + out[i * 8 + 1] = r & 255; + out[i * 8 + 2] = (g >> 8) & 255; + out[i * 8 + 3] = g & 255; + out[i * 8 + 4] = (b >> 8) & 255; + out[i * 8 + 5] = b & 255; + out[i * 8 + 6] = (a >> 8) & 255; + out[i * 8 + 7] = a & 255; + } +} + +/*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ +static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, + unsigned char* b, unsigned char* a, + const unsigned char* in, size_t i, + const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i]; + if(mode->key_defined && *r == mode->key_r) *a = 0; + else *a = 255; + } else if(mode->bitdepth == 16) { + *r = *g = *b = in[i * 2 + 0]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 255; + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = i * mode->bitdepth; + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + *r = *g = *b = (value * 255) / highest; + if(mode->key_defined && value == mode->key_r) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; + if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; + else *a = 255; + } else { + *r = in[i * 6 + 0]; + *g = in[i * 6 + 2]; + *b = in[i * 6 + 4]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_PALETTE) { + unsigned index; + if(mode->bitdepth == 8) index = in[i]; + else { + size_t j = i * mode->bitdepth; + index = readBitsFromReversedStream(&j, in, mode->bitdepth); + } + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + *r = mode->palette[index * 4 + 0]; + *g = mode->palette[index * 4 + 1]; + *b = mode->palette[index * 4 + 2]; + *a = mode->palette[index * 4 + 3]; + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i * 2 + 0]; + *a = in[i * 2 + 1]; + } else { + *r = *g = *b = in[i * 4 + 0]; + *a = in[i * 4 + 2]; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + *r = in[i * 4 + 0]; + *g = in[i * 4 + 1]; + *b = in[i * 4 + 2]; + *a = in[i * 4 + 3]; + } else { + *r = in[i * 8 + 0]; + *g = in[i * 8 + 2]; + *b = in[i * 8 + 4]; + *a = in[i * 8 + 6]; + } + } +} + +/*Similar to getPixelColorRGBA8, but with all the for loops inside of the color +mode test cases, optimized to convert the colors much faster, when converting +to the common case of RGBA with 8 bit per channel. buffer must be RGBA with +enough memory.*/ +static void getPixelColorsRGBA8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + unsigned num_channels = 4; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r) buffer[3] = 0; + } + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 3], 3); + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r && buffer[1]== mode->key_g && buffer[2] == mode->key_b) buffer[3] = 0; + } + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + buffer[3] = mode->key_defined + && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + buffer[3] = in[i * 2 + 1]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + buffer[3] = in[i * 4 + 2]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 4); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + buffer[3] = in[i * 8 + 6]; + } + } + } +} + +/*Similar to getPixelColorsRGBA8, but with 3-channel RGB output.*/ +static void getPixelColorsRGB8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + const unsigned num_channels = 3; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 3); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 4], 3); + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + } + } + } +} + +/*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with +given color type, but the given color type must be 16-bit itself.*/ +static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, + const unsigned char* in, size_t i, const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_RGB) { + *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; + *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; + *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; + if(mode->key_defined + && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_GREY_ALPHA) { + *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; + *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; + } else if(mode->colortype == LCT_RGBA) { + *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; + *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; + *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; + *a = 256u * in[i * 8 + 6] + in[i * 8 + 7]; + } +} + +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + if(mode_in->colortype == LCT_PALETTE && !mode_in->palette) { + return 107; /* error: must provide palette if input mode is palette */ + } + + if(lodepng_color_mode_equal(mode_out, mode_in)) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + + if(mode_out->colortype == LCT_PALETTE) { + size_t palettesize = mode_out->palettesize; + const unsigned char* palette = mode_out->palette; + size_t palsize = (size_t)1u << mode_out->bitdepth; + /*if the user specified output palette but did not give the values, assume + they want the values of the input color type (assuming that one is palette). + Note that we never create a new palette ourselves.*/ + if(palettesize == 0) { + palettesize = mode_in->palettesize; + palette = mode_in->palette; + /*if the input was also palette with same bitdepth, then the color types are also + equal, so copy literally. This to preserve the exact indices that were in the PNG + even in case there are duplicate colors in the palette.*/ + if(mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + } + if(palettesize < palsize) palsize = palettesize; + color_tree_init(&tree); + for(i = 0; i != palsize; ++i) { + const unsigned char* p = &palette[i * 4]; + error = color_tree_add(&tree, p[0], p[1], p[2], p[3], (unsigned)i); + if(error) break; + } + } + + if(!error) { + if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) { + for(i = 0; i != numpixels; ++i) { + unsigned short r = 0, g = 0, b = 0, a = 0; + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + rgba16ToPixel(out, i, mode_out, r, g, b, a); + } + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) { + getPixelColorsRGBA8(out, numpixels, in, mode_in); + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) { + getPixelColorsRGB8(out, numpixels, in, mode_in); + } else { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); + if(error) break; + } + } + } + + if(mode_out->colortype == LCT_PALETTE) { + color_tree_cleanup(&tree); + } + + return error; +} + + +/* Converts a single rgb color without alpha from one type to another, color bits truncated to +their bitdepth. In case of single channel (gray or palette), only the r channel is used. Slow +function, do not use to process all pixels of an image. Alpha channel not supported on purpose: +this is for bKGD, supporting alpha may prevent it from finding a color in the palette, from the +specification it looks like bKGD should ignore the alpha values of the palette since it can use +any palette index but doesn't have an alpha channel. Idem with ignoring color key. */ +unsigned lodepng_convert_rgb( + unsigned* r_out, unsigned* g_out, unsigned* b_out, + unsigned r_in, unsigned g_in, unsigned b_in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in) { + unsigned r = 0, g = 0, b = 0; + unsigned mul = 65535 / ((1u << mode_in->bitdepth) - 1u); /*65535, 21845, 4369, 257, 1*/ + unsigned shift = 16 - mode_out->bitdepth; + + if(mode_in->colortype == LCT_GREY || mode_in->colortype == LCT_GREY_ALPHA) { + r = g = b = r_in * mul; + } else if(mode_in->colortype == LCT_RGB || mode_in->colortype == LCT_RGBA) { + r = r_in * mul; + g = g_in * mul; + b = b_in * mul; + } else if(mode_in->colortype == LCT_PALETTE) { + if(r_in >= mode_in->palettesize) return 82; + r = mode_in->palette[r_in * 4 + 0] * 257u; + g = mode_in->palette[r_in * 4 + 1] * 257u; + b = mode_in->palette[r_in * 4 + 2] * 257u; + } else { + return 31; + } + + /* now convert to output format */ + if(mode_out->colortype == LCT_GREY || mode_out->colortype == LCT_GREY_ALPHA) { + *r_out = r >> shift ; + } else if(mode_out->colortype == LCT_RGB || mode_out->colortype == LCT_RGBA) { + *r_out = r >> shift ; + *g_out = g >> shift ; + *b_out = b >> shift ; + } else if(mode_out->colortype == LCT_PALETTE) { + unsigned i; + /* a 16-bit color cannot be in the palette */ + if((r >> 8) != (r & 255) || (g >> 8) != (g & 255) || (b >> 8) != (b & 255)) return 82; + for(i = 0; i < mode_out->palettesize; i++) { + unsigned j = i * 4; + if((r >> 8) == mode_out->palette[j + 0] && (g >> 8) == mode_out->palette[j + 1] && + (b >> 8) == mode_out->palette[j + 2]) { + *r_out = i; + return 0; + } + } + return 82; + } else { + return 31; + } + + return 0; +} + +#ifdef LODEPNG_COMPILE_ENCODER + +void lodepng_color_stats_init(LodePNGColorStats* stats) { + /*stats*/ + stats->colored = 0; + stats->key = 0; + stats->key_r = stats->key_g = stats->key_b = 0; + stats->alpha = 0; + stats->numcolors = 0; + stats->bits = 1; + stats->numpixels = 0; + /*settings*/ + stats->allow_palette = 1; + stats->allow_greyscale = 1; +} + +/*function used for debug purposes with C++*/ +/*void printColorStats(LodePNGColorStats* p) { + std::cout << "colored: " << (int)p->colored << ", "; + std::cout << "key: " << (int)p->key << ", "; + std::cout << "key_r: " << (int)p->key_r << ", "; + std::cout << "key_g: " << (int)p->key_g << ", "; + std::cout << "key_b: " << (int)p->key_b << ", "; + std::cout << "alpha: " << (int)p->alpha << ", "; + std::cout << "numcolors: " << (int)p->numcolors << ", "; + std::cout << "bits: " << (int)p->bits << std::endl; +}*/ + +/*Returns how many bits needed to represent given value (max 8 bit)*/ +static unsigned getValueRequiredBits(unsigned char value) { + if(value == 0 || value == 255) return 1; + /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ + if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; + return 8; +} + +/*stats must already have been inited. */ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* mode_in) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + /* mark things as done already if it would be impossible to have a more expensive case */ + unsigned colored_done = lodepng_is_greyscale_type(mode_in) ? 1 : 0; + unsigned alpha_done = lodepng_can_have_alpha(mode_in) ? 0 : 1; + unsigned numcolors_done = 0; + unsigned bpp = lodepng_get_bpp(mode_in); + unsigned bits_done = (stats->bits == 1 && bpp == 1) ? 1 : 0; + unsigned sixteen = 0; /* whether the input image is 16 bit */ + unsigned maxnumcolors = 257; + if(bpp <= 8) maxnumcolors = LODEPNG_MIN(257, stats->numcolors + (1u << bpp)); + + stats->numpixels += numpixels; + + /*if palette not allowed, no need to compute numcolors*/ + if(!stats->allow_palette) numcolors_done = 1; + + color_tree_init(&tree); + + /*If the stats was already filled in from previous data, fill its palette in tree + and mark things as done already if we know they are the most expensive case already*/ + if(stats->alpha) alpha_done = 1; + if(stats->colored) colored_done = 1; + if(stats->bits == 16) numcolors_done = 1; + if(stats->bits >= bpp) bits_done = 1; + if(stats->numcolors >= maxnumcolors) numcolors_done = 1; + + if(!numcolors_done) { + for(i = 0; i < stats->numcolors; i++) { + const unsigned char* color = &stats->palette[i * 4]; + error = color_tree_add(&tree, color[0], color[1], color[2], color[3], i); + if(error) goto cleanup; + } + } + + /*Check if the 16-bit input is truly 16-bit*/ + if(mode_in->bitdepth == 16 && !sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || + (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ { + stats->bits = 16; + sixteen = 1; + bits_done = 1; + numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ + break; + } + } + } + + if(sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 65535 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 65535 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + } + } else /* < 16-bit */ { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + + if(!bits_done && stats->bits < 8) { + /*only r is checked, < 8 bits is only relevant for grayscale*/ + unsigned bits = getValueRequiredBits(r); + if(bits > stats->bits) stats->bits = bits; + } + bits_done = (stats->bits >= bpp); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 255 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 255 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + + if(!numcolors_done) { + if(!color_tree_has(&tree, r, g, b, a)) { + error = color_tree_add(&tree, r, g, b, a, stats->numcolors); + if(error) goto cleanup; + if(stats->numcolors < 256) { + unsigned char* p = stats->palette; + unsigned n = stats->numcolors; + p[n * 4 + 0] = r; + p[n * 4 + 1] = g; + p[n * 4 + 2] = b; + p[n * 4 + 3] = a; + } + ++stats->numcolors; + numcolors_done = stats->numcolors >= maxnumcolors; + } + } + + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + } + + /*make the stats's key always 16-bit for consistency - repeat each byte twice*/ + stats->key_r += (stats->key_r << 8); + stats->key_g += (stats->key_g << 8); + stats->key_b += (stats->key_b << 8); + } + +cleanup: + color_tree_cleanup(&tree); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*Adds a single color to the color stats. The stats must already have been inited. The color must be given as 16-bit +(with 2 bytes repeating for 8-bit and 65535 for opaque alpha channel). This function is expensive, do not call it for +all pixels of an image but only for a few additional values. */ +static unsigned lodepng_color_stats_add(LodePNGColorStats* stats, + unsigned r, unsigned g, unsigned b, unsigned a) { + unsigned error = 0; + unsigned char image[8]; + LodePNGColorMode mode; + lodepng_color_mode_init(&mode); + image[0] = r >> 8; image[1] = r; image[2] = g >> 8; image[3] = g; + image[4] = b >> 8; image[5] = b; image[6] = a >> 8; image[7] = a; + mode.bitdepth = 16; + mode.colortype = LCT_RGBA; + error = lodepng_compute_color_stats(stats, image, 1, 1, &mode); + lodepng_color_mode_cleanup(&mode); + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Computes a minimal PNG color model that can contain all colors as indicated by the stats. +The stats should be computed with lodepng_compute_color_stats. +mode_in is raw color profile of the image the stats were computed on, to copy palette order from when relevant. +Minimal PNG color model means the color type and bit depth that gives smallest amount of bits in the output image, +e.g. gray if only grayscale pixels, palette if less than 256 colors, color key if only single transparent color, ... +This is used if auto_convert is enabled (it is by default). +*/ +static unsigned auto_choose_color(LodePNGColorMode* mode_out, + const LodePNGColorMode* mode_in, + const LodePNGColorStats* stats) { + unsigned error = 0; + unsigned palettebits; + size_t i, n; + size_t numpixels = stats->numpixels; + unsigned palette_ok, gray_ok; + + unsigned alpha = stats->alpha; + unsigned key = stats->key; + unsigned bits = stats->bits; + + mode_out->key_defined = 0; + + if(key && numpixels <= 16) { + alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ + key = 0; + if(bits < 8) bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + + gray_ok = !stats->colored; + if(!stats->allow_greyscale) gray_ok = 0; + if(!gray_ok && bits < 8) bits = 8; + + n = stats->numcolors; + palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); + palette_ok = n <= 256 && bits <= 8 && n != 0; /*n==0 means likely numcolors wasn't computed*/ + if(numpixels < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ + if(gray_ok && !alpha && bits <= palettebits) palette_ok = 0; /*gray is less overhead*/ + if(!stats->allow_palette) palette_ok = 0; + + if(palette_ok) { + const unsigned char* p = stats->palette; + lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ + for(i = 0; i != stats->numcolors; ++i) { + error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); + if(error) break; + } + + mode_out->colortype = LCT_PALETTE; + mode_out->bitdepth = palettebits; + + if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize + && mode_in->bitdepth == mode_out->bitdepth) { + /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ + lodepng_color_mode_cleanup(mode_out); + lodepng_color_mode_copy(mode_out, mode_in); + } + } else /*8-bit or 16-bit per channel*/ { + mode_out->bitdepth = bits; + mode_out->colortype = alpha ? (gray_ok ? LCT_GREY_ALPHA : LCT_RGBA) + : (gray_ok ? LCT_GREY : LCT_RGB); + if(key) { + unsigned mask = (1u << mode_out->bitdepth) - 1u; /*stats always uses 16-bit, mask converts it*/ + mode_out->key_r = stats->key_r & mask; + mode_out->key_g = stats->key_g & mask; + mode_out->key_b = stats->key_b & mask; + mode_out->key_defined = 1; + } + } + + return error; +} + +#endif /* #ifdef LODEPNG_COMPILE_ENCODER */ + +/* +Paeth predictor, used by PNG filter type 4 +The parameters are of type short, but should come from unsigned chars, the shorts +are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) { + short pa = LODEPNG_ABS(b - c); + short pb = LODEPNG_ABS(a - c); + short pc = LODEPNG_ABS(a + b - c - c); + /* return input value associated with smallest of pa, pb, pc (with certain priority if equal) */ + if(pb < pa) { a = b; pa = pb; } + return (pc < pa) ? c : a; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +/* +Outputs various dimensions and positions in the image related to the Adam7 reduced images. +passw: output containing the width of the 7 passes +passh: output containing the height of the 7 passes +filter_passstart: output containing the index of the start and end of each + reduced image with filter bytes +padded_passstart output containing the index of the start and end of each + reduced image when without filter bytes but with padded scanlines +passstart: output containing the index of the start and end of each reduced + image without padding between scanlines, but still padding between the images +w, h: width and height of non-interlaced image +bpp: bits per pixel +"padded" is only relevant if bpp is less than 8 and a scanline or image does not + end at a full byte +*/ +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) { + /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i != 7; ++i) { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i != 7; ++i) { + /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1u + (passw[i] * bpp + 7u) / 8u) : 0); + /*bits padded if needed to fill full byte at end of each scanline*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7u) / 8u); + /*only padded at end of reduced image*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7u) / 8u; + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNGInfo. return value is error*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned width, height; + LodePNGInfo* info = &state->info_png; + if(insize == 0 || in == 0) { + CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ + } + if(insize < 33) { + CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ + } + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + /* TODO: remove this. One should use a new LodePNGState for new sessions */ + lodepng_info_cleanup(info); + lodepng_info_init(info); + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { + CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ + } + if(lodepng_chunk_length(in + 8) != 13) { + CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ + } + if(!lodepng_chunk_type_equals(in + 8, "IHDR")) { + CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ + } + + /*read the values given in the header*/ + width = lodepng_read32bitInt(&in[16]); + height = lodepng_read32bitInt(&in[20]); + /*TODO: remove the undocumented feature that allows to give null pointers to width or height*/ + if(w) *w = width; + if(h) *h = height; + info->color.bitdepth = in[24]; + info->color.colortype = (LodePNGColorType)in[25]; + info->compression_method = in[26]; + info->filter_method = in[27]; + info->interlace_method = in[28]; + + /*errors returned only after the parsing so other values are still output*/ + + /*error: invalid image size*/ + if(width == 0 || height == 0) CERROR_RETURN_ERROR(state->error, 93); + /*error: invalid colortype or bitdepth combination*/ + state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); + if(state->error) return state->error; + /*error: only compression method 0 is allowed in the specification*/ + if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); + /*error: only filter method 0 is allowed in the specification*/ + if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); + /*error: only interlace methods 0 and 1 exist in the specification*/ + if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); + + if(!state->decoder.ignore_crc) { + unsigned CRC = lodepng_read32bitInt(&in[29]); + unsigned checksum = lodepng_crc32(&in[12], 17); + if(CRC != checksum) { + CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ + } + } + + return state->error; +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, + size_t bytewidth, unsigned char filterType, size_t length) { + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) { + case 0: + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + break; + case 1: { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + recon[j]; + break; + } + case 2: + if(precon) { + for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; + } else { + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + } + break; + case 3: + if(precon) { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1u); + /* Unroll independent paths of this predictor. A 6x and 8x version is also possible but that adds + too much code. Whether this speeds up anything depends on compiler and settings. */ + if(bytewidth >= 4) { + for(; i + 3 < length; i += 4, j += 4) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + recon[i + 2] = s2 + ((r2 + p2) >> 1u); + recon[i + 3] = s3 + ((r3 + p3) >> 1u); + } + } else if(bytewidth >= 3) { + for(; i + 2 < length; i += 3, j += 3) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + recon[i + 2] = s2 + ((r2 + p2) >> 1u); + } + } else if(bytewidth >= 2) { + for(; i + 1 < length; i += 2, j += 2) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; + recon[i + 0] = s0 + ((r0 + p0) >> 1u); + recon[i + 1] = s1 + ((r1 + p1) >> 1u); + } + } + for(; i != length; ++i, ++j) recon[i] = scanline[i] + ((recon[j] + precon[i]) >> 1u); + } else { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i != length; ++i, ++j) recon[i] = scanline[i] + (recon[j] >> 1u); + } + break; + case 4: + if(precon) { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + + /* Unroll independent paths of the paeth predictor. A 6x and 8x version is also possible but that + adds too much code. Whether this speeds up anything depends on compiler and settings. */ + if(bytewidth >= 4) { + for(; i + 3 < length; i += 4, j += 4) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2], q3 = precon[j + 3]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + recon[i + 3] = s3 + paethPredictor(r3, p3, q3); + } + } else if(bytewidth >= 3) { + for(; i + 2 < length; i += 3, j += 3) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + } + } else if(bytewidth >= 2) { + for(; i + 1 < length; i += 2, j += 2) { + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + } + } + + for(; i != length; ++i, ++j) { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[j])); + } + } else { + size_t j = 0; + for(i = 0; i != bytewidth; ++i) { + recon[i] = scanline[i]; + } + for(i = bytewidth; i != length; ++i, ++j) { + /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ + recon[i] = (scanline[i] + recon[j]); + } + } + break; + default: return 36; /*error: invalid filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) + */ + + unsigned y; + unsigned char* prevline = 0; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + for(y = 0; y < h; ++y) { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); + + prevline = &out[outindex]; + } + + return 0; +} + +/* +in: Adam7 interlaced image, with no padding bits between scanlines, but between + reduced images so that each reduced image starts at a byte. +out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h +bpp: bits per pixel +out has the following size in bits: w * h * bpp. +in is possibly bigger due to padding bits between reduced images. +out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation +(because that's likely a little bit faster) +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * (size_t)w + + ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for(y = 0; y < h; ++y) { + size_t x; + for(x = 0; x < olinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from +the IDAT chunks (with filter index bytes and possible padding bits) +return value is error*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, + unsigned w, unsigned h, const LodePNGInfo* info_png) { + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= possible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(info_png->interlace_method == 0) { + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); + removePaddingBits(out, in, w * bpp, ((w * bpp + 7u) / 8u) * 8u, h); + } + /*we can immediately filter into the out buffer, no other steps needed*/ + else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i != 7; ++i) { + CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); + /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all*/ + if(bpp < 8) { + /*remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, + ((passw[i] * bpp + 7u) / 8u) * 8u, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return 0; +} + +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned pos = 0, i; + color->palettesize = chunkLength / 3u; + if(color->palettesize == 0 || color->palettesize > 256) return 38; /*error: palette too small or big*/ + lodepng_color_mode_alloc_palette(color); + if(!color->palette && color->palettesize) { + color->palettesize = 0; + return 83; /*alloc fail*/ + } + + for(i = 0; i != color->palettesize; ++i) { + color->palette[4 * i + 0] = data[pos++]; /*R*/ + color->palette[4 * i + 1] = data[pos++]; /*G*/ + color->palette[4 * i + 2] = data[pos++]; /*B*/ + color->palette[4 * i + 3] = 255; /*alpha*/ + } + + return 0; /* OK */ +} + +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned i; + if(color->colortype == LCT_PALETTE) { + /*error: more alpha values given than there are palette entries*/ + if(chunkLength > color->palettesize) return 39; + + for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; + } else if(color->colortype == LCT_GREY) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 30; + + color->key_defined = 1; + color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; + } else if(color->colortype == LCT_RGB) { + /*error: this chunk must be 6 bytes for RGB image*/ + if(chunkLength != 6) return 41; + + color->key_defined = 1; + color->key_r = 256u * data[0] + data[1]; + color->key_g = 256u * data[2] + data[3]; + color->key_b = 256u * data[4] + data[5]; + } + else return 42; /*error: tRNS chunk not allowed for other color models*/ + + return 0; /* OK */ +} + + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*background color chunk (bKGD)*/ +static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(info->color.colortype == LCT_PALETTE) { + /*error: this chunk must be 1 byte for indexed color image*/ + if(chunkLength != 1) return 43; + + /*error: invalid palette index, or maybe this chunk appeared before PLTE*/ + if(data[0] >= info->color.palettesize) return 103; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = data[0]; + } else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 44; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + /*error: this chunk must be 6 bytes for grayscale image*/ + if(chunkLength != 6) return 45; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = 256u * data[0] + data[1]; + info->background_g = 256u * data[2] + data[3]; + info->background_b = 256u * data[4] + data[5]; + } + + return 0; /* OK */ +} + +/*text chunk (tEXt)*/ +static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + char *key = 0, *str = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + unsigned length, string2_begin; + + length = 0; + while(length < chunkLength && data[length] != 0) ++length; + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + string2_begin = length + 1; /*skip keyword null terminator*/ + + length = (unsigned)(chunkLength < string2_begin ? 0 : chunkLength - string2_begin); + str = (char*)lodepng_malloc(length + 1); + if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(str, data + string2_begin, length); + str[length] = 0; + + error = lodepng_add_text(info, key, str); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*compressed text chunk (zTXt)*/ +static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, string2_begin; + char *key = 0; + unsigned char* str = 0; + size_t size = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + zlibsettings.max_output_size = decoder->max_text_size; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[string2_begin], + length, &zlibsettings); + /*error: compressed text larger than decoder->max_text_size*/ + if(error && size > zlibsettings.max_output_size) error = 112; + if(error) break; + error = lodepng_add_text_sized(info, key, (char*)str, size); + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*international text chunk (iTXt)*/ +static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + /*Quick check if the chunk length isn't too small. Even without check + it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ + if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ + + /*read the key*/ + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + /*read the compression method*/ + compressed = data[length + 1]; + if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty for the next 3 texts*/ + + /*read the langtag*/ + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + langtag = (char*)lodepng_malloc(length + 1); + if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(langtag, data + begin, length); + langtag[length] = 0; + + /*read the transkey*/ + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + transkey = (char*)lodepng_malloc(length + 1); + if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(transkey, data + begin, length); + transkey[length] = 0; + + /*read the actual text*/ + begin += length + 1; + + length = (unsigned)chunkLength < begin ? 0 : (unsigned)chunkLength - begin; + + if(compressed) { + unsigned char* str = 0; + size_t size = 0; + zlibsettings.max_output_size = decoder->max_text_size; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[begin], + length, &zlibsettings); + /*error: compressed text larger than decoder->max_text_size*/ + if(error && size > zlibsettings.max_output_size) error = 112; + if(!error) error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)str, size); + lodepng_free(str); + } else { + error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)(data + begin), length); + } + + break; + } + + lodepng_free(key); + lodepng_free(langtag); + lodepng_free(transkey); + + return error; +} + +static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ + + info->time_defined = 1; + info->time.year = 256u * data[0] + data[1]; + info->time.month = data[2]; + info->time.day = data[3]; + info->time.hour = data[4]; + info->time.minute = data[5]; + info->time.second = data[6]; + + return 0; /* OK */ +} + +static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ + + info->phys_defined = 1; + info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; + info->phys_unit = data[8]; + + return 0; /* OK */ +} + +static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 4) return 96; /*invalid gAMA chunk size*/ + + info->gama_defined = 1; + info->gama_gamma = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + + return 0; /* OK */ +} + +static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 32) return 97; /*invalid cHRM chunk size*/ + + info->chrm_defined = 1; + info->chrm_white_x = 16777216u * data[ 0] + 65536u * data[ 1] + 256u * data[ 2] + data[ 3]; + info->chrm_white_y = 16777216u * data[ 4] + 65536u * data[ 5] + 256u * data[ 6] + data[ 7]; + info->chrm_red_x = 16777216u * data[ 8] + 65536u * data[ 9] + 256u * data[10] + data[11]; + info->chrm_red_y = 16777216u * data[12] + 65536u * data[13] + 256u * data[14] + data[15]; + info->chrm_green_x = 16777216u * data[16] + 65536u * data[17] + 256u * data[18] + data[19]; + info->chrm_green_y = 16777216u * data[20] + 65536u * data[21] + 256u * data[22] + data[23]; + info->chrm_blue_x = 16777216u * data[24] + 65536u * data[25] + 256u * data[26] + data[27]; + info->chrm_blue_y = 16777216u * data[28] + 65536u * data[29] + 256u * data[30] + data[31]; + + return 0; /* OK */ +} + +static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 1) return 98; /*invalid sRGB chunk size (this one is never ignored)*/ + + info->srgb_defined = 1; + info->srgb_intent = data[0]; + + return 0; /* OK */ +} + +static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + size_t size = 0; + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, string2_begin; + + info->iccp_defined = 1; + if(info->iccp_name) lodepng_clear_icc(info); + + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) return 75; /*no null termination, corrupt?*/ + if(length < 1 || length > 79) return 89; /*keyword too short or long*/ + + info->iccp_name = (char*)lodepng_malloc(length + 1); + if(!info->iccp_name) return 83; /*alloc fail*/ + + info->iccp_name[length] = 0; + for(i = 0; i != length; ++i) info->iccp_name[i] = (char)data[i]; + + if(data[length + 1] != 0) return 72; /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) return 75; /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + zlibsettings.max_output_size = decoder->max_icc_size; + error = zlib_decompress(&info->iccp_profile, &size, 0, + &data[string2_begin], + length, &zlibsettings); + /*error: ICC profile larger than decoder->max_icc_size*/ + if(error && size > zlibsettings.max_output_size) error = 113; + info->iccp_profile_size = size; + if(!error && !info->iccp_profile_size) error = 100; /*invalid ICC profile size*/ + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize) { + const unsigned char* chunk = in + pos; + unsigned chunkLength; + const unsigned char* data; + unsigned unhandled = 0; + unsigned error = 0; + + if(pos + 4 > insize) return 30; + chunkLength = lodepng_chunk_length(chunk); + if(chunkLength > 2147483647) return 63; + data = lodepng_chunk_data_const(chunk); + if(data + chunkLength + 4 > in + insize) return 30; + + if(lodepng_chunk_type_equals(chunk, "PLTE")) { + error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + error = readChunk_tRNS(&state->info_png.color, data, chunkLength); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + error = readChunk_bKGD(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + error = readChunk_tEXt(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + error = readChunk_tIME(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + error = readChunk_pHYs(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + error = readChunk_gAMA(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + error = readChunk_cHRM(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + error = readChunk_sRGB(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else { + /* unhandled chunk is ok (is not an error) */ + unhandled = 1; + } + + if(!error && !unhandled && !state->decoder.ignore_crc) { + if(lodepng_chunk_check_crc(chunk)) return 57; /*invalid CRC*/ + } + + return error; +} + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned char IEND = 0; + const unsigned char* chunk; + unsigned char* idat; /*the data from idat chunks, zlib compressed*/ + size_t idatsize = 0; + unsigned char* scanlines = 0; + size_t scanlines_size = 0, expected_size = 0; + size_t outsize = 0; + + /*for unknown chunk order*/ + unsigned unknown = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + + state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ + if(state->error) return; + + if(lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) { + CERROR_RETURN(state->error, 92); /*overflow possible due to amount of pixels*/ + } + + /*the input filesize is a safe upper bound for the sum of idat chunks size*/ + idat = (unsigned char*)lodepng_malloc(insize); + if(!idat) CERROR_RETURN(state->error, 83); /*alloc fail*/ + + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while(!IEND && !state->error) { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + + /*error: size of the in buffer too small to contain next chunk*/ + if((size_t)((chunk - in) + 12) > insize || chunk < in) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 30); + } + + /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ + chunkLength = lodepng_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if(chunkLength > 2147483647) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 63); + } + + if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) { + CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/ + } + + data = lodepng_chunk_data_const(chunk); + + unknown = 0; + + /*IDAT chunk, containing compressed image data*/ + if(lodepng_chunk_type_equals(chunk, "IDAT")) { + size_t newsize; + if(lodepng_addofl(idatsize, chunkLength, &newsize)) CERROR_BREAK(state->error, 95); + if(newsize > insize) CERROR_BREAK(state->error, 95); + lodepng_memcpy(idat + idatsize, data, chunkLength); + idatsize += chunkLength; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 3; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "IEND")) { + /*IEND chunk*/ + IEND = 1; + } else if(lodepng_chunk_type_equals(chunk, "PLTE")) { + /*palette chunk (PLTE)*/ + state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 2; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled + in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that + affects the alpha channel of pixels. */ + state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + state->error = readChunk_bKGD(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + /*text chunk (tEXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_tEXt(&state->info_png, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + /*compressed text chunk (zTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + /*international text chunk (iTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + state->error = readChunk_tIME(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + state->error = readChunk_pHYs(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + state->error = readChunk_gAMA(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + state->error = readChunk_cHRM(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + state->error = readChunk_sRGB(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + state->error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else /*it's not an implemented chunk type, so ignore it: skip over the data*/ { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if(!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) { + CERROR_BREAK(state->error, 69); + } + + unknown = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(state->decoder.remember_unknown_chunks) { + state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], + &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + + if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ { + if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ + } + + if(!IEND) chunk = lodepng_chunk_next_const(chunk, in + insize); + } + + if(!state->error && state->info_png.color.colortype == LCT_PALETTE && !state->info_png.color.palette) { + state->error = 106; /* error: PNG file must have PLTE chunk if color type is palette */ + } + + if(!state->error) { + /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. + If the decompressed size does not match the prediction, the image must be corrupt.*/ + if(state->info_png.interlace_method == 0) { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + expected_size = lodepng_get_raw_size_idat(*w, *h, bpp); + } else { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + /*Adam-7 interlaced: expected size is the sum of the 7 sub-images sizes*/ + expected_size = 0; + expected_size += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, bpp); + if(*w > 4) expected_size += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, bpp); + if(*w > 2) expected_size += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, bpp); + if(*w > 1) expected_size += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, bpp); + } + + state->error = zlib_decompress(&scanlines, &scanlines_size, expected_size, idat, idatsize, &state->decoder.zlibsettings); + } + if(!state->error && scanlines_size != expected_size) state->error = 91; /*decompressed size doesn't match prediction*/ + lodepng_free(idat); + + if(!state->error) { + outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!*out) state->error = 83; /*alloc fail*/ + } + if(!state->error) { + lodepng_memset(*out, 0, outsize); + state->error = postProcessScanlines(*out, scanlines, *w, *h, &state->info_png); + } + lodepng_free(scanlines); +} + +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + *out = 0; + decodeGeneric(out, w, h, state, in, insize); + if(state->error) return state->error; + if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) { + /*same color type, no copying or converting of data needed*/ + /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype + the raw image has to the end user*/ + if(!state->decoder.color_convert) { + state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); + if(state->error) return state->error; + } + } else { /*color conversion needed*/ + unsigned char* data = *out; + size_t outsize; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from grayscale input color type, to 8-bit grayscale or grayscale with alpha"*/ + if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA + || state->info_raw.colortype == LCT_CUSTOM) && !(state->info_raw.bitdepth == 8)) { + return 56; /*unsupported color mode conversion*/ + } + + outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!(*out)) { + state->error = 83; /*alloc fail*/ + } else if (state->lodepng_convert && state->info_raw.colortype == LCT_CUSTOM) { + state->error = state->lodepng_convert(*out, data, &state->info_raw, + &state->info_png.color, *w, *h); + } else { + state->error = lodepng_convert(*out, data, &state->info_raw, + &state->info_png.color, *w, *h); + } + lodepng_free(data); + } + return state->error; +} + +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*disable reading things that this function doesn't output*/ + state.decoder.read_text_chunks = 0; + state.decoder.remember_unknown_chunks = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + error = lodepng_decode(out, w, h, &state, in, insize); + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); +} + +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + size_t buffersize; + unsigned error; + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + error = lodepng_load_file(&buffer, &buffersize, filename); + if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); +} + +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) { + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->read_text_chunks = 1; + settings->remember_unknown_chunks = 0; + settings->max_text_size = 16777216; + settings->max_icc_size = 16777216; /* 16MB is much more than enough for any reasonable ICC profile */ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignore_crc = 0; + settings->ignore_critical = 0; + settings->ignore_end = 0; + lodepng_decompress_settings_init(&settings->zlibsettings); +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) + +void lodepng_state_init(LodePNGState* state) { +#ifdef LODEPNG_COMPILE_DECODER + lodepng_decoder_settings_init(&state->decoder); +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + lodepng_encoder_settings_init(&state->encoder); +#endif /*LODEPNG_COMPILE_ENCODER*/ + lodepng_color_mode_init(&state->info_raw); + lodepng_info_init(&state->info_png); + state->error = 1; + state->lodepng_convert = NULL; +} + +void lodepng_state_cleanup(LodePNGState* state) { + lodepng_color_mode_cleanup(&state->info_raw); + lodepng_info_cleanup(&state->info_png); +} + +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) { + lodepng_state_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->info_raw); + lodepng_info_init(&dest->info_png); + dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return; + dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return; +} + +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +static unsigned writeSignature(ucvector* out) { + size_t pos = out->size; + const unsigned char signature[] = {137, 80, 78, 71, 13, 10, 26, 10}; + /*8 bytes PNG signature, aka the magic bytes*/ + if(!ucvector_resize(out, out->size + 8)) return 83; /*alloc fail*/ + lodepng_memcpy(out->data + pos, signature, 8); + return 0; +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) { + unsigned char *chunk, *data; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 13, "IHDR")); + data = chunk + 8; + + lodepng_set32bitInt(data + 0, w); /*width*/ + lodepng_set32bitInt(data + 4, h); /*height*/ + data[8] = (unsigned char)bitdepth; /*bit depth*/ + data[9] = (unsigned char)colortype; /*color type*/ + data[10] = 0; /*compression method*/ + data[11] = 0; /*filter method*/ + data[12] = interlace_method; /*interlace method*/ + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +/* only adds the chunk if needed (there is a key or palette with alpha) */ +static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk; + size_t i, j = 8; + + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, info->palettesize * 3, "PLTE")); + + for(i = 0; i != info->palettesize; ++i) { + /*add all channels except alpha channel*/ + chunk[j++] = info->palette[i * 4 + 0]; + chunk[j++] = info->palette[i * 4 + 1]; + chunk[j++] = info->palette[i * 4 + 2]; + } + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk = 0; + + if(info->colortype == LCT_PALETTE) { + size_t i, amount = info->palettesize; + /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ + for(i = info->palettesize; i != 0; --i) { + if(info->palette[4 * (i - 1) + 3] != 255) break; + --amount; + } + if(amount) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, amount, "tRNS")); + /*add the alpha channel values from the palette*/ + for(i = 0; i != amount; ++i) chunk[8 + i] = info->palette[4 * i + 3]; + } + } else if(info->colortype == LCT_GREY) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + } + } else if(info->colortype == LCT_RGB) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + chunk[10] = (unsigned char)(info->key_g >> 8); + chunk[11] = (unsigned char)(info->key_g & 255); + chunk[12] = (unsigned char)(info->key_b >> 8); + chunk[13] = (unsigned char)(info->key_b & 255); + } + } + + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* zlib = 0; + size_t zlibsize = 0; + + error = zlib_compress(&zlib, &zlibsize, data, datasize, zlibsettings); + if(!error) { + error = lodepng_chunk_createv(out, zlibsize, "IDAT", zlib); + } + lodepng_free(zlib); + return error; +} + +static unsigned addChunk_IEND(ucvector* out) { + return lodepng_chunk_createv(out, 0, "IEND", 0); +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) { + unsigned char* chunk = 0; + size_t keysize = lodepng_strlen(keyword), textsize = lodepng_strlen(textstring); + size_t size = keysize + 1 + textsize; + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, size, "tEXt")); + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + lodepng_memcpy(chunk + 9 + keysize, textstring, textsize); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword); + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "zTXt"); + } + if(!error) { + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compress, const char* keyword, const char* langtag, + const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword), langsize = lodepng_strlen(langtag), transsize = lodepng_strlen(transkey); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + if(compress) { + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + } + if(!error) { + size_t size = keysize + 3 + langsize + 1 + transsize + 1 + (compress ? compressedsize : textsize); + error = lodepng_chunk_init(&chunk, out, size, "iTXt"); + } + if(!error) { + size_t pos = 8; + lodepng_memcpy(chunk + pos, keyword, keysize); + pos += keysize; + chunk[pos++] = 0; /*null termination char*/ + chunk[pos++] = (compress ? 1 : 0); /*compression flag*/ + chunk[pos++] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + pos, langtag, langsize); + pos += langsize; + chunk[pos++] = 0; /*null termination char*/ + lodepng_memcpy(chunk + pos, transkey, transsize); + pos += transsize; + chunk[pos++] = 0; /*null termination char*/ + if(compress) { + lodepng_memcpy(chunk + pos, compressed, compressedsize); + } else { + lodepng_memcpy(chunk + pos, textstring, textsize); + } + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk = 0; + if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + chunk[10] = (unsigned char)(info->background_g >> 8); + chunk[11] = (unsigned char)(info->background_g & 255); + chunk[12] = (unsigned char)(info->background_b >> 8); + chunk[13] = (unsigned char)(info->background_b & 255); + } else if(info->color.colortype == LCT_PALETTE) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "bKGD")); + chunk[8] = (unsigned char)(info->background_r & 255); /*palette index*/ + } + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 7, "tIME")); + chunk[8] = (unsigned char)(time->year >> 8); + chunk[9] = (unsigned char)(time->year & 255); + chunk[10] = (unsigned char)time->month; + chunk[11] = (unsigned char)time->day; + chunk[12] = (unsigned char)time->hour; + chunk[13] = (unsigned char)time->minute; + chunk[14] = (unsigned char)time->second; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 9, "pHYs")); + lodepng_set32bitInt(chunk + 8, info->phys_x); + lodepng_set32bitInt(chunk + 12, info->phys_y); + chunk[16] = info->phys_unit; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "gAMA")); + lodepng_set32bitInt(chunk + 8, info->gama_gamma); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 32, "cHRM")); + lodepng_set32bitInt(chunk + 8, info->chrm_white_x); + lodepng_set32bitInt(chunk + 12, info->chrm_white_y); + lodepng_set32bitInt(chunk + 16, info->chrm_red_x); + lodepng_set32bitInt(chunk + 20, info->chrm_red_y); + lodepng_set32bitInt(chunk + 24, info->chrm_green_x); + lodepng_set32bitInt(chunk + 28, info->chrm_green_y); + lodepng_set32bitInt(chunk + 32, info->chrm_blue_x); + lodepng_set32bitInt(chunk + 36, info->chrm_blue_y); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_sRGB(ucvector* out, const LodePNGInfo* info) { + unsigned char data = info->srgb_intent; + return lodepng_chunk_createv(out, 1, "sRGB", &data); +} + +static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t keysize = lodepng_strlen(info->iccp_name); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + error = zlib_compress(&compressed, &compressedsize, + info->iccp_profile, info->iccp_profile_size, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "iCCP"); + } + if(!error) { + lodepng_memcpy(chunk + 8, info->iccp_name, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, + size_t length, size_t bytewidth, unsigned char filterType) { + size_t i; + switch(filterType) { + case 0: /*None*/ + for(i = 0; i != length; ++i) out[i] = scanline[i]; + break; + case 1: /*Sub*/ + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; + break; + case 2: /*Up*/ + if(prevline) { + for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; + } else { + for(i = 0; i != length; ++i) out[i] = scanline[i]; + } + break; + case 3: /*Average*/ + if(prevline) { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1); + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1); + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1); + } + break; + case 4: /*Paeth*/ + if(prevline) { + /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ + for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); + for(i = bytewidth; i < length; ++i) { + out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ + for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); + } + break; + default: return; /*invalid filter type given*/ + } +} + +/* integer binary logarithm, max return value is 31 */ +static size_t ilog2(size_t i) { + size_t result = 0; + if(i >= 65536) { result += 16; i >>= 16; } + if(i >= 256) { result += 8; i >>= 8; } + if(i >= 16) { result += 4; i >>= 4; } + if(i >= 4) { result += 2; i >>= 2; } + if(i >= 2) { result += 1; /*i >>= 1;*/ } + return result; +} + +/* integer approximation for i * log2(i), helper function for LFS_ENTROPY */ +static size_t ilog2i(size_t i) { + size_t l; + if(i == 0) return 0; + l = ilog2(i); + /* approximate i*log2(i): l is integer logarithm, ((i - (1u << l)) << 1u) + linearly approximates the missing fractional part multiplied by i */ + return i * l + ((i - (1u << l)) << 1u); +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* color, const LodePNGEncoderSettings* settings) { + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7u) / 8u, because there are + the scanlines with 1 extra byte per scanline + */ + + unsigned bpp = lodepng_get_bpp(color); + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + const unsigned char* prevline = 0; + unsigned x, y; + unsigned error = 0; + LodePNGFilterStrategy strategy = settings->filter_strategy; + + /* + There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. + use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is + not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply + all five filters and select the filter that produces the smallest sum of absolute values per row. + This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. + + If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, + but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum + heuristic is used. + */ + if(settings->filter_palette_zero && + (color->colortype == LCT_PALETTE || color->bitdepth < 8)) strategy = LFS_ZERO; + + if(bpp == 0) return 31; /*error: invalid color type*/ + + if(strategy >= LFS_ZERO && strategy <= LFS_FOUR) { + unsigned char type = (unsigned char)strategy; + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_MINSUM) { + /*adaptive filtering*/ + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned char type, bestType = 0; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + if(type == 0) { + for(x = 0; x != linebytes; ++x) sum += (unsigned char)(attempt[type][x]); + } else { + for(x = 0; x != linebytes; ++x) { + /*For differences, each byte should be treated as signed, values above 127 are negative + (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. + This means filtertype 0 is almost never chosen, but that is justified.*/ + unsigned char s = attempt[type][x]; + sum += s < 128 ? s : (255U - s); + } + } + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum < smallest) { + bestType = type; + smallest = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_ENTROPY) { + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t bestSum = 0; + unsigned type, bestType = 0; + unsigned count[256]; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + lodepng_memset(count, 0, 256 * sizeof(*count)); + for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]]; + ++count[type]; /*the filter type itself is part of the scanline*/ + for(x = 0; x != 256; ++x) { + sum += ilog2i(count[x]); + } + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum > bestSum) { + bestType = type; + bestSum = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_PREDEFINED) { + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + unsigned char type = settings->predefined_filters[y]; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_BRUTE_FORCE) { + /*brute force filter chooser. + deflate the scanline after every filter attempt to see which one deflates best. + This is very slow and gives only slightly smaller, sometimes even larger, result*/ + size_t size[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodePNGCompressSettings zlibsettings; + lodepng_memcpy(&zlibsettings, &settings->zlibsettings, sizeof(LodePNGCompressSettings)); + /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, + to simulate the true case where the tree is the same for the whole image. Sometimes it gives + better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare + cases better compression. It does make this a bit less slow, so it's worth doing this.*/ + zlibsettings.btype = 1; + /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG + images only, so disable it*/ + zlibsettings.custom_zlib = 0; + zlibsettings.custom_deflate = 0; + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + if(!error) { + for(y = 0; y != h; ++y) /*try the 5 filter types*/ { + for(type = 0; type != 5; ++type) { + unsigned testsize = (unsigned)linebytes; + /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ + + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; + dummy = 0; + zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings); + lodepng_free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) { + bestType = type; + smallest = size[type]; + } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else return 88; /* unknown filter strategy */ + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y != h; ++y) { + size_t x; + for(x = 0; x < ilinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid + "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0); + } +} + +/* +in: non-interlaced image with size w*h +out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with + no padding bits between scanlines, but between reduced images so that each + reduced image starts at a byte. +bpp: bits per pixel +there are no padding bits, not between scanlines, not between reduced images +in has the following size in bits: w * h * bpp. +out is possibly bigger due to padding bits between reduced images +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. +return value is error**/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, + unsigned w, unsigned h, + const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) { + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= possible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + unsigned error = 0; + + if(info_png->interlace_method == 0) { + *outsize = h + (h * ((w * bpp + 7u) / 8u)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ + + if(!error) { + /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7u) / 8u)); + if(!padded) error = 83; /*alloc fail*/ + if(!error) { + addPaddingBits(padded, in, ((w * bpp + 7u) / 8u) * 8u, w * bpp, h); + error = filter(*out, padded, w, h, &info_png->color, settings); + } + lodepng_free(padded); + } else { + /*we can immediately filter into the out buffer, no other steps needed*/ + error = filter(*out, in, w, h, &info_png->color, settings); + } + } + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned char* adam7; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out)) error = 83; /*alloc fail*/ + + adam7 = (unsigned char*)lodepng_malloc(passstart[7]); + if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ + + if(!error) { + unsigned i; + + Adam7_interlace(adam7, in, w, h, bpp); + for(i = 0; i != 7; ++i) { + if(bpp < 8) { + unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); + if(!padded) ERROR_BREAK(83); /*alloc fail*/ + addPaddingBits(padded, &adam7[passstart[i]], + ((passw[i] * bpp + 7u) / 8u) * 8u, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], padded, + passw[i], passh[i], &info_png->color, settings); + lodepng_free(padded); + } else { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], + passw[i], passh[i], &info_png->color, settings); + } + + if(error) break; + } + } + + lodepng_free(adam7); + } + + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) { + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) { + CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = lodepng_chunk_next(inchunk, data + datasize); + } + return 0; +} + +static unsigned isGrayICCProfile(const unsigned char* profile, unsigned size) { + /* + It is a gray profile if bytes 16-19 are "GRAY", rgb profile if bytes 16-19 + are "RGB ". We do not perform any full parsing of the ICC profile here, other + than check those 4 bytes to grayscale profile. Other than that, validity of + the profile is not checked. This is needed only because the PNG specification + requires using a non-gray color model if there is an ICC profile with "RGB " + (sadly limiting compression opportunities if the input data is grayscale RGB + data), and requires using a gray color model if it is "GRAY". + */ + if(size < 20) return 0; + return profile[16] == 'G' && profile[17] == 'R' && profile[18] == 'A' && profile[19] == 'Y'; +} + +static unsigned isRGBICCProfile(const unsigned char* profile, unsigned size) { + /* See comment in isGrayICCProfile*/ + if(size < 20) return 0; + return profile[16] == 'R' && profile[17] == 'G' && profile[18] == 'B' && profile[19] == ' '; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state) { + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + ucvector outv = ucvector_init(NULL, 0); + LodePNGInfo info; + const LodePNGInfo* info_png = &state->info_png; + + lodepng_info_init(&info); + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + state->error = 0; + + /*check input values validity*/ + if((info_png->color.colortype == LCT_PALETTE || state->encoder.force_palette) + && (info_png->color.palettesize == 0 || info_png->color.palettesize > 256)) { + state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/ + goto cleanup; + } + if(state->encoder.zlibsettings.btype > 2) { + state->error = 61; /*error: invalid btype*/ + goto cleanup; + } + if(info_png->interlace_method > 1) { + state->error = 71; /*error: invalid interlace mode*/ + goto cleanup; + } + state->error = checkColorValidity(info_png->color.colortype, info_png->color.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + + /* color convert and compute scanline filter types */ + lodepng_info_copy(&info, &state->info_png); + if(state->encoder.auto_convert) { + LodePNGColorStats stats; + lodepng_color_stats_init(&stats); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined && + isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use palette with a GRAY ICC profile, even + if the palette has only gray colors, so disallow it.*/ + stats.allow_palette = 0; + } + if(info_png->iccp_defined && + isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use grayscale color with RGB ICC profile, so disallow gray.*/ + stats.allow_greyscale = 0; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = lodepng_compute_color_stats(&stats, image, w, h, &state->info_raw); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->background_defined) { + /*the background chunk's color must be taken into account as well*/ + unsigned r = 0, g = 0, b = 0; + LodePNGColorMode mode16 = lodepng_color_mode_make(LCT_RGB, 16); + lodepng_convert_rgb(&r, &g, &b, info_png->background_r, info_png->background_g, info_png->background_b, &mode16, &info_png->color); + state->error = lodepng_color_stats_add(&stats, r, g, b, 65535); + if(state->error) goto cleanup; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = auto_choose_color(&info.color, &state->info_raw, &stats); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*also convert the background chunk*/ + if(info_png->background_defined) { + if(lodepng_convert_rgb(&info.background_r, &info.background_g, &info.background_b, + info_png->background_r, info_png->background_g, info_png->background_b, &info.color, &info_png->color)) { + state->error = 104; + goto cleanup; + } + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined) { + unsigned gray_icc = isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned rgb_icc = isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned gray_png = info.color.colortype == LCT_GREY || info.color.colortype == LCT_GREY_ALPHA; + if(!gray_icc && !rgb_icc) { + state->error = 100; /* Disallowed profile color type for PNG */ + goto cleanup; + } + if(gray_icc != gray_png) { + /*Not allowed to use RGB/RGBA/palette with GRAY ICC profile or vice versa, + or in case of auto_convert, it wasn't possible to find appropriate model*/ + state->error = state->encoder.auto_convert ? 102 : 101; + goto cleanup; + } + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) { + unsigned char* converted; + size_t size = ((size_t)w * (size_t)h * (size_t)lodepng_get_bpp(&info.color) + 7u) / 8u; + + converted = (unsigned char*)lodepng_malloc(size); + if(!converted && size) state->error = 83; /*alloc fail*/ + if(!state->error) { + if (state->lodepng_convert && state->info_raw.colortype == LCT_CUSTOM) { + state->error = state->lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); + } else { + state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); + } + } + if(!state->error) { + state->error = preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); + } + lodepng_free(converted); + if(state->error) goto cleanup; + } else { + state->error = preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); + if(state->error) goto cleanup; + } + + /* output all PNG chunks */ { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + state->error = writeSignature(&outv); + if(state->error) goto cleanup; + /*IHDR*/ + state->error = addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks_data[0]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); + if(state->error) goto cleanup; + } + /*color profile chunks must come before PLTE */ + if(info.iccp_defined) { + state->error = addChunk_iCCP(&outv, &info, &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + if(info.srgb_defined) { + state->error = addChunk_sRGB(&outv, &info); + if(state->error) goto cleanup; + } + if(info.gama_defined) { + state->error = addChunk_gAMA(&outv, &info); + if(state->error) goto cleanup; + } + if(info.chrm_defined) { + state->error = addChunk_cHRM(&outv, &info); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*PLTE*/ + if(info.color.colortype == LCT_PALETTE) { + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) { + /*force_palette means: write suggested palette for truecolor in PLTE chunk*/ + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + /*tRNS (this will only add if when necessary) */ + state->error = addChunk_tRNS(&outv, &info.color); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) { + state->error = addChunk_bKGD(&outv, &info); + if(state->error) goto cleanup; + } + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) { + state->error = addChunk_pHYs(&outv, &info); + if(state->error) goto cleanup; + } + + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks_data[1]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) { + state->error = addChunk_tIME(&outv, &info.time); + if(state->error) goto cleanup; + } + /*tEXt and/or zTXt*/ + for(i = 0; i != info.text_num; ++i) { + if(lodepng_strlen(info.text_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.text_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + if(state->encoder.text_compression) { + state->error = addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } else { + state->error = addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); + if(state->error) goto cleanup; + } + } + /*LodePNG version id in text chunk*/ + if(state->encoder.add_id) { + unsigned already_added_id_text = 0; + for(i = 0; i != info.text_num; ++i) { + const char* k = info.text_keys[i]; + /* Could use strcmp, but we're not calling or reimplementing this C library function for this use only */ + if(k[0] == 'L' && k[1] == 'o' && k[2] == 'd' && k[3] == 'e' && + k[4] == 'P' && k[5] == 'N' && k[6] == 'G' && k[7] == '\0') { + already_added_id_text = 1; + break; + } + } + if(already_added_id_text == 0) { + state->error = addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + if(state->error) goto cleanup; + } + } + /*iTXt*/ + for(i = 0; i != info.itext_num; ++i) { + if(lodepng_strlen(info.itext_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.itext_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + state->error = addChunk_iTXt( + &outv, state->encoder.text_compression, + info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], + &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks_data[2]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + state->error = addChunk_IEND(&outv); + if(state->error) goto cleanup; + } + +cleanup: + lodepng_info_cleanup(&info); + lodepng_free(data); + + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; + + return state->error; +} + +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, + unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + state.info_png.color.colortype = colortype; + state.info_png.color.bitdepth = bitdepth; + lodepng_encode(out, outsize, image, w, h, &state); + error = state.error; + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); + if(!error) error = lodepng_save_file(buffer, buffersize, filename); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) { + lodepng_compress_settings_init(&settings->zlibsettings); + settings->filter_palette_zero = 1; + settings->filter_strategy = LFS_MINSUM; + settings->auto_convert = 1; + settings->force_palette = 0; + settings->predefined_filters = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 0; + settings->text_compression = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/* +This returns the description of a numerical error code in English. This is also +the documentation of all the error codes. +*/ +const char* lodepng_error_text(unsigned code) { + switch(code) { + case 0: return "no error, everything went ok"; + case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ + case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ + case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ + case 13: return "problem while processing dynamic deflate block"; + case 14: return "problem while processing dynamic deflate block"; + case 15: return "problem while processing dynamic deflate block"; + /*this error could happen if there are only 0 or 1 symbols present in the huffman code:*/ + case 16: return "invalid code while processing dynamic deflate block"; + case 17: return "end of out buffer memory reached while inflating"; + case 18: return "invalid distance code while inflating"; + case 19: return "end of out buffer memory reached while inflating"; + case 20: return "invalid deflate block BTYPE encountered while decoding"; + case 21: return "NLEN is not ones complement of LEN in a deflate block"; + + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ + case 22: return "end of out buffer memory reached while inflating"; + case 23: return "end of in buffer memory reached while inflating"; + case 24: return "invalid FCHECK in zlib header"; + case 25: return "invalid compression method in zlib header"; + case 26: return "FDICT encountered in zlib header while it's not used for PNG"; + case 27: return "PNG file is smaller than a PNG header"; + /*Checks the magic file header, the first 8 bytes of the PNG file*/ + case 28: return "incorrect PNG signature, it's no PNG or corrupted"; + case 29: return "first chunk is not the header chunk"; + case 30: return "chunk length too large, chunk broken off at end of file"; + case 31: return "illegal PNG color type or bpp"; + case 32: return "illegal PNG compression method"; + case 33: return "illegal PNG filter method"; + case 34: return "illegal PNG interlace method"; + case 35: return "chunk length of a chunk is too large or the chunk too small"; + case 36: return "illegal PNG filter type encountered"; + case 37: return "illegal bit depth for this color type given"; + case 38: return "the palette is too small or too big"; /*0, or more than 256 colors*/ + case 39: return "tRNS chunk before PLTE or has more entries than palette size"; + case 40: return "tRNS chunk has wrong size for grayscale image"; + case 41: return "tRNS chunk has wrong size for RGB image"; + case 42: return "tRNS chunk appeared while it was not allowed for this color type"; + case 43: return "bKGD chunk has wrong size for palette image"; + case 44: return "bKGD chunk has wrong size for grayscale image"; + case 45: return "bKGD chunk has wrong size for RGB image"; + case 48: return "empty input buffer given to decoder. Maybe caused by non-existing file?"; + case 49: return "jumped past memory while generating dynamic huffman tree"; + case 50: return "jumped past memory while generating dynamic huffman tree"; + case 51: return "jumped past memory while inflating huffman block"; + case 52: return "jumped past memory while inflating"; + case 53: return "size of zlib data too small"; + case 54: return "repeat symbol in tree while there was no value symbol yet"; + /*jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lengths. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ + case 55: return "jumped past tree while generating huffman tree"; + case 56: return "given output image colortype or bitdepth not supported for color conversion"; + case 57: return "invalid CRC encountered (checking CRC can be disabled)"; + case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; + case 59: return "requested color conversion not supported"; + case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; + case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; + /*LodePNG leaves the choice of RGB to grayscale conversion formula to the user.*/ + case 62: return "conversion from color to grayscale not supported"; + /*(2^31-1)*/ + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; + /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ + case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; + case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; + case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; + case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; + case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; + case 71: return "invalid interlace mode given to encoder (must be 0 or 1)"; + case 72: return "while decoding, invalid compression method encountering in zTXt or iTXt chunk (it must be 0)"; + case 73: return "invalid tIME chunk size"; + case 74: return "invalid pHYs chunk size"; + /*length could be wrong, or data chopped off*/ + case 75: return "no null termination char found while decoding text chunk"; + case 76: return "iTXt chunk too short to contain required bytes"; + case 77: return "integer overflow in buffer size"; + case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ + case 79: return "failed to open file for writing"; + case 80: return "tried creating a tree of 0 symbols"; + case 81: return "lazy matching at pos 0 is impossible"; + case 82: return "color conversion to palette requested while a color isn't in palette, or index out of bounds"; + case 83: return "memory allocation failed"; + case 84: return "given image too small to contain all pixels to be encoded"; + case 86: return "impossible offset in lz77 encoding (internal bug)"; + case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; + case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; + case 89: return "text chunk keyword too short or long: must have size 1-79"; + /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/ + case 90: return "windowsize must be a power of two"; + case 91: return "invalid decompressed idat size"; + case 92: return "integer overflow due to too many pixels"; + case 93: return "zero width or height is invalid"; + case 94: return "header chunk must have a size of 13 bytes"; + case 95: return "integer overflow with combined idat chunk size"; + case 96: return "invalid gAMA chunk size"; + case 97: return "invalid cHRM chunk size"; + case 98: return "invalid sRGB chunk size"; + case 99: return "invalid sRGB rendering intent"; + case 100: return "invalid ICC profile color type, the PNG specification only allows RGB or GRAY"; + case 101: return "PNG specification does not allow RGB ICC profile on gray color types and vice versa"; + case 102: return "not allowed to set grayscale ICC profile with colored pixels by PNG specification"; + case 103: return "invalid palette index in bKGD chunk. Maybe it came before PLTE chunk?"; + case 104: return "invalid bKGD color while encoding (e.g. palette index out of range)"; + case 105: return "integer overflow of bitsize"; + case 106: return "PNG file must have PLTE chunk if color type is palette"; + case 107: return "color convert from palette mode requested without setting the palette data in it"; + case 108: return "tried to add more than 256 values to a palette"; + /*this limit can be configured in LodePNGDecompressSettings*/ + case 109: return "tried to decompress zlib or deflate data larger than desired max_output_size"; + case 110: return "custom zlib or inflate decompression failed"; + case 111: return "custom zlib or deflate compression failed"; + /*max text size limit can be configured in LodePNGDecoderSettings. This error prevents + unreasonable memory consumption when decoding due to impossibly large text sizes.*/ + case 112: return "compressed text unreasonably large"; + /*max ICC size limit can be configured in LodePNGDecoderSettings. This error prevents + unreasonable memory consumption when decoding due to impossibly large ICC profile*/ + case 113: return "ICC profile unreasonably large"; + } + return "unknown error code"; +} +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // C++ Wrapper // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { + +#ifdef LODEPNG_COMPILE_DISK +unsigned load_file(std::vector& buffer, const std::string& filename) { + long size = lodepng_filesize(filename.c_str()); + if(size < 0) return 78; + buffer.resize((size_t)size); + return size == 0 ? 0 : lodepng_buffer_file(&buffer[0], (size_t)size, filename.c_str()); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned save_file(const std::vector& buffer, const std::string& filename) { + return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str()); +} +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +unsigned decompress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_decompress(&buffer, &buffersize, 0, in, insize, &settings); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decompress(std::vector& out, const std::vector& in, + const LodePNGDecompressSettings& settings) { + return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned compress(std::vector& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned compress(std::vector& out, const std::vector& in, + const LodePNGCompressSettings& settings) { + return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ + + +#ifdef LODEPNG_COMPILE_PNG + +State::State() { + lodepng_state_init(this); +} + +State::State(const State& other) { + lodepng_state_init(this); + lodepng_state_copy(this, &other); +} + +State::~State() { + lodepng_state_cleanup(this); +} + +State& State::operator=(const State& other) { + lodepng_state_copy(this, &other); + return *this; +} + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); + if(buffer && !error) { + State state; + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + const std::vector& in, LodePNGColorType colortype, unsigned bitdepth) { + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize) { + unsigned char* buffer = NULL; + unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); + if(buffer && !error) { + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector& out, unsigned& w, unsigned& h, + State& state, + const std::vector& in) { + return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned decode(std::vector& out, unsigned& w, unsigned& h, const std::string& filename, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector buffer; + /* safe output values in case error happens */ + w = h = 0; + unsigned error = load_file(buffer, filename); + if(error) return error; + return decode(out, w, h, buffer, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DECODER */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned encode(std::vector& out, const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} + +unsigned encode(std::vector& out, + const unsigned char* in, unsigned w, unsigned h, + State& state) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector& out, + const std::vector& in, unsigned w, unsigned h, + State& state) { + if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, state); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector buffer; + unsigned error = encode(buffer, in, w, h, colortype, bitdepth); + if(!error) error = save_file(buffer, filename); + return error; +} + +unsigned encode(const std::string& filename, + const std::vector& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_PNG */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ +#endif // IMLIB_ENABLE_PNG_ENCODER || IMLIB_ENABLE_PNG_DECODER diff --git a/github_source/minicv2/src/lsd.c b/github_source/minicv2/src/lsd.c new file mode 100644 index 0000000..cd18e1c --- /dev/null +++ b/github_source/minicv2/src/lsd.c @@ -0,0 +1,2799 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Line Segment Detector. + */ +#include +#include +#include "imlib.h" + +#ifdef IMLIB_ENABLE_FIND_LINE_SEGMENTS +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#pragma GCC diagnostic ignored "-Wunused-variable" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +#define PI 3.1415926 +#define error(msg) fb_alloc_fail() +#define free(ptr) ({ umm_free(ptr); }) +#define malloc(size) ({ void *_r = umm_malloc(size); if(!_r) fb_alloc_fail(); _r; }) +#define realloc(ptr, size) ({ void *_r = umm_realloc((ptr), (size)); if(!_r) fb_alloc_fail(); _r; }) +#define calloc(num, item_size) ({ void *_r = umm_calloc((num), (item_size)); if(!_r) fb_alloc_fail(); _r; }) +#define sqrt(x) fast_sqrtf(x) +#define floor(x) fast_floorf(x) +#define ceil(x) fast_ceilf(x) +#define round(x) fast_roundf(x) +#define atan(x) fast_atanf(x) +#define atan2(y, x) fast_atan2f((y), (x)) +#define exp(x) fast_expf(x) +#define fabs(x) fast_fabsf(x) +#define log(x) fast_log(x) +#define log10(x) log10f(x) +#define cos(x) cosf(x) +#define sin(x) sinf(x) +#define pow(x,y) powf((x),(y)) +#define sinh(x) sinhf(x) +#define radToDeg(x) ((x) * (180.0f / PI)) +#define degToRad(x) ((x) * (PI / 180.0f)) + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "lsd.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*---------------------------------------------------------------------------- + + LSD - Line Segment Detector on digital images + + This code is part of the following publication and was subject + to peer review: + + "LSD: a Line Segment Detector" by Rafael Grompone von Gioi, + Jeremie Jakubowicz, Jean-Michel Morel, and Gregory Randall, + Image Processing On Line, 2012. DOI:10.5201/ipol.2012.gjmr-lsd + http://dx.doi.org/10.5201/ipol.2012.gjmr-lsd + + Copyright (c) 2007-2011 rafael grompone von gioi + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + ----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** @file lsd.h + LSD module header + @author rafael grompone von gioi + */ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** LSD Full Interface + + @param n_out Pointer to an int where LSD will store the number of + line segments detected. + + @param img Pointer to input image data. It must be an array of + unsigned chars of size X x Y, and the pixel at coordinates + (x,y) is obtained by img[x+y*X]. + + @param X X size of the image: the number of columns. + + @param Y Y size of the image: the number of rows. + + @param scale When different from 1.0, LSD will scale the input image + by 'scale' factor by Gaussian filtering, before detecting + line segments. + Example: if scale=0.8, the input image will be subsampled + to 80% of its size, before the line segment detector + is applied. + Suggested value: 0.8 + + @param sigma_scale When scale!=1.0, the sigma of the Gaussian filter is: + sigma = sigma_scale / scale, if scale < 1.0 + sigma = sigma_scale, if scale >= 1.0 + Suggested value: 0.6 + + @param quant Bound to the quantization error on the gradient norm. + Example: if gray levels are quantized to integer steps, + the gradient (computed by finite differences) error + due to quantization will be bounded by 2.0, as the + worst case is when the error are 1 and -1, that + gives an error of 2.0. + Suggested value: 2.0 + + @param ang_th Gradient angle tolerance in the region growing + algorithm, in degrees. + Suggested value: 22.5 + + @param log_eps Detection threshold, accept if -log10(NFA) > log_eps. + The larger the value, the more strict the detector is, + and will result in less detections. + (Note that the 'minus sign' makes that this + behavior is opposite to the one of NFA.) + The value -log10(NFA) is equivalent but more + intuitive than NFA: + - -1.0 gives an average of 10 false detections on noise + - 0.0 gives an average of 1 false detections on noise + - 1.0 gives an average of 0.1 false detections on nose + - 2.0 gives an average of 0.01 false detections on noise + . + Suggested value: 0.0 + + @param density_th Minimal proportion of 'supporting' points in a rectangle. + Suggested value: 0.7 + + @param n_bins Number of bins used in the pseudo-ordering of gradient + modulus. + Suggested value: 1024 + + @param reg_img Optional output: if desired, LSD will return an + int image where each pixel indicates the line segment + to which it belongs. Unused pixels have the value '0', + while the used ones have the number of the line segment, + numbered 1,2,3,..., in the same order as in the + output list. If desired, a non NULL int** pointer must + be assigned, and LSD will make that the pointer point + to an int array of size reg_x x reg_y, where the pixel + value at (x,y) is obtained with (*reg_img)[x+y*reg_x]. + Note that the resulting image has the size of the image + used for the processing, that is, the size of the input + image scaled by the given factor 'scale'. If scale!=1 + this size differs from XxY and that is the reason why + its value is given by reg_x and reg_y. + Suggested value: NULL + + @param reg_x Pointer to an int where LSD will put the X size + 'reg_img' image, when asked for. + Suggested value: NULL + + @param reg_y Pointer to an int where LSD will put the Y size + 'reg_img' image, when asked for. + Suggested value: NULL + + @return A float array of size 7 x n_out, containing the list + of line segments detected. The array contains first + 7 values of line segment number 1, then the 7 values + of line segment number 2, and so on, and it finish + by the 7 values of line segment number n_out. + The seven values are: + - x1,y1,x2,y2,width,p,-log10(NFA) + . + for a line segment from coordinates (x1,y1) to (x2,y2), + a width 'width', an angle precision of p in (0,1) given + by angle_tolerance/180 degree, and NFA value 'NFA'. + If 'out' is the returned pointer, the 7 values of + line segment number 'n+1' are obtained with + 'out[7*n+0]' to 'out[7*n+6]'. + */ +float * LineSegmentDetection( int * n_out, + unsigned char * img, int X, int Y, + float scale, float sigma_scale, float quant, + float ang_th, float log_eps, float density_th, + int n_bins, + int ** reg_img, int * reg_x, int * reg_y ); + +/*----------------------------------------------------------------------------*/ +/** LSD Simple Interface with Scale and Region output. + + @param n_out Pointer to an int where LSD will store the number of + line segments detected. + + @param img Pointer to input image data. It must be an array of + unsigned chars of size X x Y, and the pixel at coordinates + (x,y) is obtained by img[x+y*X]. + + @param X X size of the image: the number of columns. + + @param Y Y size of the image: the number of rows. + + @param scale When different from 1.0, LSD will scale the input image + by 'scale' factor by Gaussian filtering, before detecting + line segments. + Example: if scale=0.8, the input image will be subsampled + to 80% of its size, before the line segment detector + is applied. + Suggested value: 0.8 + + @param reg_img Optional output: if desired, LSD will return an + int image where each pixel indicates the line segment + to which it belongs. Unused pixels have the value '0', + while the used ones have the number of the line segment, + numbered 1,2,3,..., in the same order as in the + output list. If desired, a non NULL int** pointer must + be assigned, and LSD will make that the pointer point + to an int array of size reg_x x reg_y, where the pixel + value at (x,y) is obtained with (*reg_img)[x+y*reg_x]. + Note that the resulting image has the size of the image + used for the processing, that is, the size of the input + image scaled by the given factor 'scale'. If scale!=1 + this size differs from XxY and that is the reason why + its value is given by reg_x and reg_y. + Suggested value: NULL + + @param reg_x Pointer to an int where LSD will put the X size + 'reg_img' image, when asked for. + Suggested value: NULL + + @param reg_y Pointer to an int where LSD will put the Y size + 'reg_img' image, when asked for. + Suggested value: NULL + + @return A float array of size 7 x n_out, containing the list + of line segments detected. The array contains first + 7 values of line segment number 1, then the 7 values + of line segment number 2, and so on, and it finish + by the 7 values of line segment number n_out. + The seven values are: + - x1,y1,x2,y2,width,p,-log10(NFA) + . + for a line segment from coordinates (x1,y1) to (x2,y2), + a width 'width', an angle precision of p in (0,1) given + by angle_tolerance/180 degree, and NFA value 'NFA'. + If 'out' is the returned pointer, the 7 values of + line segment number 'n+1' are obtained with + 'out[7*n+0]' to 'out[7*n+6]'. + */ +float * lsd_scale_region( int * n_out, + unsigned char * img, int X, int Y, float scale, + int ** reg_img, int * reg_x, int * reg_y ); + +/*----------------------------------------------------------------------------*/ +/** LSD Simple Interface with Scale + + @param n_out Pointer to an int where LSD will store the number of + line segments detected. + + @param img Pointer to input image data. It must be an array of + unsigned chars of size X x Y, and the pixel at coordinates + (x,y) is obtained by img[x+y*X]. + + @param X X size of the image: the number of columns. + + @param Y Y size of the image: the number of rows. + + @param scale When different from 1.0, LSD will scale the input image + by 'scale' factor by Gaussian filtering, before detecting + line segments. + Example: if scale=0.8, the input image will be subsampled + to 80% of its size, before the line segment detector + is applied. + Suggested value: 0.8 + + @return A float array of size 7 x n_out, containing the list + of line segments detected. The array contains first + 7 values of line segment number 1, then the 7 values + of line segment number 2, and so on, and it finish + by the 7 values of line segment number n_out. + The seven values are: + - x1,y1,x2,y2,width,p,-log10(NFA) + . + for a line segment from coordinates (x1,y1) to (x2,y2), + a width 'width', an angle precision of p in (0,1) given + by angle_tolerance/180 degree, and NFA value 'NFA'. + If 'out' is the returned pointer, the 7 values of + line segment number 'n+1' are obtained with + 'out[7*n+0]' to 'out[7*n+6]'. + */ +float * lsd_scale(int * n_out, unsigned char * img, int X, int Y, float scale); + +/*----------------------------------------------------------------------------*/ +/** LSD Simple Interface + + @param n_out Pointer to an int where LSD will store the number of + line segments detected. + + @param img Pointer to input image data. It must be an array of + unsigned chars of size X x Y, and the pixel at coordinates + (x,y) is obtained by img[x+y*X]. + + @param X X size of the image: the number of columns. + + @param Y Y size of the image: the number of rows. + + @return A float array of size 7 x n_out, containing the list + of line segments detected. The array contains first + 7 values of line segment number 1, then the 7 values + of line segment number 2, and so on, and it finish + by the 7 values of line segment number n_out. + The seven values are: + - x1,y1,x2,y2,width,p,-log10(NFA) + . + for a line segment from coordinates (x1,y1) to (x2,y2), + a width 'width', an angle precision of p in (0,1) given + by angle_tolerance/180 degree, and NFA value 'NFA'. + If 'out' is the returned pointer, the 7 values of + line segment number 'n+1' are obtained with + 'out[7*n+0]' to 'out[7*n+6]'. + */ +float * lsd(int * n_out, unsigned char * img, int X, int Y); + +/*----------------------------------------------------------------------------*/ + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "lsd.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*---------------------------------------------------------------------------- + + LSD - Line Segment Detector on digital images + + This code is part of the following publication and was subject + to peer review: + + "LSD: a Line Segment Detector" by Rafael Grompone von Gioi, + Jeremie Jakubowicz, Jean-Michel Morel, and Gregory Randall, + Image Processing On Line, 2012. DOI:10.5201/ipol.2012.gjmr-lsd + http://dx.doi.org/10.5201/ipol.2012.gjmr-lsd + + Copyright (c) 2007-2011 rafael grompone von gioi + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + ----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** @file lsd.c + LSD module code + @author rafael grompone von gioi + */ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** @mainpage LSD code documentation + + This is an implementation of the Line Segment Detector described + in the paper: + + "LSD: A Fast Line Segment Detector with a False Detection Control" + by Rafael Grompone von Gioi, Jeremie Jakubowicz, Jean-Michel Morel, + and Gregory Randall, IEEE Transactions on Pattern Analysis and + Machine Intelligence, vol. 32, no. 4, pp. 722-732, April, 2010. + + and in more details in the CMLA Technical Report: + + "LSD: A Line Segment Detector, Technical Report", + by Rafael Grompone von Gioi, Jeremie Jakubowicz, Jean-Michel Morel, + Gregory Randall, CMLA, ENS Cachan, 2010. + + The version implemented here includes some further improvements + described in the following publication, of which this code is part: + + "LSD: a Line Segment Detector" by Rafael Grompone von Gioi, + Jeremie Jakubowicz, Jean-Michel Morel, and Gregory Randall, + Image Processing On Line, 2012. DOI:10.5201/ipol.2012.gjmr-lsd + http://dx.doi.org/10.5201/ipol.2012.gjmr-lsd + + The module's main function is lsd(). + + The source code is contained in two files: lsd.h and lsd.c. + + HISTORY: + - version 1.6 - nov 2011: + - changes in the interface, + - max_grad parameter removed, + - the factor 11 was added to the number of test + to consider the different precision values + tested, + - a minor bug corrected in the gradient sorting + code, + - the algorithm now also returns p and log_nfa + for each detection, + - a minor bug was corrected in the image scaling, + - the angle comparison in "isaligned" changed + from < to <=, + - "eps" variable renamed "log_eps", + - "lsd_scale_region" interface was added, + - minor changes to comments. + - version 1.5 - dec 2010: Changes in 'refine', -W option added, + and more comments added. + - version 1.4 - jul 2010: lsd_scale interface added and doxygen doc. + - version 1.3 - feb 2010: Multiple bug correction and improved code. + - version 1.2 - dec 2009: First full Ansi C Language version. + - version 1.1 - sep 2009: Systematic subsampling to scale 0.8 and + correction to partially handle "angle problem". + - version 1.0 - jan 2009: First complete Megawave2 and Ansi C Language + version. + + @author rafael grompone von gioi + */ +/*----------------------------------------------------------------------------*/ + +/** ln(10) */ +#ifndef M_LN10 +#define M_LN10 2.30258509299404568402f +#endif /* !M_LN10 */ + +/** PI */ +#ifndef M_PI +#define M_PI 3.14159265358979323846f +#endif /* !M_PI */ + +#ifndef FALSE +#define FALSE 0 +#endif /* !FALSE */ + +#ifndef TRUE +#define TRUE 1 +#endif /* !TRUE */ + +/** Label for pixels with undefined gradient. */ +#define NOTDEF -512.0f // -1024.0f +#define NOTDEF_INT -29335 + +/** 3/2 pi */ +#define M_3_2_PI 4.71238898038f + +/** 2 pi */ +#define M_2__PI 6.28318530718f + +/** Label for pixels not used in yet. */ +#define NOTUSED 0 + +/** Label for pixels already used in detection. */ +#define USED 1 + +/*----------------------------------------------------------------------------*/ +/** Chained list of coordinates. + */ +struct coorlist +{ + int16_t x,y; + struct coorlist * next; +}; + +/*----------------------------------------------------------------------------*/ +/** A point (or pixel). + */ +struct lsd_point {int16_t x,y;}; + + +/*----------------------------------------------------------------------------*/ +/*------------------------- Miscellaneous functions --------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** Fatal error, print a message to standard-error output and exit. + */ +//static void error(char * msg) +//{ +// fprintf(stderr,"LSD Error: %s\n",msg); +// exit(EXIT_FAILURE); +//} + +/*----------------------------------------------------------------------------*/ +/** Doubles relative error factor + */ +#define RELATIVE_ERROR_FACTOR 100.0f + +/*----------------------------------------------------------------------------*/ +/** Compare doubles by relative error. + + The resulting rounding error after floating point computations + depend on the specific operations done. The same number computed by + different algorithms could present different rounding errors. For a + useful comparison, an estimation of the relative rounding error + should be considered and compared to a factor times EPS. The factor + should be related to the cumulated rounding error in the chain of + computation. Here, as a simplification, a fixed factor is used. + */ +static int double_equal(float a, float b) +{ + float abs_diff,aa,bb,abs_max; + + /* trivial case */ + if( a == b ) return TRUE; + + abs_diff = fabs(a-b); + // For the numbers we work with, this is valid test that avoids some calculations. + // The error threshold tested below is 1/1000 of the diff/max_val + if (abs_diff > 0.1f) return FALSE; + aa = fabs(a); + bb = fabs(b); + abs_max = aa > bb ? aa : bb; + + /* FLT_MIN is the smallest normalized number, thus, the smallest + number whose relative error is bounded by FLT_EPSILON. For + smaller numbers, the same quantization steps as for FLT_MIN + are used. Then, for smaller numbers, a meaningful "relative" + error should be computed by dividing the difference by FLT_MIN. */ + if( abs_max < FLT_MIN ) abs_max = FLT_MIN; + + /* equal if relative error <= factor x eps */ + return (abs_diff / abs_max) <= (RELATIVE_ERROR_FACTOR * FLT_EPSILON); +} + +/*----------------------------------------------------------------------------*/ +/** Computes Euclidean distance between point (x1,y1) and point (x2,y2). + */ +static float dist(float x1, float y1, float x2, float y2) +{ + return sqrt( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) ); +} + + +/*----------------------------------------------------------------------------*/ +/*----------------------- 'list of n-tuple' data type ------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** 'list of n-tuple' data type + + The i-th component of the j-th n-tuple of an n-tuple list 'ntl' + is accessed with: + + ntl->values[ i + j * ntl->dim ] + + The dimension of the n-tuple (n) is: + + ntl->dim + + The number of n-tuples in the list is: + + ntl->size + + The maximum number of n-tuples that can be stored in the + list with the allocated memory at a given time is given by: + + ntl->max_size + */ +typedef struct ntuple_list_s +{ + unsigned int size; + unsigned int max_size; + unsigned int dim; + float * values; +} * ntuple_list; + +/*----------------------------------------------------------------------------*/ +/** Free memory used in n-tuple 'in'. + */ +static void free_ntuple_list(ntuple_list in) +{ + if( in == NULL || in->values == NULL ) + error("free_ntuple_list: invalid n-tuple input."); + free( (void *) in->values ); + free( (void *) in ); +} + +/*----------------------------------------------------------------------------*/ +/** Create an n-tuple list and allocate memory for one element. + @param dim the dimension (n) of the n-tuple. + */ +static ntuple_list new_ntuple_list(unsigned int dim) +{ + ntuple_list n_tuple; + + /* check parameters */ + if( dim == 0 ) error("new_ntuple_list: 'dim' must be positive."); + + /* get memory for list structure */ + n_tuple = (ntuple_list) malloc( sizeof(struct ntuple_list_s) ); + if( n_tuple == NULL ) error("not enough memory."); + + /* initialize list */ + n_tuple->size = 0; + n_tuple->max_size = 1; + n_tuple->dim = dim; + + /* get memory for tuples */ + n_tuple->values = (float *) malloc( dim*n_tuple->max_size * sizeof(float) ); + if( n_tuple->values == NULL ) error("not enough memory."); + + return n_tuple; +} + +/*----------------------------------------------------------------------------*/ +/** Enlarge the allocated memory of an n-tuple list. + */ +static void enlarge_ntuple_list(ntuple_list n_tuple) +{ + /* check parameters */ + if( n_tuple == NULL || n_tuple->values == NULL || n_tuple->max_size == 0 ) + error("enlarge_ntuple_list: invalid n-tuple."); + + /* duplicate number of tuples */ + n_tuple->max_size *= 2; + + /* realloc memory */ + n_tuple->values = (float *) realloc( (void *) n_tuple->values, + n_tuple->dim * n_tuple->max_size * sizeof(float) ); + if( n_tuple->values == NULL ) error("not enough memory."); +} + +/*----------------------------------------------------------------------------*/ +/** Add a 7-tuple to an n-tuple list. + */ +static void add_7tuple( ntuple_list out, float v1, float v2, float v3, + float v4, float v5, float v6, float v7 ) +{ + /* check parameters */ + if( out == NULL ) error("add_7tuple: invalid n-tuple input."); + if( out->dim != 7 ) error("add_7tuple: the n-tuple must be a 7-tuple."); + + /* if needed, alloc more tuples to 'out' */ + if( out->size == out->max_size ) enlarge_ntuple_list(out); + if( out->values == NULL ) error("add_7tuple: invalid n-tuple input."); + + /* add new 7-tuple */ + out->values[ out->size * out->dim + 0 ] = v1; + out->values[ out->size * out->dim + 1 ] = v2; + out->values[ out->size * out->dim + 2 ] = v3; + out->values[ out->size * out->dim + 3 ] = v4; + out->values[ out->size * out->dim + 4 ] = v5; + out->values[ out->size * out->dim + 5 ] = v6; + out->values[ out->size * out->dim + 6 ] = v7; + + /* update number of tuples counter */ + out->size++; +} + + +/*----------------------------------------------------------------------------*/ +/*----------------------------- Image Data Types -----------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** char image data type + + The pixel value at (x,y) is accessed by: + + image->data[ x + y * image->xsize ] + + with x and y integer. + */ +typedef struct image_char_s +{ + unsigned char * data; + unsigned int xsize,ysize; +} * image_char; + +/*----------------------------------------------------------------------------*/ +/** Free memory used in image_char 'i'. + */ +static void free_image_char(image_char i) +{ + if( i == NULL || i->data == NULL ) + error("free_image_char: invalid input image."); + free( (void *) i->data ); + free( (void *) i ); +} + +/*----------------------------------------------------------------------------*/ +/** Create a new image_char of size 'xsize' times 'ysize'. + */ +static image_char new_image_char(unsigned int xsize, unsigned int ysize) +{ + image_char image; + + /* check parameters */ + if( xsize == 0 || ysize == 0 ) error("new_image_char: invalid image size."); + + /* get memory */ + image = (image_char) malloc( sizeof(struct image_char_s) ); + if( image == NULL ) error("not enough memory."); + image->data = (unsigned char *) calloc( (size_t) (xsize*ysize), + sizeof(unsigned char) ); + if( image->data == NULL ) error("not enough memory."); + + /* set image size */ + image->xsize = xsize; + image->ysize = ysize; + + return image; +} + +/*----------------------------------------------------------------------------*/ +/** Create a new image_double of size 'xsize' times 'ysize' + with the data pointed by 'data'. + */ +static image_char new_image_char_ptr( unsigned int xsize, + unsigned int ysize, unsigned char * data ) +{ + image_char image; + + /* check parameters */ + if( xsize == 0 || ysize == 0 ) + error("new_image_char_ptr: invalid image size."); + if( data == NULL ) error("new_image_char_ptr: NULL data pointer."); + + /* get memory */ + image = (image_char) malloc( sizeof(struct image_char_s) ); + if( image == NULL ) error("not enough memory."); + + /* set image */ + image->xsize = xsize; + image->ysize = ysize; + image->data = data; + + return image; +} + +/*----------------------------------------------------------------------------*/ +/** Create a new image_char of size 'xsize' times 'ysize', + initialized to the value 'fill_value'. + */ +static image_char new_image_char_ini( unsigned int xsize, unsigned int ysize, + unsigned char fill_value ) +{ + image_char image = new_image_char(xsize,ysize); /* create image */ + unsigned int N = xsize*ysize; + unsigned int i; + + /* check parameters */ + if( image == NULL || image->data == NULL ) + error("new_image_char_ini: invalid image."); + + /* initialize */ + for(i=0; idata[i] = fill_value; + + return image; +} + +/*----------------------------------------------------------------------------*/ +/** int image data type + + The pixel value at (x,y) is accessed by: + + image->data[ x + y * image->xsize ] + + with x and y integer. + */ +typedef struct image_int_s +{ + int16_t * data; + unsigned int xsize,ysize; +} * image_int; + +/*----------------------------------------------------------------------------*/ +/** Free memory used in image_int 'i'. + */ +static void free_image_int(image_int i) +{ + if( i == NULL || i->data == NULL ) + error("free_image_int: invalid input image."); + free( (void *) i->data ); + free( (void *) i ); +} + +/*----------------------------------------------------------------------------*/ +/** Create a new image_int of size 'xsize' times 'ysize'. + */ +static image_int new_image_int(unsigned int xsize, unsigned int ysize) +{ + image_int image; + + /* check parameters */ + if( xsize == 0 || ysize == 0 ) error("new_image_int: invalid image size."); + + /* get memory */ + image = (image_int) malloc( sizeof(struct image_int_s) ); + if( image == NULL ) error("not enough memory."); + image->data = (int16_t *) calloc( (size_t) (xsize*ysize), sizeof(int16_t) ); + if( image->data == NULL ) error("not enough memory."); + + /* set image size */ + image->xsize = xsize; + image->ysize = ysize; + + return image; +} + +/*----------------------------------------------------------------------------*/ +/** Create a new image_int of size 'xsize' times 'ysize', + initialized to the value 'fill_value'. + */ +static image_int new_image_int_ini( unsigned int xsize, unsigned int ysize, + int fill_value ) +{ + image_int image = new_image_int(xsize,ysize); /* create image */ + unsigned int N = xsize*ysize; + unsigned int i; + + /* initialize */ + for(i=0; idata[i] = fill_value; + + return image; +} + +/*----------------------------------------------------------------------------*/ +/** float image data type + + The pixel value at (x,y) is accessed by: + + image->data[ x + y * image->xsize ] + + with x and y integer. + */ +typedef struct image_double_s +{ + float * data; + unsigned int xsize,ysize; +} * image_double; + +/*----------------------------------------------------------------------------*/ +/** Free memory used in image_double 'i'. + */ +static void free_image_double(image_double i) +{ + if( i == NULL || i->data == NULL ) + error("free_image_double: invalid input image."); + free( (void *) i->data ); + free( (void *) i ); +} + +/*----------------------------------------------------------------------------*/ +/** Create a new image_double of size 'xsize' times 'ysize'. + */ +static image_double new_image_double(unsigned int xsize, unsigned int ysize) +{ + image_double image; + + /* check parameters */ + if( xsize == 0 || ysize == 0 ) error("new_image_double: invalid image size."); + + /* get memory */ + image = (image_double) malloc( sizeof(struct image_double_s) ); + if( image == NULL ) error("not enough memory."); + image->data = (float *) calloc( (size_t) (xsize*ysize), sizeof(float) ); + if( image->data == NULL ) error("not enough memory."); + + /* set image size */ + image->xsize = xsize; + image->ysize = ysize; + + return image; +} + +/*----------------------------------------------------------------------------*/ +/** Create a new image_double of size 'xsize' times 'ysize' + with the data pointed by 'data'. + */ +static image_double new_image_double_ptr( unsigned int xsize, + unsigned int ysize, float * data ) +{ + image_double image; + + /* check parameters */ + if( xsize == 0 || ysize == 0 ) + error("new_image_double_ptr: invalid image size."); + if( data == NULL ) error("new_image_double_ptr: NULL data pointer."); + + /* get memory */ + image = (image_double) malloc( sizeof(struct image_double_s) ); + if( image == NULL ) error("not enough memory."); + + /* set image */ + image->xsize = xsize; + image->ysize = ysize; + image->data = data; + + return image; +} + + +/*----------------------------------------------------------------------------*/ +/*----------------------------- Gaussian filter ------------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** Compute a Gaussian kernel of length 'kernel->dim', + standard deviation 'sigma', and centered at value 'mean'. + + For example, if mean=0.5, the Gaussian will be centered + in the middle point between values 'kernel->values[0]' + and 'kernel->values[1]'. + */ +static void gaussian_kernel(ntuple_list kernel, float sigma, float mean) +{ + float sum = 0.0; + float val; + unsigned int i; + + /* check parameters */ + if( kernel == NULL || kernel->values == NULL ) + error("gaussian_kernel: invalid n-tuple 'kernel'."); + if( sigma <= 0.0 ) error("gaussian_kernel: 'sigma' must be positive."); + + /* compute Gaussian kernel */ + if( kernel->max_size < 1 ) enlarge_ntuple_list(kernel); + kernel->size = 1; + for(i=0;idim;i++) + { + val = ( (float) i - mean ) / sigma; + kernel->values[i] = exp( -0.5 * val * val ); + sum += kernel->values[i]; + } + + /* normalization */ + if( sum >= 0.0f ) for(i=0;idim;i++) kernel->values[i] /= sum; +} + +/*----------------------------------------------------------------------------*/ +/** Scale the input image 'in' by a factor 'scale' by Gaussian sub-sampling. + + For example, scale=0.8 will give a result at 80% of the original size. + + The image is convolved with a Gaussian kernel + @f[ + G(x,y) = \frac{1}{2\pi\sigma^2} e^{-\frac{x^2+y^2}{2\sigma^2}} + @f] + before the sub-sampling to prevent aliasing. + + The standard deviation sigma given by: + - sigma = sigma_scale / scale, if scale < 1.0 + - sigma = sigma_scale, if scale >= 1.0 + + To be able to sub-sample at non-integer steps, some interpolation + is needed. In this implementation, the interpolation is done by + the Gaussian kernel, so both operations (filtering and sampling) + are done at the same time. The Gaussian kernel is computed + centered on the coordinates of the required sample. In this way, + when applied, it gives directly the result of convolving the image + with the kernel and interpolated to that particular position. + + A fast algorithm is done using the separability of the Gaussian + kernel. Applying the 2D Gaussian kernel is equivalent to applying + first a horizontal 1D Gaussian kernel and then a vertical 1D + Gaussian kernel (or the other way round). The reason is that + @f[ + G(x,y) = G(x) * G(y) + @f] + where + @f[ + G(x) = \frac{1}{\sqrt{2\pi}\sigma} e^{-\frac{x^2}{2\sigma^2}}. + @f] + The algorithm first applies a combined Gaussian kernel and sampling + in the x axis, and then the combined Gaussian kernel and sampling + in the y axis. + */ +static image_double gaussian_sampler( image_double in, float scale, + float sigma_scale ) +{ + image_double aux,out; + ntuple_list kernel; + unsigned int N,M,h,n,x,y,i; + int xc,yc,j,double_x_size,double_y_size; + float sigma,xx,yy,sum,prec; + + /* check parameters */ + if( in == NULL || in->data == NULL || in->xsize == 0 || in->ysize == 0 ) + error("gaussian_sampler: invalid image."); + if( scale <= 0.0 ) error("gaussian_sampler: 'scale' must be positive."); + if( sigma_scale <= 0.0 ) + error("gaussian_sampler: 'sigma_scale' must be positive."); + + /* compute new image size and get memory for images */ + if( in->xsize * scale > (float) UINT_MAX || + in->ysize * scale > (float) UINT_MAX ) + error("gaussian_sampler: the output image size exceeds the handled size."); + N = (unsigned int) ceil( in->xsize * scale ); + M = (unsigned int) ceil( in->ysize * scale ); + aux = new_image_double(N,in->ysize); + out = new_image_double(N,M); + + /* sigma, kernel size and memory for the kernel */ + sigma = scale < 1.0 ? sigma_scale / scale : sigma_scale; + /* + The size of the kernel is selected to guarantee that the + the first discarded term is at least 10^prec times smaller + than the central value. For that, h should be larger than x, with + e^(-x^2/2sigma^2) = 1/10^prec. + Then, + x = sigma * sqrt( 2 * prec * ln(10) ). + */ + prec = 3.0; + h = (unsigned int) ceil( sigma * sqrt( 2.0 * prec * log(10.0) ) ); + n = 1+2*h; /* kernel size */ + kernel = new_ntuple_list(n); + + /* auxiliary float image size variables */ + double_x_size = (int) (2 * in->xsize); + double_y_size = (int) (2 * in->ysize); + + /* First subsampling: x axis */ + for(x=0;xxsize;x++) + { + /* + x is the coordinate in the new image. + xx is the corresponding x-value in the original size image. + xc is the integer value, the pixel coordinate of xx. + */ + xx = (float) x / scale; + /* coordinate (0.0,0.0) is in the center of pixel (0,0), + so the pixel with xc=0 get the values of xx from -0.5 to 0.5 */ + xc = (int) floor( xx + 0.5 ); + gaussian_kernel( kernel, sigma, (float) h + xx - (float) xc ); + /* the kernel must be computed for each x because the fine + offset xx-xc is different in each case */ + + for(y=0;yysize;y++) + { + sum = 0.0; + for(i=0;idim;i++) + { + j = xc - h + i; + + /* symmetry boundary condition */ + while( j < 0 ) j += double_x_size; + while( j >= double_x_size ) j -= double_x_size; + if( j >= (int) in->xsize ) j = double_x_size-1-j; + + sum += in->data[ j + y * in->xsize ] * kernel->values[i]; + } + aux->data[ x + y * aux->xsize ] = sum; + } + } + + /* Second subsampling: y axis */ + for(y=0;yysize;y++) + { + /* + y is the coordinate in the new image. + yy is the corresponding x-value in the original size image. + yc is the integer value, the pixel coordinate of xx. + */ + yy = (float) y / scale; + /* coordinate (0.0,0.0) is in the center of pixel (0,0), + so the pixel with yc=0 get the values of yy from -0.5 to 0.5 */ + yc = (int) floor( yy + 0.5 ); + gaussian_kernel( kernel, sigma, (float) h + yy - (float) yc ); + /* the kernel must be computed for each y because the fine + offset yy-yc is different in each case */ + + for(x=0;xxsize;x++) + { + sum = 0.0; + for(i=0;idim;i++) + { + j = yc - h + i; + + /* symmetry boundary condition */ + while( j < 0 ) j += double_y_size; + while( j >= double_y_size ) j -= double_y_size; + if( j >= (int) in->ysize ) j = double_y_size-1-j; + + sum += aux->data[ x + j * aux->xsize ] * kernel->values[i]; + } + out->data[ x + y * out->xsize ] = sum; + } + } + + /* free memory */ + free_ntuple_list(kernel); + free_image_double(aux); + + return out; +} + + +/*----------------------------------------------------------------------------*/ +/*--------------------------------- Gradient ---------------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** Computes the direction of the level line of 'in' at each point. + + The result is: + - an image_int with the angle at each pixel, or NOTDEF if not defined. + - the image_int 'modgrad' (a pointer is passed as argument) + with the gradient magnitude at each point. + - a list of pixels 'list_p' roughly ordered by decreasing + gradient magnitude. (The order is made by classifying points + into bins by gradient magnitude. The parameters 'n_bins' and + 'max_grad' specify the number of bins and the gradient modulus + at the highest bin. The pixels in the list would be in + decreasing gradient magnitude, up to a precision of the size of + the bins.) + - a pointer 'mem_p' to the memory used by 'list_p' to be able to + free the memory when it is not used anymore. + */ +static image_int ll_angle( image_char in, float threshold, + struct coorlist ** list_p, void ** mem_p, + image_int * modgrad, unsigned int n_bins ) +{ + image_int g; + unsigned int n,p,x,y,adr,i; + float com1,com2,gx,gy,norm,norm2; + /* the rest of the variables are used for pseudo-ordering + the gradient magnitude values */ + int list_count = 0; + struct coorlist * list; + struct coorlist ** range_l_s; /* array of pointers to start of bin list */ + struct coorlist ** range_l_e; /* array of pointers to end of bin list */ + struct coorlist * start; + struct coorlist * end; + float max_grad = 0.0; + + /* check parameters */ + if( in == NULL || in->data == NULL || in->xsize == 0 || in->ysize == 0 ) + error("ll_angle: invalid image."); + if( threshold < 0.0 ) error("ll_angle: 'threshold' must be positive."); + if( list_p == NULL ) error("ll_angle: NULL pointer 'list_p'."); + if( mem_p == NULL ) error("ll_angle: NULL pointer 'mem_p'."); + if( modgrad == NULL ) error("ll_angle: NULL pointer 'modgrad'."); + if( n_bins == 0 ) error("ll_angle: 'n_bins' must be positive."); + + /* image size shortcuts */ + n = in->ysize; + p = in->xsize; + + /* allocate output image */ + g = new_image_int(in->xsize,in->ysize); + + /* get memory for the image of gradient modulus */ + *modgrad = new_image_int(in->xsize,in->ysize); + + /* get memory for "ordered" list of pixels */ + list = (struct coorlist *) calloc( (size_t) (n*p), sizeof(struct coorlist) ); + *mem_p = (void *) list; + range_l_s = (struct coorlist **) calloc( (size_t) n_bins, + sizeof(struct coorlist *) ); + range_l_e = (struct coorlist **) calloc( (size_t) n_bins, + sizeof(struct coorlist *) ); + if( list == NULL || range_l_s == NULL || range_l_e == NULL ) + error("not enough memory."); + for(i=0;idata[(n-1)*p+x] = NOTDEF; + for(y=0;ydata[p*y+p-1] = NOTDEF; + + /* compute gradient on the remaining pixels */ + for(x=0;xdata[adr+p+1] - in->data[adr]; + com2 = in->data[adr+1] - in->data[adr+p]; + + gx = com1+com2; /* gradient x component */ + gy = com1-com2; /* gradient y component */ + norm2 = gx*gx+gy*gy; + norm = sqrt( norm2 / 4.0 ); /* gradient norm */ + + (*modgrad)->data[adr] = norm; /* store gradient norm */ + + if( norm <= threshold ) /* norm too small, gradient no defined */ + g->data[adr] = NOTDEF_INT; //radToDeg(NOTDEF); /* gradient angle not defined */ + else + { + /* gradient angle computation */ + g->data[adr] = radToDeg(atan2(gx,-gy)); + + /* look for the maximum of the gradient */ + if( norm > max_grad ) max_grad = norm; + } + } + + /* compute histogram of gradient values */ + for(x=0;xdata[y*p+x]; + + /* store the point in the right bin according to its norm */ + i = (unsigned int) (norm * (float) n_bins / max_grad); + if( i >= n_bins ) i = n_bins-1; + if( range_l_e[i] == NULL ) + range_l_s[i] = range_l_e[i] = list+list_count++; + else + { + range_l_e[i]->next = list+list_count; + range_l_e[i] = list+list_count++; + } + range_l_e[i]->x = (int) x; + range_l_e[i]->y = (int) y; + range_l_e[i]->next = NULL; + } + + /* Make the list of pixels (almost) ordered by norm value. + It starts by the larger bin, so the list starts by the + pixels with the highest gradient value. Pixels would be ordered + by norm value, up to a precision given by max_grad/n_bins. + */ + for(i=n_bins-1; i>0 && range_l_s[i]==NULL; i--); + start = range_l_s[i]; + end = range_l_e[i]; + if( start != NULL ) + while(i>0) + { + --i; + if( range_l_s[i] != NULL ) + { + end->next = range_l_s[i]; + end = range_l_e[i]; + } + } + *list_p = start; + + /* free memory */ + free( (void *) range_l_s ); + free( (void *) range_l_e ); + + return g; +} + +/*----------------------------------------------------------------------------*/ +/** Is point (x,y) aligned to angle theta, up to precision 'prec'? + */ +static int isaligned( int x, int y, image_int angles, float theta, + float prec ) +{ + float a; + + /* check parameters */ + if( angles == NULL || angles->data == NULL ) + error("isaligned: invalid image 'angles'."); + if( x < 0 || y < 0 || x >= (int) angles->xsize || y >= (int) angles->ysize ) + error("isaligned: (x,y) out of the image."); + if( prec < 0.0 ) error("isaligned: 'prec' must be positive."); + + /* angle at pixel (x,y) */ + a = degToRad(angles->data[ x + y * angles->xsize ]); + + /* pixels whose level-line angle is not defined + are considered as NON-aligned */ + if( a == NOTDEF ) return FALSE; /* there is no need to call the function + 'double_equal' here because there is + no risk of problems related to the + comparison doubles, we are only + interested in the exact NOTDEF value */ + + /* it is assumed that 'theta' and 'a' are in the range [-pi,pi] */ + theta -= a; + if( theta < 0.0 ) theta = -theta; + if( theta > M_3_2_PI ) + { + theta -= M_2__PI; + if( theta < 0.0 ) theta = -theta; + } + + return theta <= prec; +} + +static int isaligned_fast(int angle, float theta, + float prec ) +{ + float a; + + if (angle == NOTDEF_INT) return FALSE; // faster to test the integer value + + /* angle at pixel (x,y) */ + a = degToRad(angle); + + /* pixels whose level-line angle is not defined + are considered as NON-aligned */ + if( a == NOTDEF ) return FALSE; /* there is no need to call the function + 'double_equal' here because there is + no risk of problems related to the + comparison doubles, we are only + interested in the exact NOTDEF value */ + + /* it is assumed that 'theta' and 'a' are in the range [-pi,pi] */ + theta -= a; + if( theta < 0.0 ) theta = -theta; + if( theta > M_3_2_PI ) + { + theta -= M_2__PI; + if( theta < 0.0 ) theta = -theta; + } + + return theta <= prec; +} /* isaligned_fast() */ + +/*----------------------------------------------------------------------------*/ +/** Absolute value angle difference. + */ +static float angle_diff(float a, float b) +{ + a -= b; + while( a <= -M_PI ) a += M_2__PI; + while( a > M_PI ) a -= M_2__PI; + if( a < 0.0 ) a = -a; + return a; +} + +/*----------------------------------------------------------------------------*/ +/** Signed angle difference. + */ +static float angle_diff_signed(float a, float b) +{ + a -= b; + while( a <= -M_PI ) a += M_2__PI; + while( a > M_PI ) a -= M_2__PI; + return a; +} + + +/*----------------------------------------------------------------------------*/ +/*----------------------------- NFA computation ------------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** Computes the natural logarithm of the absolute value of + the gamma function of x using the Lanczos approximation. + See http://www.rskey.org/gamma.htm + + The formula used is + @f[ + \Gamma(x) = \frac{ \sum_{n=0}^{N} q_n x^n }{ \Pi_{n=0}^{N} (x+n) } + (x+5.5)^{x+0.5} e^{-(x+5.5)} + @f] + so + @f[ + \log\Gamma(x) = \log\left( \sum_{n=0}^{N} q_n x^n \right) + + (x+0.5) \log(x+5.5) - (x+5.5) - \sum_{n=0}^{N} \log(x+n) + @f] + and + q0 = 75122.6331530, + q1 = 80916.6278952, + q2 = 36308.2951477, + q3 = 8687.24529705, + q4 = 1168.92649479, + q5 = 83.8676043424, + q6 = 2.50662827511. + */ +static float log_gamma_lanczos(float x) +{ + static float q[7] = { 75122.6331530, 80916.6278952, 36308.2951477, + 8687.24529705, 1168.92649479, 83.8676043424, + 2.50662827511 }; + float a = (x+0.5) * log(x+5.5) - (x+5.5); + float b = 0.0; + int n; + + for(n=0;n<7;n++) + { + a -= log( x + (float) n ); + b += q[n] * pow( x, (float) n ); + } + return a + log(b); +} + +/*----------------------------------------------------------------------------*/ +/** Computes the natural logarithm of the absolute value of + the gamma function of x using Windschitl method. + See http://www.rskey.org/gamma.htm + + The formula used is + @f[ + \Gamma(x) = \sqrt{\frac{2\pi}{x}} \left( \frac{x}{e} + \sqrt{ x\sinh(1/x) + \frac{1}{810x^6} } \right)^x + @f] + so + @f[ + \log\Gamma(x) = 0.5\log(2\pi) + (x-0.5)\log(x) - x + + 0.5x\log\left( x\sinh(1/x) + \frac{1}{810x^6} \right). + @f] + This formula is a good approximation when x > 15. + */ +static float log_gamma_windschitl(float x) +{ + return 0.918938533204673 + (x-0.5)*log(x) - x + + 0.5*x*log( x*sinh(1/x) + 1/(810.0*pow(x,6.0)) ); +} + +/*----------------------------------------------------------------------------*/ +/** Computes the natural logarithm of the absolute value of + the gamma function of x. When x>15 use log_gamma_windschitl(), + otherwise use log_gamma_lanczos(). + */ +#define log_gamma(x) ((x)>15.0?log_gamma_windschitl(x):log_gamma_lanczos(x)) + +///*----------------------------------------------------------------------------*/ +///** Size of the table to store already computed inverse values. +// */ +//#define TABSIZE 100000 + +/*----------------------------------------------------------------------------*/ +/** Computes -log10(NFA). + + NFA stands for Number of False Alarms: + @f[ + \mathrm{NFA} = NT \cdot B(n,k,p) + @f] + + - NT - number of tests + - B(n,k,p) - tail of binomial distribution with parameters n,k and p: + @f[ + B(n,k,p) = \sum_{j=k}^n + \left(\begin{array}{c}n\\j\end{array}\right) + p^{j} (1-p)^{n-j} + @f] + + The value -log10(NFA) is equivalent but more intuitive than NFA: + - -1 corresponds to 10 mean false alarms + - 0 corresponds to 1 mean false alarm + - 1 corresponds to 0.1 mean false alarms + - 2 corresponds to 0.01 mean false alarms + - ... + + Used this way, the bigger the value, better the detection, + and a logarithmic scale is used. + + @param n,k,p binomial parameters. + @param logNT logarithm of Number of Tests + + The computation is based in the gamma function by the following + relation: + @f[ + \left(\begin{array}{c}n\\k\end{array}\right) + = \frac{ \Gamma(n+1) }{ \Gamma(k+1) \cdot \Gamma(n-k+1) }. + @f] + We use efficient algorithms to compute the logarithm of + the gamma function. + + To make the computation faster, not all the sum is computed, part + of the terms are neglected based on a bound to the error obtained + (an error of 10% in the result is accepted). + */ +static float nfa(int n, int k, float p, float logNT) +{ +// static float inv[TABSIZE]; /* table to keep computed inverse values */ + float tolerance = 0.1; /* an error of 10% in the result is accepted */ + float log1term,term,bin_term,mult_term,bin_tail,err,p_term; + int i; + + /* check parameters */ + if( n<0 || k<0 || k>n || p<=0.0 || p>=1.0 ) + error("nfa: wrong n, k or p values."); + + /* trivial cases */ + if( n==0 || k==0 ) return -logNT; + if( n==k ) return -logNT - (float) n * log10(p); + + /* probability term */ + p_term = p / (1.0-p); + + /* compute the first term of the series */ + /* + binomial_tail(n,k,p) = sum_{i=k}^n bincoef(n,i) * p^i * (1-p)^{n-i} + where bincoef(n,i) are the binomial coefficients. + But + bincoef(n,k) = gamma(n+1) / ( gamma(k+1) * gamma(n-k+1) ). + We use this to compute the first term. Actually the log of it. + */ + log1term = log_gamma( (float) n + 1.0 ) - log_gamma( (float) k + 1.0 ) + - log_gamma( (float) (n-k) + 1.0 ) + + (float) k * log(p) + (float) (n-k) * log(1.0-p); + term = exp(log1term); + + /* in some cases no more computations are needed */ + if( double_equal(term,0.0) ) /* the first term is almost zero */ + { + if( (float) k > (float) n * p ) /* at begin or end of the tail? */ + return -log1term / M_LN10 - logNT; /* end: use just the first term */ + else + return -logNT; /* begin: the tail is roughly 1 */ + } + + /* compute more terms if needed */ + bin_tail = term; + for(i=k+1;i<=n;i++) + { + /* + As + term_i = bincoef(n,i) * p^i * (1-p)^(n-i) + and + bincoef(n,i)/bincoef(n,i-1) = n-1+1 / i, + then, + term_i / term_i-1 = (n-i+1)/i * p/(1-p) + and + term_i = term_i-1 * (n-i+1)/i * p/(1-p). + 1/i is stored in a table as they are computed, + because divisions are expensive. + p/(1-p) is computed only once and stored in 'p_term'. + */ +// bin_term = (float) (n-i+1) * ( ii. + Then, the error on the binomial tail when truncated at + the i term can be bounded by a geometric series of form + term_i * sum mult_term_i^j. */ + err = term * ( ( 1.0 - pow( mult_term, (float) (n-i+1) ) ) / + (1.0-mult_term) - 1.0 ); + + /* One wants an error at most of tolerance*final_result, or: + tolerance * abs(-log10(bin_tail)-logNT). + Now, the error that can be accepted on bin_tail is + given by tolerance*final_result divided by the derivative + of -log10(x) when x=bin_tail. that is: + tolerance * abs(-log10(bin_tail)-logNT) / (1/bin_tail) + Finally, we truncate the tail if the error is less than: + tolerance * abs(-log10(bin_tail)-logNT) * bin_tail */ + if( err < tolerance * fabs(-log10(bin_tail)-logNT) * bin_tail ) break; + } + } + return -log10(bin_tail) - logNT; +} + + +/*----------------------------------------------------------------------------*/ +/*--------------------------- Rectangle structure ----------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** Rectangle structure: line segment with width. + */ +struct rect +{ + float x1,y1,x2,y2; /* first and second point of the line segment */ + float width; /* rectangle width */ + float x,y; /* center of the rectangle */ + float theta; /* angle */ + float dx,dy; /* (dx,dy) is vector oriented as the line segment */ + float prec; /* tolerance angle */ + float p; /* probability of a point with angle within 'prec' */ +}; + +/*----------------------------------------------------------------------------*/ +/** Copy one rectangle structure to another. + */ +static void rect_copy(struct rect * in, struct rect * out) +{ + /* check parameters */ + if( in == NULL || out == NULL ) error("rect_copy: invalid 'in' or 'out'."); + + /* copy values */ + out->x1 = in->x1; + out->y1 = in->y1; + out->x2 = in->x2; + out->y2 = in->y2; + out->width = in->width; + out->x = in->x; + out->y = in->y; + out->theta = in->theta; + out->dx = in->dx; + out->dy = in->dy; + out->prec = in->prec; + out->p = in->p; +} + +/*----------------------------------------------------------------------------*/ +/** Rectangle points iterator. + + The integer coordinates of pixels inside a rectangle are + iteratively explored. This structure keep track of the process and + functions ri_ini(), ri_inc(), ri_end(), and ri_del() are used in + the process. An example of how to use the iterator is as follows: + \code + + struct rect * rec = XXX; // some rectangle + rect_iter * i; + for( i=ri_ini(rec); !ri_end(i); ri_inc(i) ) + { + // your code, using 'i->x' and 'i->y' as coordinates + } + ri_del(i); // delete iterator + + \endcode + The pixels are explored 'column' by 'column', where we call + 'column' a set of pixels with the same x value that are inside the + rectangle. The following is an schematic representation of a + rectangle, the 'column' being explored is marked by colons, and + the current pixel being explored is 'x,y'. + \verbatim + + vx[1],vy[1] + * * + * * + * * + * ye + * : * + vx[0],vy[0] : * + * : * + * x,y * + * : * + * : vx[2],vy[2] + * : * + y ys * + ^ * * + | * * + | * * + +---> x vx[3],vy[3] + + \endverbatim + The first 'column' to be explored is the one with the smaller x + value. Each 'column' is explored starting from the pixel of the + 'column' (inside the rectangle) with the smallest y value. + + The four corners of the rectangle are stored in order that rotates + around the corners at the arrays 'vx[]' and 'vy[]'. The first + point is always the one with smaller x value. + + 'x' and 'y' are the coordinates of the pixel being explored. 'ys' + and 'ye' are the start and end values of the current column being + explored. So, 'ys' < 'ye'. + */ +typedef struct +{ + float vx[4]; /* rectangle's corner X coordinates in circular order */ + float vy[4]; /* rectangle's corner Y coordinates in circular order */ + float ys,ye; /* start and end Y values of current 'column' */ + int x,y; /* coordinates of currently explored pixel */ +} rect_iter; + +/*----------------------------------------------------------------------------*/ +/** Interpolate y value corresponding to 'x' value given, in + the line 'x1,y1' to 'x2,y2'; if 'x1=x2' return the smaller + of 'y1' and 'y2'. + + The following restrictions are required: + - x1 <= x2 + - x1 <= x + - x <= x2 + */ +static float inter_low(float x, float x1, float y1, float x2, float y2) +{ + /* check parameters */ +// if( x1 > x2 || x < x1 || x > x2 ) +// error("inter_low: unsuitable input, 'x1>x2' or 'xx2'."); + + /* interpolation */ + if( double_equal(x1,x2) && y1y2 ) return y2; +// return y1 + (x-x1) * (y2-y1) / (x2-x1); + float result = y1 + (x-x1) * (y2-y1) / (x2-x1); + if (isnan(result) || isinf(result)) return (y1y2) ? y2 : 0); + return result; +} + +/*----------------------------------------------------------------------------*/ +/** Interpolate y value corresponding to 'x' value given, in + the line 'x1,y1' to 'x2,y2'; if 'x1=x2' return the larger + of 'y1' and 'y2'. + + The following restrictions are required: + - x1 <= x2 + - x1 <= x + - x <= x2 + */ +static float inter_hi(float x, float x1, float y1, float x2, float y2) +{ + /* check parameters */ +// if( x1 > x2 || x < x1 || x > x2 ) +// error("inter_hi: unsuitable input, 'x1>x2' or 'xx2'."); + + /* interpolation */ + if( double_equal(x1,x2) && y1y2 ) return y1; +// return y1 + (x-x1) * (y2-y1) / (x2-x1); + float result = y1 + (x-x1) * (y2-y1) / (x2-x1); + if (isnan(result) || isinf(result)) return (y1y2) ? y1 : 0); + return result; +} + +/*----------------------------------------------------------------------------*/ +/** Free memory used by a rectangle iterator. + */ +static void ri_del(rect_iter * iter) +{ + if( iter == NULL ) error("ri_del: NULL iterator."); + free( (void *) iter ); +} + +/*----------------------------------------------------------------------------*/ +/** Check if the iterator finished the full iteration. + + See details in \ref rect_iter + */ +static inline int ri_end(rect_iter * i) +{ + /* check input */ +// if( i == NULL ) error("ri_end: NULL iterator."); + + /* if the current x value is larger than the largest + x value in the rectangle (vx[2]), we know the full + exploration of the rectangle is finished. */ + return (float)(i->x) > i->vx[2]; +} + +/*----------------------------------------------------------------------------*/ +/** Increment a rectangle iterator. + + See details in \ref rect_iter + */ +static void ri_inc(rect_iter * i) +{ + /* check input */ +// if( i == NULL ) error("ri_inc: NULL iterator."); + + /* if not at end of exploration, + increase y value for next pixel in the 'column' */ + if( !ri_end(i) ) i->y++; + + /* if the end of the current 'column' is reached, + and it is not the end of exploration, + advance to the next 'column' */ + while( (float) (i->y) > i->ye && !ri_end(i) ) + { + /* increase x, next 'column' */ + i->x++; + + /* if end of exploration, return */ + if( ri_end(i) ) return; + + /* update lower y limit (start) for the new 'column'. + + We need to interpolate the y value that corresponds to the + lower side of the rectangle. The first thing is to decide if + the corresponding side is + + vx[0],vy[0] to vx[3],vy[3] or + vx[3],vy[3] to vx[2],vy[2] + + Then, the side is interpolated for the x value of the + 'column'. But, if the side is vertical (as it could happen if + the rectangle is vertical and we are dealing with the first + or last 'columns') then we pick the lower value of the side + by using 'inter_low'. + */ + if( (float) i->x < i->vx[3] ) + i->ys = inter_low((float)i->x,i->vx[0],i->vy[0],i->vx[3],i->vy[3]); + else + i->ys = inter_low((float)i->x,i->vx[3],i->vy[3],i->vx[2],i->vy[2]); + + /* update upper y limit (end) for the new 'column'. + + We need to interpolate the y value that corresponds to the + upper side of the rectangle. The first thing is to decide if + the corresponding side is + + vx[0],vy[0] to vx[1],vy[1] or + vx[1],vy[1] to vx[2],vy[2] + + Then, the side is interpolated for the x value of the + 'column'. But, if the side is vertical (as it could happen if + the rectangle is vertical and we are dealing with the first + or last 'columns') then we pick the lower value of the side + by using 'inter_low'. + */ + if( (float)i->x < i->vx[1] ) + i->ye = inter_hi((float)i->x,i->vx[0],i->vy[0],i->vx[1],i->vy[1]); + else + i->ye = inter_hi((float)i->x,i->vx[1],i->vy[1],i->vx[2],i->vy[2]); + + /* new y */ + i->y = (int) ceil(i->ys); + } +} + +/*----------------------------------------------------------------------------*/ +/** Create and initialize a rectangle iterator. + + See details in \ref rect_iter + */ +static rect_iter * ri_ini(struct rect * r) +{ + float vx[4],vy[4]; + int n,offset; + rect_iter * i; + + /* check parameters */ + if( r == NULL ) error("ri_ini: invalid rectangle."); + + /* get memory */ + i = (rect_iter *) malloc(sizeof(rect_iter)); + if( i == NULL ) error("ri_ini: Not enough memory."); + + /* build list of rectangle corners ordered + in a circular way around the rectangle */ + vx[0] = r->x1 - r->dy * r->width / 2.0; + vy[0] = r->y1 + r->dx * r->width / 2.0; + vx[1] = r->x2 - r->dy * r->width / 2.0; + vy[1] = r->y2 + r->dx * r->width / 2.0; + vx[2] = r->x2 + r->dy * r->width / 2.0; + vy[2] = r->y2 - r->dx * r->width / 2.0; + vx[3] = r->x1 + r->dy * r->width / 2.0; + vy[3] = r->y1 - r->dx * r->width / 2.0; + + /* compute rotation of index of corners needed so that the first + point has the smaller x. + + if one side is vertical, thus two corners have the same smaller x + value, the one with the largest y value is selected as the first. + */ + if( r->x1 < r->x2 && r->y1 <= r->y2 ) offset = 0; + else if( r->x1 >= r->x2 && r->y1 < r->y2 ) offset = 1; + else if( r->x1 > r->x2 && r->y1 >= r->y2 ) offset = 2; + else offset = 3; + + /* apply rotation of index. */ + for(n=0; n<4; n++) + { + i->vx[n] = vx[(offset+n)%4]; + i->vy[n] = vy[(offset+n)%4]; + } + + /* Set an initial condition. + + The values are set to values that will cause 'ri_inc' (that will + be called immediately) to initialize correctly the first 'column' + and compute the limits 'ys' and 'ye'. + + 'y' is set to the integer value of vy[0], the starting corner. + + 'ys' and 'ye' are set to very small values, so 'ri_inc' will + notice that it needs to start a new 'column'. + + The smallest integer coordinate inside of the rectangle is + 'ceil(vx[0])'. The current 'x' value is set to that value minus + one, so 'ri_inc' (that will increase x by one) will advance to + the first 'column'. + */ + i->x = (int) ceil(i->vx[0]) - 1; + i->y = (int) ceil(i->vy[0]); + i->ys = i->ye = -FLT_MAX; + + /* advance to the first pixel */ + ri_inc(i); + + return i; +} +// We don't need to spend time allocating and freeing the interator structure +// since we only use 1 at a time and it's small enough to safely use as a stack var +void ri_ini_fast(rect_iter *i, struct rect * r) +{ + float vx[4],vy[4]; + int n,offset; + + /* build list of rectangle corners ordered + in a circular way around the rectangle */ + vx[0] = r->x1 - r->dy * r->width / 2.0; + vy[0] = r->y1 + r->dx * r->width / 2.0; + vx[1] = r->x2 - r->dy * r->width / 2.0; + vy[1] = r->y2 + r->dx * r->width / 2.0; + vx[2] = r->x2 + r->dy * r->width / 2.0; + vy[2] = r->y2 - r->dx * r->width / 2.0; + vx[3] = r->x1 + r->dy * r->width / 2.0; + vy[3] = r->y1 - r->dx * r->width / 2.0; + + /* compute rotation of index of corners needed so that the first + point has the smaller x. + + if one side is vertical, thus two corners have the same smaller x + value, the one with the largest y value is selected as the first. + */ + if( r->x1 < r->x2 && r->y1 <= r->y2 ) offset = 0; + else if( r->x1 >= r->x2 && r->y1 < r->y2 ) offset = 1; + else if( r->x1 > r->x2 && r->y1 >= r->y2 ) offset = 2; + else offset = 3; + + /* apply rotation of index. */ + for(n=0; n<4; n++) + { + i->vx[n] = vx[(n+offset) & 3]; + i->vy[n] = vy[(n+offset) & 3]; + } + + /* Set an initial condition. + + The values are set to values that will cause 'ri_inc' (that will + be called immediately) to initialize correctly the first 'column' + and compute the limits 'ys' and 'ye'. + + 'y' is set to the integer value of vy[0], the starting corner. + + 'ys' and 'ye' are set to very small values, so 'ri_inc' will + notice that it needs to start a new 'column'. + + The smallest integer coordinate inside of the rectangle is + 'ceil(vx[0])'. The current 'x' value is set to that value minus + one, so 'ri_inc' (that will increase x by one) will advance to + the first 'column'. + */ + i->x = (int) ceil(i->vx[0]) - 1; + i->y = (int) ceil(i->vy[0]); + i->ys = i->ye = -FLT_MAX; + + /* advance to the first pixel */ + ri_inc(i); + +} /* ri_ini_fast() */ + +/*----------------------------------------------------------------------------*/ +/** Compute a rectangle's NFA value. + */ +static float rect_nfa(struct rect * rec, image_int angles, float logNT) +{ + rect_iter i; + int pts = 0; + int alg = 0; + int xsize = angles->xsize, ysize = angles->ysize; + /* check parameters */ +// if( rec == NULL ) error("rect_nfa: invalid rectangle."); +// if( angles == NULL ) error("rect_nfa: invalid 'angles'."); + + /* compute the total number of pixels and of aligned points in 'rec' */ + ri_ini_fast(&i, rec); + for(; !ri_end(&i); ri_inc(&i)) /* rectangle iterator */ + if( i.x >= 0 && i.y >= 0 && + i.x < xsize && i.y < ysize ) + { + ++pts; /* total number of pixels counter */ + if( isaligned_fast((float)angles->data[(i.y*xsize)+i.x], rec->theta, rec->prec) ) + ++alg; /* aligned points counter */ + } +// ri_del(i); /* delete iterator */ + + return nfa(pts,alg,rec->p,logNT); /* compute NFA value */ +} + + +/*----------------------------------------------------------------------------*/ +/*---------------------------------- Regions ---------------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** Compute region's angle as the principal inertia axis of the region. + + The following is the region inertia matrix A: + @f[ + + A = \left(\begin{array}{cc} + Ixx & Ixy \\ + Ixy & Iyy \\ + \end{array}\right) + + @f] + where + + Ixx = sum_i G(i).(y_i - cx)^2 + + Iyy = sum_i G(i).(x_i - cy)^2 + + Ixy = - sum_i G(i).(x_i - cx).(y_i - cy) + + and + - G(i) is the gradient norm at pixel i, used as pixel's weight. + - x_i and y_i are the coordinates of pixel i. + - cx and cy are the coordinates of the center of th region. + + lambda1 and lambda2 are the eigenvalues of matrix A, + with lambda1 >= lambda2. They are found by solving the + characteristic polynomial: + + det( lambda I - A) = 0 + + that gives: + + lambda1 = ( Ixx + Iyy + sqrt( (Ixx-Iyy)^2 + 4.0*Ixy*Ixy) ) / 2 + + lambda2 = ( Ixx + Iyy - sqrt( (Ixx-Iyy)^2 + 4.0*Ixy*Ixy) ) / 2 + + To get the line segment direction we want to get the angle the + eigenvector associated to the smallest eigenvalue. We have + to solve for a,b in: + + a.Ixx + b.Ixy = a.lambda2 + + a.Ixy + b.Iyy = b.lambda2 + + We want the angle theta = atan(b/a). It can be computed with + any of the two equations: + + theta = atan( (lambda2-Ixx) / Ixy ) + + or + + theta = atan( Ixy / (lambda2-Iyy) ) + + When |Ixx| > |Iyy| we use the first, otherwise the second (just to + get better numeric precision). + */ +static float get_theta( struct lsd_point * reg, int reg_size, float x, float y, + image_int modgrad, float reg_angle, float prec ) +{ + float lambda,theta,weight; + float Ixx = 0.0; + float Iyy = 0.0; + float Ixy = 0.0; + int i; + + /* check parameters */ + if( reg == NULL ) error("get_theta: invalid region."); + if( reg_size <= 1 ) error("get_theta: region size <= 1."); + if( modgrad == NULL || modgrad->data == NULL ) + error("get_theta: invalid 'modgrad'."); + if( prec < 0.0 ) error("get_theta: 'prec' must be positive."); + + /* compute inertia matrix */ + for(i=0; idata[ reg[i].x + reg[i].y * modgrad->xsize ]; + Ixx += ( (float) reg[i].y - y ) * ( (float) reg[i].y - y ) * weight; + Iyy += ( (float) reg[i].x - x ) * ( (float) reg[i].x - x ) * weight; + Ixy -= ( (float) reg[i].x - x ) * ( (float) reg[i].y - y ) * weight; + } + if( double_equal(Ixx,0.0) && double_equal(Iyy,0.0) && double_equal(Ixy,0.0) ) + error("get_theta: null inertia matrix."); + + /* compute smallest eigenvalue */ + lambda = 0.5 * ( Ixx + Iyy - sqrt( (Ixx-Iyy)*(Ixx-Iyy) + 4.0*Ixy*Ixy ) ); + + /* compute angle */ + theta = fabs(Ixx)>fabs(Iyy) ? atan2(lambda-Ixx,Ixy) : atan2(Ixy,lambda-Iyy); + + /* The previous procedure doesn't cares about orientation, + so it could be wrong by 180 degrees. Here is corrected if necessary. */ + if( angle_diff(theta,reg_angle) > prec ) theta += M_PI; + + return theta; +} + +/*----------------------------------------------------------------------------*/ +/** Computes a rectangle that covers a region of points. + */ +static void region2rect( struct lsd_point * reg, int reg_size, + image_int modgrad, float reg_angle, + float prec, float p, struct rect * rec ) +{ + float x,y,dx,dy,l,w,theta,weight,sum,l_min,l_max,w_min,w_max; + int i; + int ix,iy,isum,iweight; + /* check parameters */ + if( reg == NULL ) error("region2rect: invalid region."); + if( reg_size <= 1 ) error("region2rect: region size <= 1."); + if( modgrad == NULL || modgrad->data == NULL ) + error("region2rect: invalid image 'modgrad'."); + if( rec == NULL ) error("region2rect: invalid 'rec'."); + + /* center of the region: + + It is computed as the weighted sum of the coordinates + of all the pixels in the region. The norm of the gradient + is used as the weight of a pixel. The sum is as follows: + cx = \sum_i G(i).x_i + cy = \sum_i G(i).y_i + where G(i) is the norm of the gradient of pixel i + and x_i,y_i are its coordinates. + */ +// x = y = sum = 0.0; + ix = iy = isum = 0; // integers are faster since the source data is integer + for(i=0; idata[ reg[i].x + reg[i].y * modgrad->xsize ]; + ix += reg[i].x * iweight; + iy += reg[i].y * iweight; + isum += iweight; + } + if( isum <= 0 ) error("region2rect: weights sum equal to zero."); + x = (float)ix; y = (float)iy; sum = (float)isum; + x /= sum; + y /= sum; + + /* theta */ + theta = get_theta(reg,reg_size,x,y,modgrad,reg_angle,prec); + + /* length and width: + + 'l' and 'w' are computed as the distance from the center of the + region to pixel i, projected along the rectangle axis (dx,dy) and + to the orthogonal axis (-dy,dx), respectively. + + The length of the rectangle goes from l_min to l_max, where l_min + and l_max are the minimum and maximum values of l in the region. + Analogously, the width is selected from w_min to w_max, where + w_min and w_max are the minimum and maximum of w for the pixels + in the region. + */ + dx = cos(theta); + dy = sin(theta); + l_min = l_max = w_min = w_max = 0.0; + for(i=0; i l_max ) l_max = l; + if( l < l_min ) l_min = l; + if( w > w_max ) w_max = w; + if( w < w_min ) w_min = w; + } + + /* store values */ + rec->x1 = x + l_min * dx; + rec->y1 = y + l_min * dy; + rec->x2 = x + l_max * dx; + rec->y2 = y + l_max * dy; + rec->width = w_max - w_min; + rec->x = x; + rec->y = y; + rec->theta = theta; + rec->dx = dx; + rec->dy = dy; + rec->prec = prec; + rec->p = p; + + /* we impose a minimal width of one pixel + + A sharp horizontal or vertical step would produce a perfectly + horizontal or vertical region. The width computed would be + zero. But that corresponds to a one pixels width transition in + the image. + */ + if( rec->width < 1.0 ) rec->width = 1.0; +} + +/*----------------------------------------------------------------------------*/ +/** Build a region of pixels that share the same angle, up to a + tolerance 'prec', starting at point (x,y). + */ +static void region_grow( int x, int y, image_int angles, struct lsd_point * reg, + int * reg_size, float * reg_angle, image_char used, + float prec ) +{ + float sumdx,sumdy; + int xx,yy,i; + int l_size; // local copy + float l_angle; // local copy + int xsize = used->xsize; + /* check parameters */ + if( x < 0 || y < 0 || x >= (int) angles->xsize || y >= (int) angles->ysize ) + error("region_grow: (x,y) out of the image."); +// if( angles == NULL || angles->data == NULL ) +// error("region_grow: invalid image 'angles'."); +// if( reg == NULL ) error("region_grow: invalid 'reg'."); +// if( reg_size == NULL ) error("region_grow: invalid pointer 'reg_size'."); +// if( reg_angle == NULL ) error("region_grow: invalid pointer 'reg_angle'."); +// if( used == NULL || used->data == NULL ) +// error("region_grow: invalid image 'used'."); + + /* first point of the region */ + l_size = 1; + reg[0].x = x; + reg[0].y = y; + l_angle = degToRad(angles->data[x+y*angles->xsize]); /* region's angle */ + sumdx = cos(l_angle); + sumdy = sin(l_angle); + used->data[x+y*used->xsize] = USED; + + /* try neighbors as new region points */ + for(i=0; i= xsize) { + dx--; + } + if (ty < 0) { + ty = 0; dy--; + } else if (ty+dy >= used->ysize) { + dy--; + } + for(xx=tx; xxdata[xx+yy*xsize] != USED && + isaligned_fast((float)angles->data[(yy*xsize)+xx],l_angle,prec) ) + { + /* add point */ + used->data[xx+yy*xsize] = USED; + reg[l_size].x = xx; + reg[l_size].y = yy; + ++l_size; + + /* update region's angle */ + int16_t angle = angles->data[xx+yy*xsize] % 360; + if (angle < 0) angle += 360; + sumdx += cos_table[angle]; + sumdy += sin_table[angle]; + l_angle = atan2(sumdy,sumdx); + } + } + *reg_size = l_size; + *reg_angle = l_angle; +} + +/*----------------------------------------------------------------------------*/ +/** Try some rectangles variations to improve NFA value. Only if the + rectangle is not meaningful (i.e., log_nfa <= log_eps). + */ +static float rect_improve( struct rect * rec, image_int angles, + float logNT, float log_eps ) +{ + struct rect r; + float log_nfa,log_nfa_new; + float delta = 0.5; + float delta_2 = delta / 2.0; + int n; + + log_nfa = rect_nfa(rec,angles,logNT); + + if( log_nfa > log_eps ) return log_nfa; + + /* try finer precisions */ + rect_copy(rec,&r); + for(n=0; n<5; n++) + { + r.p /= 2.0; + r.prec = r.p * M_PI; + log_nfa_new = rect_nfa(&r,angles,logNT); + if( log_nfa_new > log_nfa ) + { + log_nfa = log_nfa_new; + rect_copy(&r,rec); + } + } + + if( log_nfa > log_eps ) return log_nfa; + + /* try to reduce width */ + rect_copy(rec,&r); + for(n=0; n<5; n++) + { + if( (r.width - delta) >= 0.5 ) + { + r.width -= delta; + log_nfa_new = rect_nfa(&r,angles,logNT); + if( log_nfa_new > log_nfa ) + { + rect_copy(&r,rec); + log_nfa = log_nfa_new; + } + } + } + + if( log_nfa > log_eps ) return log_nfa; + + /* try to reduce one side of the rectangle */ + rect_copy(rec,&r); + for(n=0; n<5; n++) + { + if( (r.width - delta) >= 0.5 ) + { + r.x1 += -r.dy * delta_2; + r.y1 += r.dx * delta_2; + r.x2 += -r.dy * delta_2; + r.y2 += r.dx * delta_2; + r.width -= delta; + log_nfa_new = rect_nfa(&r,angles,logNT); + if( log_nfa_new > log_nfa ) + { + rect_copy(&r,rec); + log_nfa = log_nfa_new; + } + } + } + + if( log_nfa > log_eps ) return log_nfa; + + /* try to reduce the other side of the rectangle */ + rect_copy(rec,&r); + for(n=0; n<5; n++) + { + if( (r.width - delta) >= 0.5 ) + { + r.x1 -= -r.dy * delta_2; + r.y1 -= r.dx * delta_2; + r.x2 -= -r.dy * delta_2; + r.y2 -= r.dx * delta_2; + r.width -= delta; + log_nfa_new = rect_nfa(&r,angles,logNT); + if( log_nfa_new > log_nfa ) + { + rect_copy(&r,rec); + log_nfa = log_nfa_new; + } + } + } + + if( log_nfa > log_eps ) return log_nfa; + + /* try even finer precisions */ + rect_copy(rec,&r); + for(n=0; n<5; n++) + { + r.p /= 2.0; + r.prec = r.p * M_PI; + log_nfa_new = rect_nfa(&r,angles,logNT); + if( log_nfa_new > log_nfa ) + { + log_nfa = log_nfa_new; + rect_copy(&r,rec); + } + } + + return log_nfa; +} + +/*----------------------------------------------------------------------------*/ +/** Reduce the region size, by elimination the points far from the + starting point, until that leads to rectangle with the right + density of region points or to discard the region if too small. + */ +static int reduce_region_radius( struct lsd_point * reg, int * reg_size, + image_int modgrad, float reg_angle, + float prec, float p, struct rect * rec, + image_char used, image_int angles, + float density_th ) +{ + float density,rad1,rad2,rad,xc,yc; + int i; + + /* check parameters */ + if( reg == NULL ) error("reduce_region_radius: invalid pointer 'reg'."); + if( reg_size == NULL ) + error("reduce_region_radius: invalid pointer 'reg_size'."); + if( prec < 0.0 ) error("reduce_region_radius: 'prec' must be positive."); + if( rec == NULL ) error("reduce_region_radius: invalid pointer 'rec'."); + if( used == NULL || used->data == NULL ) + error("reduce_region_radius: invalid image 'used'."); + if( angles == NULL || angles->data == NULL ) + error("reduce_region_radius: invalid image 'angles'."); + + /* compute region points density */ + density = (float) *reg_size / + ( dist(rec->x1,rec->y1,rec->x2,rec->y2) * rec->width ); + + /* if the density criterion is satisfied there is nothing to do */ + if( density >= density_th ) return TRUE; + + /* compute region's radius */ + xc = (float) reg[0].x; + yc = (float) reg[0].y; + rad1 = dist( xc, yc, rec->x1, rec->y1 ); + rad2 = dist( xc, yc, rec->x2, rec->y2 ); + rad = rad1 > rad2 ? rad1 : rad2; + + /* while the density criterion is not satisfied, remove farther pixels */ + while( density < density_th ) + { + rad *= 0.75; /* reduce region's radius to 75% of its value */ + + /* remove points from the region and update 'used' map */ + for(i=0; i<*reg_size; i++) + if( dist( xc, yc, (float) reg[i].x, (float) reg[i].y ) > rad ) + { + /* point not kept, mark it as NOTUSED */ + used->data[ reg[i].x + reg[i].y * used->xsize ] = NOTUSED; + /* remove point from the region */ + reg[i].x = reg[*reg_size-1].x; /* if i==*reg_size-1 copy itself */ + reg[i].y = reg[*reg_size-1].y; + --(*reg_size); + --i; /* to avoid skipping one point */ + } + + /* reject if the region is too small. + 2 is the minimal region size for 'region2rect' to work. */ + if( *reg_size < 2 ) return FALSE; + + /* re-compute rectangle */ + region2rect(reg,*reg_size,modgrad,reg_angle,prec,p,rec); + + /* re-compute region points density */ + density = (float) *reg_size / + ( dist(rec->x1,rec->y1,rec->x2,rec->y2) * rec->width ); + } + + /* if this point is reached, the density criterion is satisfied */ + return TRUE; +} + +/*----------------------------------------------------------------------------*/ +/** Refine a rectangle. + + For that, an estimation of the angle tolerance is performed by the + standard deviation of the angle at points near the region's + starting point. Then, a new region is grown starting from the same + point, but using the estimated angle tolerance. If this fails to + produce a rectangle with the right density of region points, + 'reduce_region_radius' is called to try to satisfy this condition. + */ +static int refine( struct lsd_point * reg, int * reg_size, image_int modgrad, + float reg_angle, float prec, float p, struct rect * rec, + image_char used, image_int angles, float density_th ) +{ + float angle,ang_d,mean_angle,tau,density,xc,yc,ang_c,sum,s_sum; + int i,n; + + /* check parameters */ + if( reg == NULL ) error("refine: invalid pointer 'reg'."); + if( reg_size == NULL ) error("refine: invalid pointer 'reg_size'."); + if( prec < 0.0 ) error("refine: 'prec' must be positive."); + if( rec == NULL ) error("refine: invalid pointer 'rec'."); + if( used == NULL || used->data == NULL ) + error("refine: invalid image 'used'."); + if( angles == NULL || angles->data == NULL ) + error("refine: invalid image 'angles'."); + + /* compute region points density */ + density = (float) *reg_size / + ( dist(rec->x1,rec->y1,rec->x2,rec->y2) * rec->width ); + + /* if the density criterion is satisfied there is nothing to do */ + if( density >= density_th ) return TRUE; + + /*------ First try: reduce angle tolerance ------*/ + + /* compute the new mean angle and tolerance */ + xc = (float) reg[0].x; + yc = (float) reg[0].y; + ang_c = degToRad(angles->data[ reg[0].x + reg[0].y * angles->xsize ]); + sum = s_sum = 0.0; + n = 0; + for(i=0; i<*reg_size; i++) + { + used->data[ reg[i].x + reg[i].y * used->xsize ] = NOTUSED; + if( dist( xc, yc, (float) reg[i].x, (float) reg[i].y ) < rec->width ) + { + angle = degToRad(angles->data[ reg[i].x + reg[i].y * angles->xsize ]); + ang_d = angle_diff_signed(angle,ang_c); + sum += ang_d; + s_sum += ang_d * ang_d; + ++n; + } + } + mean_angle = sum / (float) n; + tau = 2.0 * sqrt( (s_sum - 2.0 * mean_angle * sum) / (float) n + + mean_angle*mean_angle ); /* 2 * standard deviation */ + + /* find a new region from the same starting point and new angle tolerance */ + region_grow(reg[0].x,reg[0].y,angles,reg,reg_size,®_angle,used,tau); + + /* if the region is too small, reject */ + if( *reg_size < 2 ) return FALSE; + + /* re-compute rectangle */ + region2rect(reg,*reg_size,modgrad,reg_angle,prec,p,rec); + + /* re-compute region points density */ + density = (float) *reg_size / + ( dist(rec->x1,rec->y1,rec->x2,rec->y2) * rec->width ); + + /*------ Second try: reduce region radius ------*/ + if( density < density_th ) + return reduce_region_radius( reg, reg_size, modgrad, reg_angle, prec, p, + rec, used, angles, density_th ); + + /* if this point is reached, the density criterion is satisfied */ + return TRUE; +} + + +/*----------------------------------------------------------------------------*/ +/*-------------------------- Line Segment Detector ---------------------------*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +/** LSD full interface. + */ +float * LineSegmentDetection( int * n_out, + unsigned char * img, int X, int Y, + float scale, float sigma_scale, float quant, + float ang_th, float log_eps, float density_th, + int n_bins, + int ** reg_img, int * reg_x, int * reg_y ) +{ + image_char image; + ntuple_list out = new_ntuple_list(7); + float * return_value; + image_int scaled_image,angles,modgrad; + image_char used; + image_int region = NULL; + struct coorlist * list_p; + void * mem_p; + struct rect rec; + struct lsd_point * reg; + int reg_size,min_reg_size,i; + unsigned int xsize,ysize; + float rho,reg_angle,prec,p,log_nfa,logNT; + int ls_count = 0; /* line segments are numbered 1,2,3,... */ + + + /* check parameters */ + if( img == NULL || X <= 0 || Y <= 0 ) error("invalid image input."); + if( scale <= 0.0 ) error("'scale' value must be positive."); + if( sigma_scale <= 0.0 ) error("'sigma_scale' value must be positive."); + if( quant < 0.0 ) error("'quant' value must be positive."); + if( ang_th <= 0.0 || ang_th >= 180.0 ) + error("'ang_th' value must be in the range (0,180)."); + if( density_th < 0.0 || density_th > 1.0 ) + error("'density_th' value must be in the range [0,1]."); + if( n_bins <= 0 ) error("'n_bins' value must be positive."); + + + /* angle tolerance */ + prec = M_PI * ang_th / 180.0; + p = ang_th / 180.0; + rho = quant / sin(prec); /* gradient magnitude threshold */ + + + /* load and scale image (if necessary) and compute angle at each pixel */ + image = new_image_char_ptr( (unsigned int) X, (unsigned int) Y, img ); +// if( scale != 1.0 ) +// { +// scaled_image = gaussian_sampler( image, scale, sigma_scale ); +// angles = ll_angle( scaled_image, rho, &list_p, &mem_p, +// &modgrad, (unsigned int) n_bins ); +// free_image_double(scaled_image); +// } +// else + angles = ll_angle( image, rho, &list_p, &mem_p, &modgrad, + (unsigned int) n_bins ); + xsize = angles->xsize; + ysize = angles->ysize; + + /* Number of Tests - NT + + The theoretical number of tests is Np.(XY)^(5/2) + where X and Y are number of columns and rows of the image. + Np corresponds to the number of angle precisions considered. + As the procedure 'rect_improve' tests 5 times to halve the + angle precision, and 5 more times after improving other factors, + 11 different precision values are potentially tested. Thus, + the number of tests is + 11 * (X*Y)^(5/2) + whose logarithm value is + log10(11) + 5/2 * (log10(X) + log10(Y)). + */ + logNT = 5.0 * ( log10( (float) xsize ) + log10( (float) ysize ) ) / 2.0 + + log10(11.0); + min_reg_size = (int) (-logNT/log10(p)); /* minimal number of points in region + that can give a meaningful event */ + + +// /* initialize some structures */ +// if( reg_img != NULL && reg_x != NULL && reg_y != NULL ) /* save region data */ +// region = new_image_int_ini(angles->xsize,angles->ysize,0); + used = new_image_char_ini(xsize,ysize,NOTUSED); + reg = (struct lsd_point *) calloc( (size_t) (xsize*ysize), sizeof(struct lsd_point) ); + if( reg == NULL ) error("not enough memory!"); + + + /* search for line segments */ + for(; list_p != NULL; list_p = list_p->next ) + if( used->data[ list_p->x + list_p->y * used->xsize ] == NOTUSED && + angles->data[ list_p->x + list_p->y * angles->xsize ] != NOTDEF_INT ) +// degToRad(angles->data[ list_p->x + list_p->y * angles->xsize ]) != NOTDEF ) + /* there is no risk of float comparison problems here + because we are only interested in the exact NOTDEF value */ + { + /* find the region of connected point and ~equal angle */ + region_grow( list_p->x, list_p->y, angles, reg, ®_size, + ®_angle, used, prec ); + + /* reject small regions */ + if( reg_size < min_reg_size ) continue; + + /* construct rectangular approximation for the region */ + region2rect(reg,reg_size,modgrad,reg_angle,prec,p,&rec); + + /* Check if the rectangle exceeds the minimal density of + region points. If not, try to improve the region. + The rectangle will be rejected if the final one does + not fulfill the minimal density condition. + This is an addition to the original LSD algorithm published in + "LSD: A Fast Line Segment Detector with a False Detection Control" + by R. Grompone von Gioi, J. Jakubowicz, J.M. Morel, and G. Randall. + The original algorithm is obtained with density_th = 0.0. + */ + if( !refine( reg, ®_size, modgrad, reg_angle, + prec, p, &rec, used, angles, density_th ) ) continue; + + /* compute NFA value */ + log_nfa = rect_improve(&rec,angles,logNT,log_eps); + if( log_nfa <= log_eps ) continue; + + /* A New Line Segment was found! */ + ++ls_count; /* increase line segment counter */ + + /* + The gradient was computed with a 2x2 mask, its value corresponds to + points with an offset of (0.5,0.5), that should be added to output. + The coordinates origin is at the center of pixel (0,0). + */ + rec.x1 += 0.5; rec.y1 += 0.5; + rec.x2 += 0.5; rec.y2 += 0.5; + + /* scale the result values if a subsampling was performed */ +// if( scale != 1.0 ) +// { +// rec.x1 /= scale; rec.y1 /= scale; +// rec.x2 /= scale; rec.y2 /= scale; +// rec.width /= scale; +// } + + /* add line segment found to output */ + add_7tuple( out, rec.x1, rec.y1, rec.x2, rec.y2, + rec.width, rec.p, log_nfa ); + +// /* add region number to 'region' image if needed */ +// if( region != NULL ) +// for(i=0; idata[ reg[i].x + reg[i].y * region->xsize ] = ls_count; + } + + + /* free memory */ + free( (void *) image ); /* only the char_image structure should be freed, + the data pointer was provided to this functions + and should not be destroyed. */ + free_image_int(angles); + free_image_int(modgrad); + free_image_char(used); + free( (void *) reg ); + free( (void *) mem_p ); + +// /* return the result */ +// if( reg_img != NULL && reg_x != NULL && reg_y != NULL ) +// { +// if( region == NULL ) error("'region' should be a valid image."); +// *reg_img = region->data; +// if( region->xsize > (unsigned int) INT_MAX || +// region->xsize > (unsigned int) INT_MAX ) +// error("region image to big to fit in INT sizes."); +// *reg_x = (int) (region->xsize); +// *reg_y = (int) (region->ysize); + +// /* free the 'region' structure. +// we cannot use the function 'free_image_int' because we need to keep +// the memory with the image data to be returned by this function. */ +// free( (void *) region ); +// } + if( out->size > (unsigned int) INT_MAX ) + error("too many detections to fit in an INT."); + *n_out = (int) (out->size); + + return_value = out->values; + free( (void *) out ); /* only the 'ntuple_list' structure must be freed, + but the 'values' pointer must be keep to return + as a result. */ + + return return_value; +} + +/*----------------------------------------------------------------------------*/ +/** LSD Simple Interface with Scale and Region output. + */ +float * lsd_scale_region( int * n_out, + unsigned char * img, int X, int Y, float scale, + int ** reg_img, int * reg_x, int * reg_y ) +{ + /* LSD parameters */ + float sigma_scale = 0.6; /* Sigma for Gaussian filter is computed as + sigma = sigma_scale/scale. */ + float quant = 2.0; /* Bound to the quantization error on the + gradient norm. */ + float ang_th = 22.5; /* Gradient angle tolerance in degrees. */ + float log_eps = 0.0; /* Detection threshold: -log10(NFA) > log_eps */ + float density_th = 0.7; /* Minimal density of region points in rectangle. */ + int n_bins = 1024; /* Number of bins in pseudo-ordering of gradient + modulus. */ + + return LineSegmentDetection( n_out, img, X, Y, scale, sigma_scale, quant, + ang_th, log_eps, density_th, n_bins, + reg_img, reg_x, reg_y ); +} + +/*----------------------------------------------------------------------------*/ +/** LSD Simple Interface with Scale. + */ +float * lsd_scale(int * n_out, unsigned char * img, int X, int Y, float scale) +{ + return lsd_scale_region(n_out,img,X,Y,scale,NULL,NULL,NULL); +} + +/*----------------------------------------------------------------------------*/ +/** LSD Simple Interface. + */ +float * lsd(int * n_out, unsigned char * img, int X, int Y) +{ + /* LSD parameters */ + float scale = 0.8; /* Scale the image by Gaussian filter to 'scale'. */ + + return lsd_scale(n_out,img,X,Y,scale); +} +/*----------------------------------------------------------------------------*/ + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void imlib_lsd_find_line_segments(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int merge_distance, unsigned int max_theta_diff) +{ + uint8_t *grayscale_image = fb_alloc(roi->w * roi->h, FB_ALLOC_NO_HINT); + + image_t img; + img.w = roi->w; + img.h = roi->h; + img.pixfmt = PIXFORMAT_GRAYSCALE; + img.data = grayscale_image; + img.size = img.w * img.h; + imlib_pixfmt_to(&img, ptr, roi); + // imlib_draw_image(&img, ptr, 0, 0, 1.f, 1.f, roi, -1, 256, NULL, NULL, 0, NULL, NULL); + + umm_init_x(fb_avail()); + + int n_ls; + float *ls = LineSegmentDetection(&n_ls, grayscale_image, roi->w, roi->h, 0.8, 0.6, 2.0, 22.5, 0.0, 0.7, 1024, NULL, NULL, NULL); + imlib_list_init(out, sizeof(find_lines_list_lnk_data_t)); + + for (int i = 0, j = n_ls; i < j; i++) { + find_lines_list_lnk_data_t lnk_line; + + lnk_line.line.x1 = fast_roundf(ls[(7*i)+0]); + lnk_line.line.y1 = fast_roundf(ls[(7*i)+1]); + lnk_line.line.x2 = fast_roundf(ls[(7*i)+2]); + lnk_line.line.y2 = fast_roundf(ls[(7*i)+3]); + + if(lb_clip_line(&lnk_line.line, 0, 0, roi->w, roi->h)) { + lnk_line.line.x1 += roi->x; + lnk_line.line.y1 += roi->y; + lnk_line.line.x2 += roi->x; + lnk_line.line.y2 += roi->y; + + int dx = lnk_line.line.x2 - lnk_line.line.x1, mdx = lnk_line.line.x1 + (dx/2); + int dy = lnk_line.line.y2 - lnk_line.line.y1, mdy = lnk_line.line.y1 + (dy/2); + float rotation = (dx ? fast_atan2f(dy, dx) : 1.570796f) + 1.570796f; // PI/2 + + lnk_line.theta = fast_roundf(rotation * 57.295780) % 180; // * (180 / PI) + if (lnk_line.theta < 0) lnk_line.theta += 180; + lnk_line.rho = fast_roundf((mdx * cos_table[lnk_line.theta]) + (mdy * sin_table[lnk_line.theta])); + + lnk_line.magnitude = fast_roundf(ls[(7*i)+6]); + + list_push_back(out, &lnk_line); + } + } + + if (merge_distance > 0) + { + merge_alot(out, merge_distance, max_theta_diff); + } + + fb_free(NULL); // umm_init_x(); + fb_free(grayscale_image); // grayscale_image; +} + +#pragma GCC diagnostic pop +#endif //IMLIB_ENABLE_FIND_LINE_SEGMENTS diff --git a/github_source/minicv2/src/mathop.c b/github_source/minicv2/src/mathop.c new file mode 100644 index 0000000..1bc8ded --- /dev/null +++ b/github_source/minicv2/src/mathop.c @@ -0,0 +1,1013 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Image math operations. + */ +#include "imlib.h" + +#ifdef IMLIB_ENABLE_MATH_OPS +void imlib_gamma_corr(image_t *img, float gamma, float contrast, float brightness) +{ + gamma = IM_DIV(1.0, gamma); + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + float pScale = COLOR_BINARY_MAX - COLOR_BINARY_MIN; + float pDiv = 1 / pScale; + int *p_lut = fb_alloc((COLOR_BINARY_MAX - COLOR_BINARY_MIN + 1) * sizeof(int), FB_ALLOC_NO_HINT); + + for (int i = COLOR_BINARY_MIN; i <= COLOR_BINARY_MAX; i++) { + int p = ((fast_powf(i * pDiv, gamma) * contrast) + brightness) * pScale; + p_lut[i] = IM_MIN(IM_MAX(p , COLOR_BINARY_MIN), COLOR_BINARY_MAX); + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int dataPixel = IMAGE_GET_BINARY_PIXEL_FAST(data, x); + int p = p_lut[dataPixel]; + IMAGE_PUT_BINARY_PIXEL_FAST(data, x, p); + } + } + + fb_free(p_lut); + break; + } + case PIXFORMAT_GRAYSCALE: { + float pScale = COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN; + float pDiv = 1 / pScale; + int *p_lut = fb_alloc((COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN + 1) * sizeof(int), FB_ALLOC_NO_HINT); + + for (int i = COLOR_GRAYSCALE_MIN; i <= COLOR_GRAYSCALE_MAX; i++) { + int p = ((fast_powf(i * pDiv, gamma) * contrast) + brightness) * pScale; + p_lut[i] = IM_MIN(IM_MAX(p , COLOR_GRAYSCALE_MIN), COLOR_GRAYSCALE_MAX); + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int dataPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, x); + int p = p_lut[dataPixel]; + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, x, p); + } + } + + fb_free(p_lut); + break; + } + case PIXFORMAT_RGB565: { + float rScale = COLOR_R5_MAX - COLOR_R5_MIN; + float gScale = COLOR_G6_MAX - COLOR_G6_MIN; + float bScale = COLOR_B5_MAX - COLOR_B5_MIN; + float rDiv = 1 / rScale; + float gDiv = 1 / gScale; + float bDiv = 1 / bScale; + int *r_lut = fb_alloc((COLOR_R5_MAX - COLOR_R5_MIN + 1) * sizeof(int), FB_ALLOC_NO_HINT); + int *g_lut = fb_alloc((COLOR_G6_MAX - COLOR_G6_MIN + 1) * sizeof(int), FB_ALLOC_NO_HINT); + int *b_lut = fb_alloc((COLOR_B5_MAX - COLOR_B5_MIN + 1) * sizeof(int), FB_ALLOC_NO_HINT); + + for (int i = COLOR_R5_MIN; i <= COLOR_R5_MAX; i++) { + int r = ((fast_powf(i * rDiv, gamma) * contrast) + brightness) * rScale; + r_lut[i] = IM_MIN(IM_MAX(r , COLOR_R5_MIN), COLOR_R5_MAX); + } + + for (int i = COLOR_G6_MIN; i <= COLOR_G6_MAX; i++) { + int g = ((fast_powf(i * gDiv, gamma) * contrast) + brightness) * gScale; + g_lut[i] = IM_MIN(IM_MAX(g , COLOR_G6_MIN), COLOR_G6_MAX); + } + + for (int i = COLOR_B5_MIN; i <= COLOR_B5_MAX; i++) { + int b = ((fast_powf(i * bDiv, gamma) * contrast) + brightness) * bScale; + b_lut[i] = IM_MIN(IM_MAX(b , COLOR_B5_MIN), COLOR_B5_MAX); + } + + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int dataPixel = IMAGE_GET_RGB565_PIXEL_FAST(data, x); + int r = r_lut[COLOR_RGB565_TO_R5(dataPixel)]; + int g = g_lut[COLOR_RGB565_TO_G6(dataPixel)]; + int b = b_lut[COLOR_RGB565_TO_B5(dataPixel)]; + IMAGE_PUT_RGB565_PIXEL_FAST(data, x, COLOR_R5_G6_B5_TO_RGB565(r, g, b)); + } + } + + fb_free(b_lut); + fb_free(g_lut); + fb_free(r_lut); + break; + } + case PIXFORMAT_RGB888: { + float rScale = COLOR_R8_MAX - COLOR_R8_MIN; + float gScale = COLOR_G8_MAX - COLOR_G8_MIN; + float bScale = COLOR_B8_MAX - COLOR_B8_MIN; + float rDiv = 1 / rScale; + float gDiv = 1 / gScale; + float bDiv = 1 / bScale; + int *r_lut = fb_alloc((COLOR_R8_MAX - COLOR_R8_MIN + 1) * sizeof(int), FB_ALLOC_NO_HINT); + int *g_lut = fb_alloc((COLOR_G8_MAX - COLOR_G8_MIN + 1) * sizeof(int), FB_ALLOC_NO_HINT); + int *b_lut = fb_alloc((COLOR_B8_MAX - COLOR_B8_MIN + 1) * sizeof(int), FB_ALLOC_NO_HINT); + + for (int i = COLOR_R8_MIN; i <= COLOR_R8_MAX; i++) { + int r = ((fast_powf(i * rDiv, gamma) * contrast) + brightness) * rScale; + r_lut[i] = IM_MIN(IM_MAX(r , COLOR_R8_MIN), COLOR_R8_MAX); + } + + for (int i = COLOR_G8_MIN; i <= COLOR_G8_MAX; i++) { + int g = ((fast_powf(i * gDiv, gamma) * contrast) + brightness) * gScale; + g_lut[i] = IM_MIN(IM_MAX(g , COLOR_G8_MIN), COLOR_G8_MAX); + } + + for (int i = COLOR_B8_MIN; i <= COLOR_B8_MAX; i++) { + int b = ((fast_powf(i * bDiv, gamma) * contrast) + brightness) * bScale; + b_lut[i] = IM_MIN(IM_MAX(b , COLOR_B8_MIN), COLOR_B8_MAX); + } + + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, x); + int r = r_lut[COLOR_RGB888_TO_R8(dataPixel)]; + int g = g_lut[COLOR_RGB888_TO_G8(dataPixel)]; + int b = b_lut[COLOR_RGB888_TO_B8(dataPixel)]; + IMAGE_PUT_RGB888_PIXEL_FAST(data, x, COLOR_R8_G8_B8_TO_RGB888(r, g, b)); + } + } + + fb_free(b_lut); + fb_free(g_lut); + fb_free(r_lut); + break; + } + default: { + break; + } + } +} + +void imlib_negate(image_t *img) +{ + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, y); + int x = 0, xx = img->w; + uint32_t *s = data; + for (; x < xx-31; x += 32) { // do it faster with bit access + s[0] = ~s[0]; // invert 32 bits (pixels) in one shot + s++; + } + for (; x < xx; x++) { + int dataPixel = IMAGE_GET_BINARY_PIXEL_FAST(data, x); + int p = (COLOR_BINARY_MAX - COLOR_BINARY_MIN) - dataPixel; + IMAGE_PUT_BINARY_PIXEL_FAST(data, x, p); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, y); + int x = 0, xx = img->w; + uint32_t a, b, *s = (uint32_t *)data; + for (; x < xx-7; x+= 8) { // process a pair of 4 pixels at a time + a = s[0]; b = s[1]; // read 8 pixels + s[0] = ~a; s[1] = ~b; + s += 2; + } + for (; x < xx; x++) { + int dataPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, x); + int p = (COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN) - dataPixel; + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, x, p); + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = 0, yy = img->h; y < yy; y++) { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int dataPixel = IMAGE_GET_RGB565_PIXEL_FAST(data, x); + IMAGE_PUT_RGB565_PIXEL_FAST(data, x, ~dataPixel); + } + } + break; + } + case PIXFORMAT_RGB888: { + for (int y = 0, yy = img->h; y < yy; y++) { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, y); + for (int x = 0, xx = img->w; x < xx; x++) { + int dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, x); + IMAGE_PUT_RGB888_PIXEL_FAST(data, x, ~dataPixel); + } + } + break; + } + default: { + break; + } + } +} + +typedef struct imlib_replace_line_op_state { + bool hmirror, vflip, transpose; + image_t *mask; +} imlib_replace_line_op_state_t; + +static void imlib_replace_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + bool hmirror = ((imlib_replace_line_op_state_t *) data)->hmirror; + bool vflip = ((imlib_replace_line_op_state_t *) data)->vflip; + bool transpose = ((imlib_replace_line_op_state_t *) data)->transpose; + image_t *mask = ((imlib_replace_line_op_state_t *) data)->mask; + + image_t target; + memcpy(&target, img, sizeof(image_t)); + + if (transpose) { + int w = target.w; + int h = target.h; + target.w = h; + target.h = w; + } + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + int v_line = vflip ? (img->h - line - 1) : line; + for (int i = 0, j = img->w; i < j; i++) { + int h_i = hmirror ? (img->w - i - 1) : i; + + if ((!mask) || image_get_mask_pixel(mask, h_i, v_line)) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), h_i); + IMAGE_PUT_BINARY_PIXEL(&target, transpose ? v_line : i, transpose ? i : v_line, pixel); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + int v_line = vflip ? (img->h - line - 1) : line; + for (int i = 0, j = img->w; i < j; i++) { + int h_i = hmirror ? (img->w - i - 1) : i; + + if ((!mask) || image_get_mask_pixel(mask, h_i, v_line)) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), h_i); + IMAGE_PUT_GRAYSCALE_PIXEL(&target, transpose ? v_line : i, transpose ? i : v_line, pixel); + } + } + break; + } + case PIXFORMAT_RGB565: { + int v_line = vflip ? (img->h - line - 1) : line; + for (int i = 0, j = img->w; i < j; i++) { + int h_i = hmirror ? (img->w - i - 1) : i; + + if ((!mask) || image_get_mask_pixel(mask, h_i, v_line)) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), h_i); + IMAGE_PUT_RGB565_PIXEL(&target, transpose ? v_line : i, transpose ? i : v_line, pixel); + } + } + break; + } + case PIXFORMAT_RGB888: { + int v_line = vflip ? (img->h - line - 1) : line; + for (int i = 0, j = img->w; i < j; i++) { + int h_i = hmirror ? (img->w - i - 1) : i; + + if ((!mask) || image_get_mask_pixel(mask, h_i, v_line)) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), h_i); + IMAGE_PUT_RGB888_PIXEL(&target, transpose ? v_line : i, transpose ? i : v_line, pixel); + } + } + break; + } + default: { + break; + } + } +} + +void imlib_replace(image_t *img, const char *path, image_t *other, int scalar, bool hmirror, bool vflip, bool transpose, image_t *mask) +{ + bool in_place = img->data == other->data; + image_t temp; + + if (in_place) { + memcpy(&temp, other, sizeof(image_t)); + temp.data = fb_alloc(image_size(&temp), FB_ALLOC_NO_HINT); + memcpy(temp.data, other->data, image_size(&temp)); + other = &temp; + } + + imlib_replace_line_op_state_t state; + state.hmirror = hmirror; + state.vflip = vflip; + state.mask = mask; + state.transpose = transpose; + imlib_image_operation(img, path, other, scalar, imlib_replace_line_op, &state); + + if (in_place) { + fb_free(temp.data); + } + + if (transpose) { + int w = img->w; + int h = img->h; + img->w = h; + img->h = w; + } +} + +static void imlib_add_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + image_t *mask = (image_t *) data; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_BINARY_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i); + int p = dataPixel | otherPixel; //dataPixel + otherPixel; +// p = IM_MIN(p, COLOR_BINARY_MAX); + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i); + int p = dataPixel + otherPixel; + p = IM_MIN(p, COLOR_GRAYSCALE_MAX); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB565_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i); + int r = COLOR_RGB565_TO_R5(dataPixel) + COLOR_RGB565_TO_R5(otherPixel); + int g = COLOR_RGB565_TO_G6(dataPixel) + COLOR_RGB565_TO_G6(otherPixel); + int b = COLOR_RGB565_TO_B5(dataPixel) + COLOR_RGB565_TO_B5(otherPixel); + r = IM_MIN(r, COLOR_R5_MAX); + g = IM_MIN(g, COLOR_G6_MAX); + b = IM_MIN(b, COLOR_B5_MAX); + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, COLOR_R5_G6_B5_TO_RGB565(r, g, b)); + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i); + int r = COLOR_RGB888_TO_R8(dataPixel) + COLOR_RGB888_TO_R8(otherPixel); + int g = COLOR_RGB888_TO_G8(dataPixel) + COLOR_RGB888_TO_G8(otherPixel); + int b = COLOR_RGB888_TO_B8(dataPixel) + COLOR_RGB888_TO_B8(otherPixel); + r = IM_MIN(r, COLOR_R8_MAX); + g = IM_MIN(g, COLOR_G8_MAX); + b = IM_MIN(b, COLOR_B8_MAX); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888(r, g, b)); + } + } + break; + } + default: { + break; + } + } +} + +void imlib_add(image_t *img, const char *path, image_t *other, int scalar, image_t *mask) +{ + imlib_image_operation(img, path, other, scalar, imlib_add_line_op, mask); +} + +typedef struct imlib_sub_line_op_state { + bool reverse; + image_t *mask; +} imlib_sub_line_op_state_t; + +static void imlib_sub_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + bool reverse = ((imlib_sub_line_op_state_t *) data)->reverse; + image_t *mask = ((imlib_sub_line_op_state_t *) data)->mask; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_BINARY_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i); + int p = reverse ? (otherPixel - dataPixel) : (dataPixel - otherPixel); + p = IM_MAX(p, COLOR_BINARY_MIN); + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i); + int p = reverse ? (otherPixel - dataPixel) : (dataPixel - otherPixel); + p = IM_MAX(p, COLOR_GRAYSCALE_MIN); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB565_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i); + int dR = COLOR_RGB565_TO_R5(dataPixel); + int dG = COLOR_RGB565_TO_G6(dataPixel); + int dB = COLOR_RGB565_TO_B5(dataPixel); + int oR = COLOR_RGB565_TO_R5(otherPixel); + int oG = COLOR_RGB565_TO_G6(otherPixel); + int oB = COLOR_RGB565_TO_B5(otherPixel); + int r = reverse ? (oR - dR) : (dR - oR); + int g = reverse ? (oG - dG) : (dG - oG); + int b = reverse ? (oB - dB) : (dB - oB); + r = IM_MAX(r, COLOR_R5_MIN); + g = IM_MAX(g, COLOR_G6_MIN); + b = IM_MAX(b, COLOR_B5_MIN); + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, COLOR_R5_G6_B5_TO_RGB565(r, g, b)); + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i); + int dR = COLOR_RGB888_TO_R8(dataPixel); + int dG = COLOR_RGB888_TO_G8(dataPixel); + int dB = COLOR_RGB888_TO_B8(dataPixel); + int oR = COLOR_RGB888_TO_R8(otherPixel); + int oG = COLOR_RGB888_TO_G8(otherPixel); + int oB = COLOR_RGB888_TO_B8(otherPixel); + int r = reverse ? (oR - dR) : (dR - oR); + int g = reverse ? (oG - dG) : (dG - oG); + int b = reverse ? (oB - dB) : (dB - oB); + r = IM_MAX(r, COLOR_R8_MIN); + g = IM_MAX(g, COLOR_G8_MIN); + b = IM_MAX(b, COLOR_B8_MIN); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888(r, g, b)); + } + } + break; + } + default: { + break; + } + } +} + +void imlib_sub(image_t *img, const char *path, image_t *other, int scalar, bool reverse, image_t *mask) +{ + imlib_sub_line_op_state_t state; + state.reverse = reverse; + state.mask = mask; + imlib_image_operation(img, path, other, scalar, imlib_sub_line_op, &state); +} + +typedef struct imlib_mul_line_op_state { + bool invert; + image_t *mask; +} imlib_mul_line_op_state_t; + +static void imlib_mul_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + bool invert = ((imlib_mul_line_op_state_t *) data)->invert; + image_t *mask = ((imlib_mul_line_op_state_t *) data)->mask; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + float pScale = COLOR_BINARY_MAX - COLOR_BINARY_MIN; + float pDiv = 1 / pScale; + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_BINARY_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i); + int p = invert ? (pScale - ((pScale - dataPixel) * (pScale - otherPixel) * pDiv)) + : (dataPixel * otherPixel * pDiv); + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + float pScale = COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN; + float pDiv = 1 / pScale; + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i); + int p = invert ? (pScale - ((pScale - dataPixel) * (pScale - otherPixel) * pDiv)) + : (dataPixel * otherPixel * pDiv); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + float rScale = COLOR_R5_MAX - COLOR_R5_MIN; + float gScale = COLOR_G6_MAX - COLOR_G6_MIN; + float bScale = COLOR_B5_MAX - COLOR_B5_MIN; + float rDiv = 1 / rScale; + float gDiv = 1 / gScale; + float bDiv = 1 / bScale; + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB565_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i); + int dR = COLOR_RGB565_TO_R5(dataPixel); + int dG = COLOR_RGB565_TO_G6(dataPixel); + int dB = COLOR_RGB565_TO_B5(dataPixel); + int oR = COLOR_RGB565_TO_R5(otherPixel); + int oG = COLOR_RGB565_TO_G6(otherPixel); + int oB = COLOR_RGB565_TO_B5(otherPixel); + int r = invert ? (rScale - ((rScale - dR) * (rScale - oR) * rDiv)) + : (dR * oR * rDiv); + int g = invert ? (gScale - ((gScale - dG) * (gScale - oG) * gDiv)) + : (dG * oG * gDiv); + int b = invert ? (bScale - ((bScale - dB) * (bScale - oB) * bDiv)) + : (dB * oB * bDiv); + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, COLOR_R5_G6_B5_TO_RGB565(r, g, b)); + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + float rScale = COLOR_R8_MAX - COLOR_R8_MIN; + float gScale = COLOR_G8_MAX - COLOR_G8_MIN; + float bScale = COLOR_B8_MAX - COLOR_B8_MIN; + float rDiv = 1 / rScale; + float gDiv = 1 / gScale; + float bDiv = 1 / bScale; + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i); + int dR = COLOR_RGB888_TO_R8(dataPixel); + int dG = COLOR_RGB888_TO_G8(dataPixel); + int dB = COLOR_RGB888_TO_B8(dataPixel); + int oR = COLOR_RGB888_TO_R8(otherPixel); + int oG = COLOR_RGB888_TO_G8(otherPixel); + int oB = COLOR_RGB888_TO_B8(otherPixel); + int r = invert ? (rScale - ((rScale - dR) * (rScale - oR) * rDiv)) + : (dR * oR * rDiv); + int g = invert ? (gScale - ((gScale - dG) * (gScale - oG) * gDiv)) + : (dG * oG * gDiv); + int b = invert ? (bScale - ((bScale - dB) * (bScale - oB) * bDiv)) + : (dB * oB * bDiv); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888(r, g, b)); + } + } + break; + } + default: { + break; + } + } +} + +void imlib_mul(image_t *img, const char *path, image_t *other, int scalar, bool invert, image_t *mask) +{ + imlib_mul_line_op_state_t state; + state.invert = invert; + state.mask = mask; + imlib_image_operation(img, path, other, scalar, imlib_mul_line_op, &state); +} + +typedef struct imlib_div_line_op_state { + bool invert, mod; + image_t *mask; +} imlib_div_line_op_state_t; + +static void imlib_div_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + bool invert = ((imlib_div_line_op_state_t *) data)->invert; + bool mod = ((imlib_div_line_op_state_t *) data)->mod; + image_t *mask = ((imlib_div_line_op_state_t *) data)->mask; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + int pScale = COLOR_BINARY_MAX - COLOR_BINARY_MIN; + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_BINARY_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i); + int p = mod + ? IM_MOD((invert?otherPixel:dataPixel) * pScale, (invert?dataPixel:otherPixel)) + : IM_DIV((invert?otherPixel:dataPixel) * pScale, (invert?dataPixel:otherPixel)); + p = IM_MIN(p, COLOR_BINARY_MAX); + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + int pScale = COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN; + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i); + int p = mod + ? IM_MOD((invert?otherPixel:dataPixel) * pScale, (invert?dataPixel:otherPixel)) + : IM_DIV((invert?otherPixel:dataPixel) * pScale, (invert?dataPixel:otherPixel)); + p = IM_MIN(p, COLOR_GRAYSCALE_MAX); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + int rScale = COLOR_R5_MAX - COLOR_R5_MIN; + int gScale = COLOR_G6_MAX - COLOR_G6_MIN; + int bScale = COLOR_B5_MAX - COLOR_B5_MIN; + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB565_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i); + int dR = COLOR_RGB565_TO_R5(dataPixel); + int dG = COLOR_RGB565_TO_G6(dataPixel); + int dB = COLOR_RGB565_TO_B5(dataPixel); + int oR = COLOR_RGB565_TO_R5(otherPixel); + int oG = COLOR_RGB565_TO_G6(otherPixel); + int oB = COLOR_RGB565_TO_B5(otherPixel); + int r = mod + ? IM_MOD((invert?oR:dR) * rScale, (invert?dR:oR)) + : IM_DIV((invert?oR:dR) * rScale, (invert?dR:oR)); + int g = mod + ? IM_MOD((invert?oG:dG) * gScale, (invert?dG:oG)) + : IM_DIV((invert?oG:dG) * gScale, (invert?dG:oG)); + int b = mod + ? IM_MOD((invert?oB:dB) * bScale, (invert?dB:oB)) + : IM_DIV((invert?oB:dB) * bScale, (invert?dB:oB)); + r = IM_MIN(r, COLOR_R5_MAX); + g = IM_MIN(g, COLOR_G6_MAX); + b = IM_MIN(b, COLOR_B5_MAX); + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, COLOR_R5_G6_B5_TO_RGB565(r, g, b)); + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + int rScale = COLOR_R8_MAX - COLOR_R8_MIN; + int gScale = COLOR_G8_MAX - COLOR_G8_MIN; + int bScale = COLOR_B8_MAX - COLOR_B8_MIN; + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i); + int dR = COLOR_RGB888_TO_R8(dataPixel); + int dG = COLOR_RGB888_TO_G8(dataPixel); + int dB = COLOR_RGB888_TO_B8(dataPixel); + int oR = COLOR_RGB888_TO_R8(otherPixel); + int oG = COLOR_RGB888_TO_G8(otherPixel); + int oB = COLOR_RGB888_TO_B8(otherPixel); + int r = mod + ? IM_MOD((invert?oR:dR) * rScale, (invert?dR:oR)) + : IM_DIV((invert?oR:dR) * rScale, (invert?dR:oR)); + int g = mod + ? IM_MOD((invert?oG:dG) * gScale, (invert?dG:oG)) + : IM_DIV((invert?oG:dG) * gScale, (invert?dG:oG)); + int b = mod + ? IM_MOD((invert?oB:dB) * bScale, (invert?dB:oB)) + : IM_DIV((invert?oB:dB) * bScale, (invert?dB:oB)); + r = IM_MIN(r, COLOR_R8_MAX); + g = IM_MIN(g, COLOR_G8_MAX); + b = IM_MIN(b, COLOR_B8_MAX); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888(r, g, b)); + } + } + break; + } + default: { + break; + } + } +} + +void imlib_div(image_t *img, const char *path, image_t *other, int scalar, bool invert, bool mod, image_t *mask) +{ + imlib_div_line_op_state_t state; + state.invert = invert; + state.mod = mod; + state.mask = mask; + imlib_image_operation(img, path, other, scalar, imlib_div_line_op, &state); +} + +static void imlib_min_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + image_t *mask = (image_t *) data; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_BINARY_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i); + int p = IM_MIN(dataPixel, otherPixel); + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i); + int p = IM_MIN(dataPixel, otherPixel); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB565_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i); + int r = IM_MIN(COLOR_RGB565_TO_R5(dataPixel), COLOR_RGB565_TO_R5(otherPixel)); + int g = IM_MIN(COLOR_RGB565_TO_G6(dataPixel), COLOR_RGB565_TO_G6(otherPixel)); + int b = IM_MIN(COLOR_RGB565_TO_B5(dataPixel), COLOR_RGB565_TO_B5(otherPixel)); + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, COLOR_R5_G6_B5_TO_RGB565(r, g, b)); + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i); + int r = IM_MIN(COLOR_RGB888_TO_R8(dataPixel), COLOR_RGB888_TO_R8(otherPixel)); + int g = IM_MIN(COLOR_RGB888_TO_G8(dataPixel), COLOR_RGB888_TO_G8(otherPixel)); + int b = IM_MIN(COLOR_RGB888_TO_B8(dataPixel), COLOR_RGB888_TO_B8(otherPixel)); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888(r, g, b)); + } + } + break; + } + default: { + break; + } + } +} + +void imlib_min(image_t *img, const char *path, image_t *other, int scalar, image_t *mask) +{ + imlib_image_operation(img, path, other, scalar, imlib_min_line_op, mask); +} + +static void imlib_max_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + image_t *mask = (image_t *) data; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_BINARY_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i); + int p = IM_MAX(dataPixel, otherPixel); + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i); + int p = IM_MAX(dataPixel, otherPixel); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB565_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i); + int r = IM_MAX(COLOR_RGB565_TO_R5(dataPixel), COLOR_RGB565_TO_R5(otherPixel)); + int g = IM_MAX(COLOR_RGB565_TO_G6(dataPixel), COLOR_RGB565_TO_G6(otherPixel)); + int b = IM_MAX(COLOR_RGB565_TO_B5(dataPixel), COLOR_RGB565_TO_B5(otherPixel)); + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, COLOR_R5_G6_B5_TO_RGB565(r, g, b)); + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i); + int r = IM_MAX(COLOR_RGB888_TO_R8(dataPixel), COLOR_RGB888_TO_R8(otherPixel)); + int g = IM_MAX(COLOR_RGB888_TO_G8(dataPixel), COLOR_RGB888_TO_G8(otherPixel)); + int b = IM_MAX(COLOR_RGB888_TO_B8(dataPixel), COLOR_RGB888_TO_B8(otherPixel)); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888(r, g, b)); + } + } + break; + } + default: { + break; + } + } +} + +void imlib_max(image_t *img, const char *path, image_t *other, int scalar, image_t *mask) +{ + imlib_image_operation(img, path, other, scalar, imlib_max_line_op, mask); +} + +static void imlib_difference_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + image_t *mask = (image_t *) data; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_BINARY_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i); + int p = dataPixel ^ otherPixel; // abs(dataPixel - otherPixel); + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i); + int p = abs(dataPixel - otherPixel); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB565_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i); + int r = abs(COLOR_RGB565_TO_R5(dataPixel) - COLOR_RGB565_TO_R5(otherPixel)); + int g = abs(COLOR_RGB565_TO_G6(dataPixel) - COLOR_RGB565_TO_G6(otherPixel)); + int b = abs(COLOR_RGB565_TO_B5(dataPixel) - COLOR_RGB565_TO_B5(otherPixel)); + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, COLOR_R5_G6_B5_TO_RGB565(r, g, b)); + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i); + int r = abs(COLOR_RGB888_TO_R8(dataPixel) - COLOR_RGB888_TO_R8(otherPixel)); + int g = abs(COLOR_RGB888_TO_G8(dataPixel) - COLOR_RGB888_TO_G8(otherPixel)); + int b = abs(COLOR_RGB888_TO_B8(dataPixel) - COLOR_RGB888_TO_B8(otherPixel)); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888(r, g, b)); + } + } + break; + } + default: { + break; + } + } +} + +void imlib_difference(image_t *img, const char *path, image_t *other, int scalar, image_t *mask) +{ + imlib_image_operation(img, path, other, scalar,imlib_difference_line_op, mask); +} + +typedef struct imlib_blend_line_op_state { + float alpha; + image_t *mask; +} imlib_blend_line_op_t; + +static void imlib_blend_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + float alpha = ((imlib_blend_line_op_t *) data)->alpha, beta = 1 - alpha; + image_t *mask = ((imlib_blend_line_op_t *) data)->mask; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *data = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_BINARY_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_BINARY_PIXEL_FAST(((uint32_t *) other), i); + int p = (dataPixel * alpha) + (otherPixel * beta); + IMAGE_PUT_BINARY_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *data = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(((uint8_t *) other), i); + int p = (dataPixel * alpha) + (otherPixel * beta); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(data, i, p); + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *data = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB565_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB565_PIXEL_FAST(((uint16_t *) other), i); + int r = (COLOR_RGB565_TO_R5(dataPixel) * alpha) + (COLOR_RGB565_TO_R5(otherPixel) * beta); + int g = (COLOR_RGB565_TO_G6(dataPixel) * alpha) + (COLOR_RGB565_TO_G6(otherPixel) * beta); + int b = (COLOR_RGB565_TO_B5(dataPixel) * alpha) + (COLOR_RGB565_TO_B5(otherPixel) * beta); + IMAGE_PUT_RGB565_PIXEL_FAST(data, i, COLOR_R5_G6_B5_TO_RGB565(r, g, b)); + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *data = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + for (int i = 0, j = img->w; i < j; i++) { + if ((!mask) || image_get_mask_pixel(mask, i, line)) { + int dataPixel = IMAGE_GET_RGB888_PIXEL_FAST(data, i); + int otherPixel = IMAGE_GET_RGB888_PIXEL_FAST(((pixel24_t *) other), i); + int r = (COLOR_RGB888_TO_R8(dataPixel) * alpha) + (COLOR_RGB888_TO_R8(otherPixel) * beta); + int g = (COLOR_RGB888_TO_G8(dataPixel) * alpha) + (COLOR_RGB888_TO_G8(otherPixel) * beta); + int b = (COLOR_RGB888_TO_B8(dataPixel) * alpha) + (COLOR_RGB888_TO_B8(otherPixel) * beta); + IMAGE_PUT_RGB888_PIXEL_FAST(data, i, COLOR_R8_G8_B8_TO_RGB888(r, g, b)); + } + } + break; + } + default: { + break; + } + } +} + +void imlib_blend(image_t *img, const char *path, image_t *other, int scalar, float alpha, image_t *mask) +{ + imlib_blend_line_op_t state; + state.alpha = alpha; + state.mask = mask; + imlib_image_operation(img, path, other, scalar, imlib_blend_line_op, &state); +} +#endif //IMLIB_ENABLE_MATH_OPS diff --git a/github_source/minicv2/src/mjpeg.c b/github_source/minicv2/src/mjpeg.c new file mode 100644 index 0000000..d5db05c --- /dev/null +++ b/github_source/minicv2/src/mjpeg.c @@ -0,0 +1,152 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * A simple MJPEG encoder. + */ +#include "imlib.h" +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + +#include "fb_alloc.h" +#include "ff_wrapper.h" + +#define SIZE_OFFSET (1*4) +#define MICROS_OFFSET (8*4) +#define FRAMES_OFFSET (12*4) +#define RATE_0_OFFSET (19*4) +#define LENGTH_0_OFFSET (21*4) +#define RATE_1_OFFSET (33*4) +#define LENGTH_1_OFFSET (35*4) +#define MOVI_OFFSET (54*4) + +void mjpeg_open(FIL *fp, int width, int height) +{ + write_data(fp, "RIFF", 4); // FOURCC fcc; - 0 + write_long(fp, 0); // DWORD cb; size - updated on close - 1 + write_data(fp, "AVI ", 4); // FOURCC fcc; - 2 + + write_data(fp, "LIST", 4); // FOURCC fcc; - 3 + write_long(fp, 192); // DWORD cb; - 4 + write_data(fp, "hdrl", 4); // FOURCC fcc; - 5 + + write_data(fp, "avih", 4); // FOURCC fcc; - 6 + write_long(fp, 56); // DWORD cb; - 7 + write_long(fp, 0); // DWORD dwMicroSecPerFrame; micros - updated on close - 8 + write_long(fp, 0); // DWORD dwMaxBytesPerSec; updated on close - 9 + write_long(fp, 4); // DWORD dwPaddingGranularity; - 10 + write_long(fp, 0); // DWORD dwFlags; - 11 + write_long(fp, 0); // DWORD dwTotalFrames; frames - updated on close - 12 + write_long(fp, 0); // DWORD dwInitialFrames; - 13 + write_long(fp, 1); // DWORD dwStreams; - 14 + write_long(fp, 0); // DWORD dwSuggestedBufferSize; - 15 + write_long(fp, width); // DWORD dwWidth; - 16 + write_long(fp, height); // DWORD dwHeight; - 17 + write_long(fp, 1000); // DWORD dwScale; - 18 + write_long(fp, 0); // DWORD dwRate; rate - updated on close - 19 + write_long(fp, 0); // DWORD dwStart; - 20 + write_long(fp, 0); // DWORD dwLength; length - updated on close - 21 + + write_data(fp, "LIST", 4); // FOURCC fcc; - 22 + write_long(fp, 116); // DWORD cb; - 23 + write_data(fp, "strl", 4); // FOURCC fcc; - 24 + + write_data(fp, "strh", 4); // FOURCC fcc; - 25 + write_long(fp, 56); // DWORD cb; - 26 + write_data(fp, "vids", 4); // FOURCC fccType; - 27 + write_data(fp, "MJPG", 4); // FOURCC fccHandler; - 28 + write_long(fp, 0); // DWORD dwFlags; - 29 + write_word(fp, 0); // WORD wPriority; - 30 + write_word(fp, 0); // WORD wLanguage; - 30.5 + write_long(fp, 0); // DWORD dwInitialFrames; - 31 + write_long(fp, 1000); // DWORD dwScale; - 32 + write_long(fp, 0); // DWORD dwRate; rate - updated on close - 33 + write_long(fp, 0); // DWORD dwStart; - 34 + write_long(fp, 0); // DWORD dwLength; length - updated on close - 35 + write_long(fp, 0); // DWORD dwSuggestedBufferSize; - 36 + write_long(fp, 10000); // DWORD dwQuality; - 37 + write_long(fp, 0); // DWORD dwSampleSize; - 38 + write_word(fp, 0); // short int left; - 39 + write_word(fp, 0); // short int top; - 39.5 + write_word(fp, 0); // short int right; - 40 + write_word(fp, 0); // short int bottom; - 40.5 + + write_data(fp, "strf", 4); // FOURCC fcc; - 41 + write_long(fp, 40); // DWORD cb; - 42 + write_long(fp, 40); // DWORD biSize; - 43 + write_long(fp, width); // LONG biWidth; - 44 + write_long(fp, height); // LONG biHeight; - 45 + write_word(fp, 1); // WORD biPlanes; - 46 + write_word(fp, 24); // WORD biBitCount; - 46.5 + write_data(fp, "MJPG", 4); // DWORD biCompression; - 47 + write_long(fp, 0); // DWORD biSizeImage; - 48 + write_long(fp, 0); // LONG biXPelsPerMeter; - 49 + write_long(fp, 0); // LONG biYPelsPerMeter; - 50 + write_long(fp, 0); // DWORD biClrUsed; - 51 + write_long(fp, 0); // DWORD biClrImportant; - 52 + + write_data(fp, "LIST", 4); // FOURCC fcc; - 53 + write_long(fp, 0); // DWORD cb; movi - updated on close - 54 + write_data(fp, "movi", 4); // FOURCC fcc; - 55 +} + +void mjpeg_add_frame(FIL *fp, uint32_t *frames, uint32_t *bytes, image_t *img, int quality) +{ + write_data(fp, "00dc", 4); // FOURCC fcc; + *frames += 1; + if (IM_IS_JPEG(img)) { + uint32_t size_padded = (((img->size + 3) / 4) * 4); + write_long(fp, size_padded); // DWORD cb; + write_data(fp, img->pixels, size_padded); // reading past okay + *bytes += size_padded; + } else { + image_t out = { .w=img->w, .h=img->h, .pixfmt=PIXFORMAT_JPEG, .size=0, .pixels=NULL }; + // When jpeg_compress needs more memory than in currently allocated it + // will try to realloc. MP will detect that the pointer is outside of + // the heap and return NULL which will cause an out of memory error. + jpeg_compress(img, &out, quality, true); + uint32_t size_padded = (((out.size + 3) / 4) * 4); + write_long(fp, size_padded); // DWORD cb; + write_data(fp, out.pixels, size_padded); // reading past okay + *bytes += size_padded; + fb_free(NULL); // frees alloc in jpeg_compress() + } +} + +void mjpeg_close(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps) +{ + // Needed + file_seek(fp, SIZE_OFFSET); + write_long(fp, 216 + (*frames * 8) + *bytes); + // Needed + file_seek(fp, MICROS_OFFSET); + write_long(fp, (!fast_roundf(fps)) ? 0 : + fast_roundf(1000000 / fps)); + write_long(fp, (!(*frames)) ? 0 : + fast_roundf((((*frames * 8) + *bytes) * fps) / *frames)); + // Needed + file_seek(fp, FRAMES_OFFSET); + write_long(fp, *frames); + // Probably not needed but writing it just in case. + file_seek(fp, RATE_0_OFFSET); + write_long(fp, fast_roundf(fps * 1000)); + // Probably not needed but writing it just in case. + file_seek(fp, LENGTH_0_OFFSET); + write_long(fp, (!fast_roundf(fps)) ? 0 : + fast_roundf((*frames * 1000) / fps)); + // Probably not needed but writing it just in case. + file_seek(fp, RATE_1_OFFSET); + write_long(fp, fast_roundf(fps * 1000)); + // Probably not needed but writing it just in case. + file_seek(fp, LENGTH_1_OFFSET); + write_long(fp, (!fast_roundf(fps)) ? 0 : + fast_roundf((*frames * 1000) / fps)); + // Needed + file_seek(fp, MOVI_OFFSET); + write_long(fp, 4 + (*frames * 8) + *bytes); + file_close(fp); +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO diff --git a/github_source/minicv2/src/orb.c b/github_source/minicv2/src/orb.c new file mode 100644 index 0000000..f24ccdc --- /dev/null +++ b/github_source/minicv2/src/orb.c @@ -0,0 +1,846 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * ORB keypoints descriptor based on OpenCV ORB detector. + * Software License Agreement (BSD License) + * + * Copyright (c) 2009, Willow Garage, Inc. 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 name of the Willow Garage nor the names of its + * 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. + */ +#include +#include +#include +#include +#include "fmath.h" +// #include "arm_math.h" +// #include "ff.h" +#include "imlib.h" +#include "xalloc.h" +#include "fb_alloc.h" +#include "ff_wrapper.h" +#ifdef IMLIB_ENABLE_FIND_KEYPOINTS + +#define PATCH_SIZE (31) // 31x31 pixels +#define KDESC_SIZE (32) // 32 bytes +#define MAX_KP_DIST (KDESC_SIZE*8) + +typedef struct { + int x; + int y; +} sample_point_t; + +const static int u_max[] = { + 15, 15, 15, 15, 14, 14, 14, 13, + 13, 12, 11, 10, 9, 8, 6, 3, 0 +}; + +const static int sample_pattern[256*4] = { + 8,-3, 9,5/*mean (0), correlation (0)*/, + 4,2, 7,-12/*mean (1.12461e-05), correlation (0.0437584)*/, + -11,9, -8,2/*mean (3.37382e-05), correlation (0.0617409)*/, + 7,-12, 12,-13/*mean (5.62303e-05), correlation (0.0636977)*/, + 2,-13, 2,12/*mean (0.000134953), correlation (0.085099)*/, + 1,-7, 1,6/*mean (0.000528565), correlation (0.0857175)*/, + -2,-10, -2,-4/*mean (0.0188821), correlation (0.0985774)*/, + -13,-13, -11,-8/*mean (0.0363135), correlation (0.0899616)*/, + -13,-3, -12,-9/*mean (0.121806), correlation (0.099849)*/, + 10,4, 11,9/*mean (0.122065), correlation (0.093285)*/, + -13,-8, -8,-9/*mean (0.162787), correlation (0.0942748)*/, + -11,7, -9,12/*mean (0.21561), correlation (0.0974438)*/, + 7,7, 12,6/*mean (0.160583), correlation (0.130064)*/, + -4,-5, -3,0/*mean (0.228171), correlation (0.132998)*/, + -13,2, -12,-3/*mean (0.00997526), correlation (0.145926)*/, + -9,0, -7,5/*mean (0.198234), correlation (0.143636)*/, + 12,-6, 12,-1/*mean (0.0676226), correlation (0.16689)*/, + -3,6, -2,12/*mean (0.166847), correlation (0.171682)*/, + -6,-13, -4,-8/*mean (0.101215), correlation (0.179716)*/, + 11,-13, 12,-8/*mean (0.200641), correlation (0.192279)*/, + 4,7, 5,1/*mean (0.205106), correlation (0.186848)*/, + 5,-3, 10,-3/*mean (0.234908), correlation (0.192319)*/, + 3,-7, 6,12/*mean (0.0709964), correlation (0.210872)*/, + -8,-7, -6,-2/*mean (0.0939834), correlation (0.212589)*/, + -2,11, -1,-10/*mean (0.127778), correlation (0.20866)*/, + -13,12, -8,10/*mean (0.14783), correlation (0.206356)*/, + -7,3, -5,-3/*mean (0.182141), correlation (0.198942)*/, + -4,2, -3,7/*mean (0.188237), correlation (0.21384)*/, + -10,-12, -6,11/*mean (0.14865), correlation (0.23571)*/, + 5,-12, 6,-7/*mean (0.222312), correlation (0.23324)*/, + 5,-6, 7,-1/*mean (0.229082), correlation (0.23389)*/, + 1,0, 4,-5/*mean (0.241577), correlation (0.215286)*/, + 9,11, 11,-13/*mean (0.00338507), correlation (0.251373)*/, + 4,7, 4,12/*mean (0.131005), correlation (0.257622)*/, + 2,-1, 4,4/*mean (0.152755), correlation (0.255205)*/, + -4,-12, -2,7/*mean (0.182771), correlation (0.244867)*/, + -8,-5, -7,-10/*mean (0.186898), correlation (0.23901)*/, + 4,11, 9,12/*mean (0.226226), correlation (0.258255)*/, + 0,-8, 1,-13/*mean (0.0897886), correlation (0.274827)*/, + -13,-2, -8,2/*mean (0.148774), correlation (0.28065)*/, + -3,-2, -2,3/*mean (0.153048), correlation (0.283063)*/, + -6,9, -4,-9/*mean (0.169523), correlation (0.278248)*/, + 8,12, 10,7/*mean (0.225337), correlation (0.282851)*/, + 0,9, 1,3/*mean (0.226687), correlation (0.278734)*/, + 7,-5, 11,-10/*mean (0.00693882), correlation (0.305161)*/, + -13,-6, -11,0/*mean (0.0227283), correlation (0.300181)*/, + 10,7, 12,1/*mean (0.125517), correlation (0.31089)*/, + -6,-3, -6,12/*mean (0.131748), correlation (0.312779)*/, + 10,-9, 12,-4/*mean (0.144827), correlation (0.292797)*/, + -13,8, -8,-12/*mean (0.149202), correlation (0.308918)*/, + -13,0, -8,-4/*mean (0.160909), correlation (0.310013)*/, + 3,3, 7,8/*mean (0.177755), correlation (0.309394)*/, + 5,7, 10,-7/*mean (0.212337), correlation (0.310315)*/, + -1,7, 1,-12/*mean (0.214429), correlation (0.311933)*/, + 3,-10, 5,6/*mean (0.235807), correlation (0.313104)*/, + 2,-4, 3,-10/*mean (0.00494827), correlation (0.344948)*/, + -13,0, -13,5/*mean (0.0549145), correlation (0.344675)*/, + -13,-7, -12,12/*mean (0.103385), correlation (0.342715)*/, + -13,3, -11,8/*mean (0.134222), correlation (0.322922)*/, + -7,12, -4,7/*mean (0.153284), correlation (0.337061)*/, + 6,-10, 12,8/*mean (0.154881), correlation (0.329257)*/, + -9,-1, -7,-6/*mean (0.200967), correlation (0.33312)*/, + -2,-5, 0,12/*mean (0.201518), correlation (0.340635)*/, + -12,5, -7,5/*mean (0.207805), correlation (0.335631)*/, + 3,-10, 8,-13/*mean (0.224438), correlation (0.34504)*/, + -7,-7, -4,5/*mean (0.239361), correlation (0.338053)*/, + -3,-2, -1,-7/*mean (0.240744), correlation (0.344322)*/, + 2,9, 5,-11/*mean (0.242949), correlation (0.34145)*/, + -11,-13, -5,-13/*mean (0.244028), correlation (0.336861)*/, + -1,6, 0,-1/*mean (0.247571), correlation (0.343684)*/, + 5,-3, 5,2/*mean (0.000697256), correlation (0.357265)*/, + -4,-13, -4,12/*mean (0.00213675), correlation (0.373827)*/, + -9,-6, -9,6/*mean (0.0126856), correlation (0.373938)*/, + -12,-10, -8,-4/*mean (0.0152497), correlation (0.364237)*/, + 10,2, 12,-3/*mean (0.0299933), correlation (0.345292)*/, + 7,12, 12,12/*mean (0.0307242), correlation (0.366299)*/, + -7,-13, -6,5/*mean (0.0534975), correlation (0.368357)*/, + -4,9, -3,4/*mean (0.099865), correlation (0.372276)*/, + 7,-1, 12,2/*mean (0.117083), correlation (0.364529)*/, + -7,6, -5,1/*mean (0.126125), correlation (0.369606)*/, + -13,11, -12,5/*mean (0.130364), correlation (0.358502)*/, + -3,7, -2,-6/*mean (0.131691), correlation (0.375531)*/, + 7,-8, 12,-7/*mean (0.160166), correlation (0.379508)*/, + -13,-7, -11,-12/*mean (0.167848), correlation (0.353343)*/, + 1,-3, 12,12/*mean (0.183378), correlation (0.371916)*/, + 2,-6, 3,0/*mean (0.228711), correlation (0.371761)*/, + -4,3, -2,-13/*mean (0.247211), correlation (0.364063)*/, + -1,-13, 1,9/*mean (0.249325), correlation (0.378139)*/, + 7,1, 8,-6/*mean (0.000652272), correlation (0.411682)*/, + 1,-1, 3,12/*mean (0.00248538), correlation (0.392988)*/, + 9,1, 12,6/*mean (0.0206815), correlation (0.386106)*/, + -1,-9, -1,3/*mean (0.0364485), correlation (0.410752)*/, + -13,-13, -10,5/*mean (0.0376068), correlation (0.398374)*/, + 7,7, 10,12/*mean (0.0424202), correlation (0.405663)*/, + 12,-5, 12,9/*mean (0.0942645), correlation (0.410422)*/, + 6,3, 7,11/*mean (0.1074), correlation (0.413224)*/, + 5,-13, 6,10/*mean (0.109256), correlation (0.408646)*/, + 2,-12, 2,3/*mean (0.131691), correlation (0.416076)*/, + 3,8, 4,-6/*mean (0.165081), correlation (0.417569)*/, + 2,6, 12,-13/*mean (0.171874), correlation (0.408471)*/, + 9,-12, 10,3/*mean (0.175146), correlation (0.41296)*/, + -8,4, -7,9/*mean (0.183682), correlation (0.402956)*/, + -11,12, -4,-6/*mean (0.184672), correlation (0.416125)*/, + 1,12, 2,-8/*mean (0.191487), correlation (0.386696)*/, + 6,-9, 7,-4/*mean (0.192668), correlation (0.394771)*/, + 2,3, 3,-2/*mean (0.200157), correlation (0.408303)*/, + 6,3, 11,0/*mean (0.204588), correlation (0.411762)*/, + 3,-3, 8,-8/*mean (0.205904), correlation (0.416294)*/, + 7,8, 9,3/*mean (0.213237), correlation (0.409306)*/, + -11,-5, -6,-4/*mean (0.243444), correlation (0.395069)*/, + -10,11, -5,10/*mean (0.247672), correlation (0.413392)*/, + -5,-8, -3,12/*mean (0.24774), correlation (0.411416)*/, + -10,5, -9,0/*mean (0.00213675), correlation (0.454003)*/, + 8,-1, 12,-6/*mean (0.0293635), correlation (0.455368)*/, + 4,-6, 6,-11/*mean (0.0404971), correlation (0.457393)*/, + -10,12, -8,7/*mean (0.0481107), correlation (0.448364)*/, + 4,-2, 6,7/*mean (0.050641), correlation (0.455019)*/, + -2,0, -2,12/*mean (0.0525978), correlation (0.44338)*/, + -5,-8, -5,2/*mean (0.0629667), correlation (0.457096)*/, + 7,-6, 10,12/*mean (0.0653846), correlation (0.445623)*/, + -9,-13, -8,-8/*mean (0.0858749), correlation (0.449789)*/, + -5,-13, -5,-2/*mean (0.122402), correlation (0.450201)*/, + 8,-8, 9,-13/*mean (0.125416), correlation (0.453224)*/, + -9,-11, -9,0/*mean (0.130128), correlation (0.458724)*/, + 1,-8, 1,-2/*mean (0.132467), correlation (0.440133)*/, + 7,-4, 9,1/*mean (0.132692), correlation (0.454)*/, + -2,1, -1,-4/*mean (0.135695), correlation (0.455739)*/, + 11,-6, 12,-11/*mean (0.142904), correlation (0.446114)*/, + -12,-9, -6,4/*mean (0.146165), correlation (0.451473)*/, + 3,7, 7,12/*mean (0.147627), correlation (0.456643)*/, + 5,5, 10,8/*mean (0.152901), correlation (0.455036)*/, + 0,-4, 2,8/*mean (0.167083), correlation (0.459315)*/, + -9,12, -5,-13/*mean (0.173234), correlation (0.454706)*/, + 0,7, 2,12/*mean (0.18312), correlation (0.433855)*/, + -1,2, 1,7/*mean (0.185504), correlation (0.443838)*/, + 5,11, 7,-9/*mean (0.185706), correlation (0.451123)*/, + 3,5, 6,-8/*mean (0.188968), correlation (0.455808)*/, + -13,-4, -8,9/*mean (0.191667), correlation (0.459128)*/, + -5,9, -3,-3/*mean (0.193196), correlation (0.458364)*/, + -4,-7, -3,-12/*mean (0.196536), correlation (0.455782)*/, + 6,5, 8,0/*mean (0.1972), correlation (0.450481)*/, + -7,6, -6,12/*mean (0.199438), correlation (0.458156)*/, + -13,6, -5,-2/*mean (0.211224), correlation (0.449548)*/, + 1,-10, 3,10/*mean (0.211718), correlation (0.440606)*/, + 4,1, 8,-4/*mean (0.213034), correlation (0.443177)*/, + -2,-2, 2,-13/*mean (0.234334), correlation (0.455304)*/, + 2,-12, 12,12/*mean (0.235684), correlation (0.443436)*/, + -2,-13, 0,-6/*mean (0.237674), correlation (0.452525)*/, + 4,1, 9,3/*mean (0.23962), correlation (0.444824)*/, + -6,-10, -3,-5/*mean (0.248459), correlation (0.439621)*/, + -3,-13, -1,1/*mean (0.249505), correlation (0.456666)*/, + 7,5, 12,-11/*mean (0.00119208), correlation (0.495466)*/, + 4,-2, 5,-7/*mean (0.00372245), correlation (0.484214)*/, + -13,9, -9,-5/*mean (0.00741116), correlation (0.499854)*/, + 7,1, 8,6/*mean (0.0208952), correlation (0.499773)*/, + 7,-8, 7,6/*mean (0.0220085), correlation (0.501609)*/, + -7,-4, -7,1/*mean (0.0233806), correlation (0.496568)*/, + -8,11, -7,-8/*mean (0.0236505), correlation (0.489719)*/, + -13,6, -12,-8/*mean (0.0268781), correlation (0.503487)*/, + 2,4, 3,9/*mean (0.0323324), correlation (0.501938)*/, + 10,-5, 12,3/*mean (0.0399235), correlation (0.494029)*/, + -6,-5, -6,7/*mean (0.0420153), correlation (0.486579)*/, + 8,-3, 9,-8/*mean (0.0548021), correlation (0.484237)*/, + 2,-12, 2,8/*mean (0.0616622), correlation (0.496642)*/, + -11,-2, -10,3/*mean (0.0627755), correlation (0.498563)*/, + -12,-13, -7,-9/*mean (0.0829622), correlation (0.495491)*/, + -11,0, -10,-5/*mean (0.0843342), correlation (0.487146)*/, + 5,-3, 11,8/*mean (0.0929937), correlation (0.502315)*/, + -2,-13, -1,12/*mean (0.113327), correlation (0.48941)*/, + -1,-8, 0,9/*mean (0.132119), correlation (0.467268)*/, + -13,-11, -12,-5/*mean (0.136269), correlation (0.498771)*/, + -10,-2, -10,11/*mean (0.142173), correlation (0.498714)*/, + -3,9, -2,-13/*mean (0.144141), correlation (0.491973)*/, + 2,-3, 3,2/*mean (0.14892), correlation (0.500782)*/, + -9,-13, -4,0/*mean (0.150371), correlation (0.498211)*/, + -4,6, -3,-10/*mean (0.152159), correlation (0.495547)*/, + -4,12, -2,-7/*mean (0.156152), correlation (0.496925)*/, + -6,-11, -4,9/*mean (0.15749), correlation (0.499222)*/, + 6,-3, 6,11/*mean (0.159211), correlation (0.503821)*/, + -13,11, -5,5/*mean (0.162427), correlation (0.501907)*/, + 11,11, 12,6/*mean (0.16652), correlation (0.497632)*/, + 7,-5, 12,-2/*mean (0.169141), correlation (0.484474)*/, + -1,12, 0,7/*mean (0.169456), correlation (0.495339)*/, + -4,-8, -3,-2/*mean (0.171457), correlation (0.487251)*/, + -7,1, -6,7/*mean (0.175), correlation (0.500024)*/, + -13,-12, -8,-13/*mean (0.175866), correlation (0.497523)*/, + -7,-2, -6,-8/*mean (0.178273), correlation (0.501854)*/, + -8,5, -6,-9/*mean (0.181107), correlation (0.494888)*/, + -5,-1, -4,5/*mean (0.190227), correlation (0.482557)*/, + -13,7, -8,10/*mean (0.196739), correlation (0.496503)*/, + 1,5, 5,-13/*mean (0.19973), correlation (0.499759)*/, + 1,0, 10,-13/*mean (0.204465), correlation (0.49873)*/, + 9,12, 10,-1/*mean (0.209334), correlation (0.49063)*/, + 5,-8, 10,-9/*mean (0.211134), correlation (0.503011)*/, + -1,11, 1,-13/*mean (0.212), correlation (0.499414)*/, + -9,-3, -6,2/*mean (0.212168), correlation (0.480739)*/, + -1,-10, 1,12/*mean (0.212731), correlation (0.502523)*/, + -13,1, -8,-10/*mean (0.21327), correlation (0.489786)*/, + 8,-11, 10,-6/*mean (0.214159), correlation (0.488246)*/, + 2,-13, 3,-6/*mean (0.216993), correlation (0.50287)*/, + 7,-13, 12,-9/*mean (0.223639), correlation (0.470502)*/, + -10,-10, -5,-7/*mean (0.224089), correlation (0.500852)*/, + -10,-8, -8,-13/*mean (0.228666), correlation (0.502629)*/, + 4,-6, 8,5/*mean (0.22906), correlation (0.498305)*/, + 3,12, 8,-13/*mean (0.233378), correlation (0.503825)*/, + -4,2, -3,-3/*mean (0.234323), correlation (0.476692)*/, + 5,-13, 10,-12/*mean (0.236392), correlation (0.475462)*/, + 4,-13, 5,-1/*mean (0.236842), correlation (0.504132)*/, + -9,9, -4,3/*mean (0.236977), correlation (0.497739)*/, + 0,3, 3,-9/*mean (0.24314), correlation (0.499398)*/, + -12,1, -6,1/*mean (0.243297), correlation (0.489447)*/, + 3,2, 4,-8/*mean (0.00155196), correlation (0.553496)*/, + -10,-10, -10,9/*mean (0.00239541), correlation (0.54297)*/, + 8,-13, 12,12/*mean (0.0034413), correlation (0.544361)*/, + -8,-12, -6,-5/*mean (0.003565), correlation (0.551225)*/, + 2,2, 3,7/*mean (0.00835583), correlation (0.55285)*/, + 10,6, 11,-8/*mean (0.00885065), correlation (0.540913)*/, + 6,8, 8,-12/*mean (0.0101552), correlation (0.551085)*/, + -7,10, -6,5/*mean (0.0102227), correlation (0.533635)*/, + -3,-9, -3,9/*mean (0.0110211), correlation (0.543121)*/, + -1,-13, -1,5/*mean (0.0113473), correlation (0.550173)*/, + -3,-7, -3,4/*mean (0.0140913), correlation (0.554774)*/, + -8,-2, -8,3/*mean (0.017049), correlation (0.55461)*/, + 4,2, 12,12/*mean (0.01778), correlation (0.546921)*/, + 2,-5, 3,11/*mean (0.0224022), correlation (0.549667)*/, + 6,-9, 11,-13/*mean (0.029161), correlation (0.546295)*/, + 3,-1, 7,12/*mean (0.0303081), correlation (0.548599)*/, + 11,-1, 12,4/*mean (0.0355151), correlation (0.523943)*/, + -3,0, -3,6/*mean (0.0417904), correlation (0.543395)*/, + 4,-11, 4,12/*mean (0.0487292), correlation (0.542818)*/, + 2,-4, 2,1/*mean (0.0575124), correlation (0.554888)*/, + -10,-6, -8,1/*mean (0.0594242), correlation (0.544026)*/, + -13,7, -11,1/*mean (0.0597391), correlation (0.550524)*/, + -13,12, -11,-13/*mean (0.0608974), correlation (0.55383)*/, + 6,0, 11,-13/*mean (0.065126), correlation (0.552006)*/, + 0,-1, 1,4/*mean (0.074224), correlation (0.546372)*/, + -13,3, -9,-2/*mean (0.0808592), correlation (0.554875)*/, + -9,8, -6,-3/*mean (0.0883378), correlation (0.551178)*/, + -13,-6, -8,-2/*mean (0.0901035), correlation (0.548446)*/, + 5,-9, 8,10/*mean (0.0949843), correlation (0.554694)*/, + 2,7, 3,-9/*mean (0.0994152), correlation (0.550979)*/, + -1,-6, -1,-1/*mean (0.10045), correlation (0.552714)*/, + 9,5, 11,-2/*mean (0.100686), correlation (0.552594)*/, + 11,-3, 12,-8/*mean (0.101091), correlation (0.532394)*/, + 3,0, 3,5/*mean (0.101147), correlation (0.525576)*/, + -1,4, 0,10/*mean (0.105263), correlation (0.531498)*/, + 3,-6, 4,5/*mean (0.110785), correlation (0.540491)*/, + -13,0, -10,5/*mean (0.112798), correlation (0.536582)*/, + 5,8, 12,11/*mean (0.114181), correlation (0.555793)*/, + 8,9, 9,-6/*mean (0.117431), correlation (0.553763)*/, + 7,-4, 8,-12/*mean (0.118522), correlation (0.553452)*/, + -10,4, -10,9/*mean (0.12094), correlation (0.554785)*/, + 7,3, 12,4/*mean (0.122582), correlation (0.555825)*/, + 9,-7, 10,-2/*mean (0.124978), correlation (0.549846)*/, + 7,0, 12,-2/*mean (0.127002), correlation (0.537452)*/, + -1,-6, 0,-11/*mean (0.127148), correlation (0.547401)*/ +}; + +static int kpt_comp(const kp_t *kp1, const kp_t *kp2) +{ + // Descending order + return kp2->score - kp1->score; +} + +static int comp_angle(image_t *img, kp_t *kp, float *a, float *b) +{ + int step = img->w; + int half_k = 31/2; + int m_01 = 0, m_10 = 0; + uint8_t *center = img->pixels+(kp->y*img->w+kp->x); + + // Treat the center line differently, v=0 + for (int u = -half_k; u <= half_k; ++u) { + m_10 += u * center[u]; + } + + // Go line by line in the circular patch + for (int v = 1; v <= half_k; ++v) { + // Proceed over the two lines + int v_sum = 0; + int d = u_max[v]; + for (int u = -d; u <= d; ++u) { + int val_plus = center[u + v*step], val_minus = center[u - v*step]; + v_sum += (val_plus - val_minus); + m_10 += u * (val_plus + val_minus); + } + m_01 += v * v_sum; + } + + int angle = (int) (atan2f((float)m_01, (float)m_10) * (180.0f/M_PI)); + if (angle < 0) { + angle += 360; + } + + // Quantize angle to 15 degrees + angle = angle - (angle % 15); + + *a = cos_table[angle]; + *b = sin_table[angle]; + return angle; +} + +static void image_scale(image_t *src, image_t *dst) +{ + int x_ratio = (int)((src->w<<16)/dst->w) +1; + int y_ratio = (int)((src->h<<16)/dst->h) +1; + + for (int y=0; yh; y++) { + int sy = (y*y_ratio)>>16; + for (int x=0; xw; x++) { + int sx = (x*x_ratio)>>16; + dst->pixels[y*dst->w+x] = IM_TO_GS_PIXEL(src, sx, sy); + } + } +} + +array_t *orb_find_keypoints(image_t *img, bool normalized, int threshold, + float scale_factor, int max_keypoints, corner_detector_t corner_detector, rectangle_t *roi) +{ + array_t *kpts; + array_alloc(&kpts, xfree); + + int octave = 1; + int kpts_index = 0; + rectangle_t roi_scaled; + + for(float scale=1.0f; ; scale*=scale_factor, octave++) { + image_t img_scaled = { + .w = (int) roundf(img->w/scale), + .h = (int) roundf(img->h/scale), + .pixfmt = PIXFORMAT_GRAYSCALE, + .pixels = NULL + }; + + // Add patch size to ROI + roi_scaled.x = (int) roundf(roi->x/scale) + (PATCH_SIZE); + roi_scaled.y = (int) roundf(roi->y/scale) + (PATCH_SIZE); + roi_scaled.w = (int) roundf(roi->w/scale) - (PATCH_SIZE*2); + roi_scaled.h = (int) roundf(roi->h/scale) - (PATCH_SIZE*2); + + if (roi_scaled.w <= (PATCH_SIZE*2) || + roi_scaled.h <= (PATCH_SIZE*2)) { + break; + } + + img_scaled.pixels = fb_alloc(img_scaled.w * img_scaled.h, FB_ALLOC_NO_HINT); + // Down scale image + image_scale(img, &img_scaled); + + // Gaussian smooth the image before extracting keypoints + imlib_sepconv3(&img_scaled, kernel_gauss_3, 1.0f/16.0f, 0.0f); + + // Find kpts + #ifdef IMLIB_ENABLE_FAST + if (corner_detector == CORNER_FAST) { + fast_detect(&img_scaled, kpts, threshold, &roi_scaled); + } + else + #endif + { + agast_detect(&img_scaled, kpts, threshold, &roi_scaled); + } + + for (int k=kpts_index; koctave = octave; + + int x, y; + float a, b; + sample_point_t *pattern = (sample_point_t*) sample_pattern; + kpt->angle = comp_angle(&img_scaled, kpt, &a, &b); + + #if 1 + #define GET_VALUE(idx) \ + (x = (int) roundf(pattern[idx].x*a - pattern[idx].y*b), \ + y = (int) roundf(pattern[idx].x*b + pattern[idx].y*a), \ + img_scaled.pixels[((kpt->y+y)*img_scaled.w)+(kpt->x+x)]) + #else + #define GET_VALUE(idx) \ + (img_scaled.pixels[((kpt->y+pattern[idx].y)*img_scaled.w)+(kpt->x+pattern[idx].x)]) + #endif + + for (int i=0; i t0 ) t0 = t1, u = 1; + if( t3 > t2 ) t2 = t3, v = 3; + k = t0 > t2 ? u : v; + val = k; + + t0 = GET_VALUE(4); t1 = GET_VALUE(5); + t2 = GET_VALUE(6); t3 = GET_VALUE(7); + u = 0, v = 2; + if( t1 > t0 ) t0 = t1, u = 1; + if( t3 > t2 ) t2 = t3, v = 3; + k = t0 > t2 ? u : v; + val |= k << 2; + + t0 = GET_VALUE(8); t1 = GET_VALUE(9); + t2 = GET_VALUE(10); t3 = GET_VALUE(11); + u = 0, v = 2; + if( t1 > t0 ) t0 = t1, u = 1; + if( t3 > t2 ) t2 = t3, v = 3; + k = t0 > t2 ? u : v; + val |= k << 4; + + t0 = GET_VALUE(12); t1 = GET_VALUE(13); + t2 = GET_VALUE(14); t3 = GET_VALUE(15); + u = 0, v = 2; + if( t1 > t0 ) t0 = t1, u = 1; + if( t3 > t2 ) t2 = t3, v = 3; + k = t0 > t2 ? u : v; + val |= k << 6; + + kpt->desc[i] = (uint8_t) val; + } + + kpt->x = (int)floorf(kpt->x * scale); + kpt->y = (int)floorf(kpt->y * scale); + } + + // Free current scale + fb_free(img_scaled.pixels); + + if (normalized) { + break; + } + } + + // Sort keypoints by score and return top n keypoints + array_sort(kpts, (array_comp_t) kpt_comp); + if (array_length(kpts) > max_keypoints) { + array_resize(kpts, max_keypoints); + } + + return kpts; +} + +// This is a modifed popcount that counts every 2 different bits as 1. +// This is what should actually be used with wta_k == 3 or 4. +static inline uint32_t popcount(uint32_t i) +{ + i = i - ((i >> 1) & 0x55555555); + i = ((i & 0xAAAAAAAA)>>1) | (i & 0x55555555); + i = (i & 0x33333333) + ((i >> 2) & 0x33333333); + return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; +} + +static kp_t *find_best_match(kp_t *kp1, array_t *kpts, int *dist_out1, int *dist_out2, int *index) +{ + kp_t *min_kp=NULL; + int min_dist1 = MAX_KP_DIST; + int min_dist2 = MAX_KP_DIST; + int kpts_size = array_length(kpts); + + for (int i=0; imatched == 0) { + for (int m=0; m<(KDESC_SIZE/4); m++) { + dist += popcount(((uint32_t*)(kp1->desc))[m] ^ ((uint32_t*)(kp2->desc))[m]); + } + + if (dist < min_dist1) { + *index = i; + min_kp = kp2; + min_dist2 = min_dist1; + min_dist1 = dist; + } + } + } + + *dist_out1 = min_dist1; + *dist_out2 = min_dist2; + return min_kp; +} + +int orb_match_keypoints(array_t *kpts1, array_t *kpts2, int *match, int threshold, rectangle_t *r, point_t *c, int *angle) +{ + int matches=0; + int cx = 0, cy = 0; + uint16_t angles[360]={0}; + int kpts1_size = array_length(kpts1); + + r->w = r->h = 0; + r->x = r->y = 20000; + + // Match keypoints and find "good matches" This runs 2/3 tests found in the RobustMatcher from the OpenCV programming cookbook. + // The first test is based on the distance ratio between the two best matches for a feature, to remove ambiguous matches. + // Second test is the symmetry test (corss-matching) both points in a match must be the best matching feature of each other. + for (int i=0; i threshold) { + continue; + } + + // Cross-match the keypoint in the first set + kp_t *kp2 = find_best_match(min_kp, kpts1, &min_dist1, &min_dist2, &kp_index1); + // Test the distance ratio between the best two matches + if ((min_dist1*100/min_dist2) > threshold) { + continue; + } + + // Cross-match test + if (kp1 == kp2) { + int x, y; + matches++; + min_kp->matched = 1; + cx += x = min_kp->x; + cy += y = min_kp->y; + rectangle_expand(r, x, y); + int angle = (int) abs(min_kp->angle-kp1->angle); + if (angle >= 0 && angle < 360) { + angles[angle]++; + } + *match++ = kp_index1; + *match++ = kp_index2; + } + } + + if (matches == 0) { + r->x = r->y = 0; + return 0; + } + + // Fix centroid x/y + c->x = cx/matches; + c->y = cy/matches; + + // Fix rectangle w/h + r->w = r->w - r->x; + r->h = r->h - r->y; + + int max_angle = 0; + for (int i=0; i<360; i++) { + if (angles[i] > max_angle) { + max_angle = angles[i]; + *angle = i; + } + } + + return matches; +} + +int orb_filter_keypoints(array_t *kpts, rectangle_t *r, point_t *c) +{ + int matches=0; + int cx = 0, cy = 0; + int kpts_size = array_length(kpts); + + r->w = r->h = 0; + r->x = r->y = 20000; + + float *kpts_dist = fb_alloc(kpts_size * sizeof(float), FB_ALLOC_NO_HINT); + + // Find centroid + for (int i=0; imatched) { + matches ++; + cx += kp->x; + cy += kp->y; + } + } + + // Centroid + cx /= matches; + cy /= matches; + + // Find mean distance from centroid + float mdist = 0.0f; + for (int i=0; imatched) { + kpts_dist[i] = orb_cluster_dist(cx, cy, kp); + mdist += kpts_dist[i]; + } + } + // Mean distance from centroid + mdist /= matches; + + // Find variance + float var = 0.0f; + for (int i=0; imatched) { + float dist = kpts_dist[i]; + var += (mdist - dist) * (mdist - dist); + } + } + + // Find standard deviation + float stdist = fast_sqrtf(var/matches); + + // Reset centroid + matches = 0; + cx = cy = 0; + + // Remove outliers and get new centroid + for (int i=0; imatched) { + float dist = fabs(mdist - kpts_dist[i]); + if (dist > stdist) { + kp->matched = 0; + } else { + int x, y; + matches++; + cx += x = kp->x; + cy += y = kp->y; + rectangle_expand(r, x, y); + } + } + } + + // Fix centroid x/y + c->x = cx/matches; + c->y = cy/matches; + + // Fix rectangle w/h + r->w = r->w - r->x; + r->h = r->h - r->y; + + // Free distance array + fb_free(kpts_dist); + return matches; +} + +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) +int orb_save_descriptor(FIL *fp, array_t *kpts) +{ + uint32_t res; + + int kpts_size = array_length(kpts); + + // Write the number of keypoints + res = write_data(fp, &kpts_size, sizeof(kpts_size)); + // res = fwrite(&kpts_size, sizeof(kpts_size), 1, *fp); + if (res != sizeof(kpts_size)) { + goto error; + } + + // Write keypoints + for (int i=0; ix, sizeof(kp->x)); + // res = fwrite(&kp->x, sizeof(kp->x), 1, *fp); + if (res != sizeof(kp->x)) { + goto error; + } + + // Write Y + res = write_data(fp, &kp->y, sizeof(kp->y)); + // res = f_write(fp, &kp->y, sizeof(kp->y), &bytes); + if (res != sizeof(kp->y)) { + goto error; + } + + // Write Score + res = write_data(fp, &kp->score, sizeof(kp->score)); + // res = f_write(fp, &kp->score, sizeof(kp->score), &bytes); + if (res != sizeof(kp->score)) { + goto error; + } + + // Write Octave + res = write_data(fp, &kp->octave, sizeof(kp->octave)); + // res = f_write(fp, &kp->octave, sizeof(kp->octave), &bytes); + if (res != sizeof(kp->octave)) { + goto error; + } + + // Write Angle + res = write_data(fp, &kp->angle, sizeof(kp->angle)); + // res = f_write(fp, &kp->angle, sizeof(kp->angle), &bytes); + if (res != sizeof(kp->angle)) { + goto error; + } + + // Write descriptor + res = write_data(fp, kp->desc, KDESC_SIZE); + // res = f_write(fp, kp->desc, KDESC_SIZE, &bytes); + if (res != KDESC_SIZE) { + goto error; + } + } + +error: + return res; +} + +int orb_load_descriptor(FIL *fp, array_t *kpts) +{ + uint32_t res; + + int kpts_size=0; + + // Read number of keypoints + res = read_data(fp, &kpts_size, sizeof(kpts_size)); + // res = f_read(fp, &kpts_size, sizeof(kpts_size), &bytes); + if (res != sizeof(kpts_size)) { + goto error; + } + + // Read keypoints + for (int i=0; imatched = 0; + + // Read X + res = read_data(fp, &kp->x, sizeof(kp->x)); + // res = f_read(fp, &kp->x, sizeof(kp->x), &bytes); + if (res != sizeof(kp->x)) { + goto error; + } + + // Read Y + res = read_data(fp, &kp->y, sizeof(kp->y)); + if (res != sizeof(kp->y)) { + goto error; + } + + // Read Score + res = read_data(fp, &kp->score, sizeof(kp->score)); + if (res != sizeof(kp->score)) { + goto error; + } + + // Read Octave + res = read_data(fp, &kp->octave, sizeof(kp->octave)); + if (res != sizeof(kp->octave)) { + goto error; + } + + // Read Angle + res = read_data(fp, &kp->angle, sizeof(kp->angle)); + if (res != sizeof(kp->angle)) { + goto error; + } + + // Read descriptor + res = read_data(fp, kp->desc, KDESC_SIZE); + if (res != KDESC_SIZE) { + goto error; + } + + // Add keypoint to array + array_push_back(kpts, kp); + } + +error: + return res; +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO + +float orb_cluster_dist(int cx, int cy, void *kp_in) +{ + float sum=0.0f; + kp_t *kp = kp_in; + sum += (cx - kp->x) * (cx - kp->x); + sum += (cy - kp->y) * (cy - kp->y); + return fast_sqrtf(sum); + +} +#endif //IMLIB_ENABLE_FIND_KEYPOINTS diff --git a/github_source/minicv2/src/phasecorrelation.c b/github_source/minicv2/src/phasecorrelation.c new file mode 100644 index 0000000..933fb66 --- /dev/null +++ b/github_source/minicv2/src/phasecorrelation.c @@ -0,0 +1,655 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Phase correlation. + */ +#include "imlib.h" +#include "fft.h" + +void imlib_logpolar_int(image_t *dst, image_t *src, rectangle_t *roi, bool linear, bool reverse) +{ + int w = roi->w; // == dst_w + int h = roi->h; // == dst_h + int w_2 = w / 2; + int h_2 = h / 2; + float rho_scale = fast_sqrtf((w_2 * w_2) + (h_2 * h_2)); + if (!linear) rho_scale = fast_log(rho_scale); + const float m_pi_1_5 = 1.5f * M_PI; + const float m_pi_1_5_d = IM_RAD2DEG(m_pi_1_5); + const float m_pi_2_0 = 2.0f * M_PI; + const float m_pi_2_0_d = IM_RAD2DEG(m_pi_2_0); + const int m_pi_2_0_d_i = m_pi_2_0_d; + float theta_scale_d = m_pi_2_0_d / (w - 2); + float theta_scale_inv = w / m_pi_2_0; + + if (!reverse) { + rho_scale /= h; + + switch (src->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *tmp = (uint32_t *) src->data; + int tmp_w = src->w, tmp_h = src->h, tmp_x = roi->x + w_2 - 1, tmp_y = roi->y + h_2; + + for (int y = 0, yy = h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, y); + float rho = y * rho_scale; + if (!linear) rho = fast_expf(rho); + for (int x = 0, xx = w_2; x < xx; x++) { + + int theta = fast_roundf(m_pi_1_5_d - (x * theta_scale_d)); + if (theta < 0) theta += m_pi_2_0_d_i; // wrap for table access + int sourceX = tmp_x + fast_roundf(rho * cos_table[theta]); // rounding is necessary + int sourceY = tmp_y + fast_roundf(rho * sin_table[theta]); // rounding is necessary + + if ((0 <= sourceX) && (0 <= sourceY) && (sourceY < tmp_h)) { // plot the 2 symmetrical pixels + uint32_t *ptr, pixel; + ptr = tmp + (((tmp_w + UINT32_T_MASK) >> UINT32_T_SHIFT) * sourceY); + pixel = IMAGE_GET_BINARY_PIXEL_FAST(ptr, sourceX); + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, pixel); + pixel = IMAGE_GET_BINARY_PIXEL_FAST(ptr, tmp_w-1-sourceX); + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, w-1-x, pixel); + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *tmp = (uint8_t *) src->data; + int tmp_w = src->w, tmp_h = src->h, tmp_x = roi->x + w_2 - 1, tmp_y = roi->y + h_2; + + for (int y = 0, yy = h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(dst, y); + float rho = y * rho_scale; + if (!linear) rho = fast_expf(rho); + for (int x = 0, xx = w_2; x < xx; x++) { + + int theta = fast_roundf(m_pi_1_5_d - (x * theta_scale_d)); + if (theta < 0) theta += m_pi_2_0_d_i; // wrap for table access + int sourceX = tmp_x + fast_roundf(rho * cos_table[theta]); // rounding is necessary + int sourceY = tmp_y + fast_roundf(rho * sin_table[theta]); // rounding is necessary + + if ((0 <= sourceX) && (0 <= sourceY) && (sourceY < tmp_h)) { // plot the 2 symmetrical pixels + uint8_t *ptr, pixel; + ptr = tmp + (tmp_w * sourceY); + pixel = ptr[sourceX]; + row_ptr[x] = pixel; + pixel = ptr[tmp_w - 1 - sourceX]; + row_ptr[w - 1 - x] = pixel; + } + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *tmp = (uint16_t *) src->data; + int tmp_w = src->w, tmp_h = src->h, tmp_x = roi->x + w_2 - 1, tmp_y = roi->y + h_2; + + for (int y = 0, yy = h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, y); + float rho = y * rho_scale; + if (!linear) rho = fast_expf(rho); + for (int x = 0, xx = w_2; x < xx; x++) { + + int theta = fast_roundf(m_pi_1_5_d - (x * theta_scale_d)); + if (theta < 0) theta += m_pi_2_0_d_i; // wrap for table access + int sourceX = tmp_x + fast_roundf(rho * cos_table[theta]); // rounding is necessary + int sourceY = tmp_y + fast_roundf(rho * sin_table[theta]); // rounding is necessary + + if ((0 <= sourceX) && (0 <= sourceY) && (sourceY < tmp_h)) { // plot the 2 symmetrical pixels + uint16_t *ptr, pixel; + ptr = tmp + (tmp_w * sourceY); + pixel = ptr[sourceX]; + row_ptr[x] = pixel; + pixel = ptr[tmp_w - 1 - sourceX]; + row_ptr[w - 1 - x] = pixel; + } + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *tmp = (pixel24_t *) src->data; + int tmp_w = src->w, tmp_h = src->h, tmp_x = roi->x + w_2 - 1, tmp_y = roi->y + h_2; + + for (int y = 0, yy = h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(dst, y); + float rho = y * rho_scale; + if (!linear) rho = fast_expf(rho); + for (int x = 0, xx = w_2; x < xx; x++) { + + int theta = fast_roundf(m_pi_1_5_d - (x * theta_scale_d)); + if (theta < 0) theta += m_pi_2_0_d_i; // wrap for table access + int sourceX = tmp_x + fast_roundf(rho * cos_table[theta]); // rounding is necessary + int sourceY = tmp_y + fast_roundf(rho * sin_table[theta]); // rounding is necessary + + if ((0 <= sourceX) && (0 <= sourceY) && (sourceY < tmp_h)) { // plot the 2 symmetrical pixels + pixel24_t *ptr, pixel; + ptr = tmp + (tmp_w * sourceY); + pixel = ptr[sourceX]; + row_ptr[x] = pixel; + pixel = ptr[tmp_w - 1 - sourceX]; + row_ptr[w - 1 - x] = pixel; + } + } + } + break; + } + default: { + break; + } + } + } else { + float rho_scale_inv = (h - 1) / rho_scale; + switch (src->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *tmp = (uint32_t *) src->data; + int tmp_w = src->w, tmp_x = roi->x, tmp_y = roi->y; + + for (int y = 0, yy = h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, y); + int y_2 = y - h_2; + int y_2_2 = y_2 * y_2; + + for (int x = 0, xx = w_2; x < xx; x++) { + int x_2 = x - w_2; + int x_2_2 = x_2 * x_2; + + float rho = fast_sqrtf(x_2_2 + y_2_2); + if (!linear) rho = fast_log(rho); + float theta = m_pi_1_5 - fast_atan2f(y_2, x_2); + int sourceX = tmp_x + fast_roundf(theta * theta_scale_inv); // rounding is necessary + int sourceY = tmp_y + fast_roundf(rho * rho_scale_inv); // rounding is necessary + + // plot the 2 symmetrical pixels + uint32_t *ptr, pixel; + ptr = tmp + (((tmp_w + UINT32_T_MASK) >> UINT32_T_SHIFT) * sourceY); + pixel = IMAGE_GET_BINARY_PIXEL_FAST(ptr, sourceX); + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, pixel); + pixel = IMAGE_GET_BINARY_PIXEL_FAST(ptr, tmp_w-1-sourceX); + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, w-1-x, pixel); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *tmp = (uint8_t *) src->data; + int tmp_w = src->w, tmp_x = roi->x, tmp_y = roi->y; + + for (int y = 0, yy = h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(dst, y); + int y_2 = y - h_2; + int y_2_2 = y_2 * y_2; + + for (int x = 0, xx = w_2; x < xx; x++) { + int x_2 = x - w_2; + int x_2_2 = x_2 * x_2; + + float rho = fast_sqrtf(x_2_2 + y_2_2); + if (!linear) rho = fast_log(rho); + float theta = m_pi_1_5 - fast_atan2f(y_2, x_2); + int sourceX = tmp_x + fast_roundf(theta * theta_scale_inv); // rounding is necessary + int sourceY = tmp_y + fast_roundf(rho * rho_scale_inv); // rounding is necessary + + // plot the 2 symmetrical pixels + uint8_t *ptr, pixel; + ptr = tmp + (tmp_w * sourceY); + pixel = ptr[sourceX]; + row_ptr[x] = pixel; + pixel = ptr[tmp_w - 1 - sourceX]; + row_ptr[w - 1 - x] = pixel; + } + } + break; + } + case PIXFORMAT_RGB565: { + uint16_t *tmp = (uint16_t *) src->data; + int tmp_w = src->w, tmp_x = roi->x, tmp_y = roi->y; + + for (int y = 0, yy = h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, y); + int y_2 = y - h_2; + int y_2_2 = y_2 * y_2; + + for (int x = 0, xx = w_2; x < xx; x++) { + int x_2 = x - w_2; + int x_2_2 = x_2 * x_2; + + float rho = fast_sqrtf(x_2_2 + y_2_2); + if (!linear) rho = fast_log(rho); + float theta = m_pi_1_5 - fast_atan2f(y_2, x_2); + int sourceX = tmp_x + fast_roundf(theta * theta_scale_inv); // rounding is necessary + int sourceY = tmp_y + fast_roundf(rho * rho_scale_inv); // rounding is necessary + + // plot the 2 symmetrical pixels + uint16_t *ptr, pixel; + ptr = tmp + (tmp_w * sourceY); + pixel = ptr[sourceX]; + row_ptr[x] = pixel; + pixel = ptr[tmp_w - 1 - sourceX]; + row_ptr[w - 1 - x] = pixel; + } + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *tmp = (pixel24_t *) src->data; + int tmp_w = src->w, tmp_x = roi->x, tmp_y = roi->y; + + for (int y = 0, yy = h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(dst, y); + int y_2 = y - h_2; + int y_2_2 = y_2 * y_2; + + for (int x = 0, xx = w_2; x < xx; x++) { + int x_2 = x - w_2; + int x_2_2 = x_2 * x_2; + + float rho = fast_sqrtf(x_2_2 + y_2_2); + if (!linear) rho = fast_log(rho); + float theta = m_pi_1_5 - fast_atan2f(y_2, x_2); + int sourceX = tmp_x + fast_roundf(theta * theta_scale_inv); // rounding is necessary + int sourceY = tmp_y + fast_roundf(rho * rho_scale_inv); // rounding is necessary + + // plot the 2 symmetrical pixels + pixel24_t *ptr, pixel; + ptr = tmp + (tmp_w * sourceY); + pixel = ptr[sourceX]; + row_ptr[x] = pixel; + pixel = ptr[tmp_w - 1 - sourceX]; + row_ptr[w - 1 - x] = pixel; + } + } + break; + } + default: { + break; + } + } + } +} + +#if defined(IMLIB_ENABLE_LOGPOLAR) || defined(IMLIB_ENABLE_LINPOLAR) +void imlib_logpolar(image_t *img, bool linear, bool reverse) +{ + image_t img_2; + img_2.w = img->w; + img_2.h = img->h; + img_2.pixfmt = img->pixfmt; + + rectangle_t rect; + rect.x = 0; + rect.y = 0; + rect.w = img->w; + rect.h = img->h; + + size_t size = image_size(img); + img_2.data = fb_alloc(size, FB_ALLOC_NO_HINT); + memcpy(img_2.data, img->data, size); + memset(img->data, 0, size); + + imlib_logpolar_int(img, &img_2, &rect, linear, reverse); + + fb_free(img_2.data); +} +#endif //defined(IMLIB_ENABLE_LOGPOLAR) || defined(IMLIB_ENABLE_LINPOLAR) + +#ifdef IMLIB_ENABLE_FIND_DISPLACEMENT +// Note that both ROI widths and heights must be equal. +void imlib_phasecorrelate(image_t *img0, image_t *img1, rectangle_t *roi0, rectangle_t *roi1, bool logpolar, bool fix_rotation_scale, + float *x_translation, float *y_translation, float *rotation, float *scale, float *response) +{ + // Step 1 - Get Rotation/Scale Differences + if ((!logpolar) && fix_rotation_scale) { + fft2d_controller_t fft0, fft1; + + fft2d_alloc(&fft0, img0, roi0); + fft2d_alloc(&fft1, img1, roi1); + + fft2d_run(&fft0); + fft2d_run(&fft1); + + fft2d_mag(&fft0); + fft2d_mag(&fft1); + + fft2d_swap(&fft0); + fft2d_swap(&fft1); + + fft2d_logpolar(&fft0); + fft2d_logpolar(&fft1); + + fft2d_run_again(&fft0); + fft2d_run_again(&fft1); + + int w = (1 << fft0.w_pow2); + int h = (1 << fft0.h_pow2); + + for (int i = 0, j = h * w * 2; i < j; i += 2) { + float ga_r = fft0.data[i+0]; + float ga_i = fft0.data[i+1]; + float gb_r = fft1.data[i+0]; + float gb_i = -fft1.data[i+1]; // complex conjugate... + float hp_r = (ga_r * gb_r) - (ga_i * gb_i); // hadamard product + float hp_i = (ga_r * gb_i) + (ga_i * gb_r); // hadamard product + float mag = 1 / fast_sqrtf((hp_r*hp_r)+(hp_i*hp_i)); // magnitude + // Replace first fft with phase correlation... + fft0.data[i+0] = hp_r * mag; + fft0.data[i+1] = hp_i * mag; + } + + ifft2d_run(&fft0); + + float sum = 0; + float max = 0; + int off_x = 0; + int off_y = 0; + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + // Note that the output of the FFT is packed with real data in both + // the real and imaginary parts... (right side of the array is zero). + float f_r = fft0.data[(i * w * 2) + j]; + sum += f_r; + if (f_r > max) { + max = f_r; + off_x = j; + off_y = i; + } + } + } + + float tmp_response = max / sum; // normalize this to [0:1]. + + float f_sum = 0; + float f_off_x = 0; + float f_off_y = 0; + + for (int i = -2; i < 2; i++) { + for (int j = -2; j < 2; j++) { + + // Wrap around + int new_x = off_x + j; + if (new_x < 0) new_x += w; + if (new_x >= w) new_x -= w; + + // Wrap around + int new_y = off_y + i; + if (new_y < 0) new_y += h; + if (new_y >= h) new_y -= h; + + // Compute centroid. + float f_r = fft0.data[(new_y * w * 2) + new_x]; + f_off_x += (off_x + j) * f_r; // don't use new_x here + f_off_y += (off_y + i) * f_r; // don't use new_y here + f_sum += f_r; + } + } + + f_off_x /= f_sum; + f_off_y /= f_sum; + + // FFT Shift X + if (f_off_x >= (w/2.0f)) { + f_off_x = f_off_x - w; + } else { + f_off_x = f_off_x; + } + + // FFT Shift Y + if (f_off_y >= (h/2.0f)) { + f_off_y = -(f_off_y - h); + } else { + f_off_y = -f_off_y; + } + + if ((f_off_x < (-w/2.0f)) + || ((w/2.0f) <= f_off_x) + || (f_off_y < (-h/2.0f)) + || ((h/2.0f) <= f_off_y) + || isnanf(f_off_x) + || isinff(f_off_x) + || isnanf(f_off_y) + || isinff(f_off_y) + || isnanf(tmp_response) + || isinff(tmp_response)) { // Noise Filter + f_off_x = 0; + f_off_y = 0; + tmp_response = 0; + } + + fft2d_dealloc(&fft1); // fft1 + fft2d_dealloc(&fft0); // fft0 + + float w_2 = roi0->w / 2.0f; + float h_2 = roi0->h / 2.0f; + float rho_scale = fast_log(fast_sqrtf((w_2 * w_2) + (h_2 * h_2))) / roi0->h; + float theta_scale = (2 * M_PI) / roi0->w; + + *rotation = f_off_x * theta_scale; + *scale = (f_off_y * rho_scale) + 1; + } else { + *rotation = 0; + *scale = 0; + } + + image_t img0_fixed; + rectangle_t roi0_fixed; + + // Step 2 - Fix Rotation/Scale Differences + if ((!logpolar) && fix_rotation_scale) { + + img0_fixed.w = roi0->w; + img0_fixed.h = roi0->h; + img0_fixed.pixfmt = img0->pixfmt; + img0_fixed.pixels = fb_alloc(image_size(&img0_fixed), FB_ALLOC_NO_HINT); + + roi0_fixed.x = 0; + roi0_fixed.y = 0; + roi0_fixed.w = roi0->w; + roi0_fixed.h = roi0->h; + + switch (img0->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = roi0->y, yy = roi0->y + roi0->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img0, y); + for (int x = roi0->x, xx = roi0->x + roi0->w; x < xx; x++) { + IMAGE_PUT_BINARY_PIXEL(&img0_fixed, x, y, IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x)); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = roi0->y, yy = roi0->y + roi0->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img0, y); + for (int x = roi0->x, xx = roi0->x + roi0->w; x < xx; x++) { + IMAGE_PUT_GRAYSCALE_PIXEL(&img0_fixed, x, y, IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x)); + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = roi0->y, yy = roi0->y + roi0->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img0, y); + for (int x = roi0->x, xx = roi0->x + roi0->w; x < xx; x++) { + IMAGE_PUT_RGB565_PIXEL(&img0_fixed, x, y, IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x)); + } + } + break; + } + case PIXFORMAT_RGB888: { + for (int y = roi0->y, yy = roi0->y + roi0->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img0, y); + for (int x = roi0->x, xx = roi0->x + roi0->w; x < xx; x++) { + IMAGE_PUT_RGB888_PIXEL_(&img0_fixed, x, y, IMAGE_GET_RGB888_PIXEL_FAST_(row_ptr, x)); + } + } + break; + } + default: { + memset(img0_fixed.data, 0, image_size(&img0_fixed)); + break; + } + } + + imlib_rotation_corr(&img0_fixed, 0, 0, *rotation, 0, 0, *scale, 60, NULL); + } else { + memcpy(&img0_fixed, img0, sizeof(image_t)); + memcpy(&roi0_fixed, roi0, sizeof(rectangle_t)); + } + + // Step 3 - Get Translation Differences + { + image_t img0alt, img1alt; + rectangle_t roi0alt, roi1alt; + + if (logpolar) { + img0alt.w = roi0_fixed.w; + img0alt.h = roi0_fixed.h; + img0alt.pixfmt = img0_fixed.pixfmt; + img0alt.data = fb_alloc0(image_size(&img0alt), FB_ALLOC_NO_HINT); + imlib_logpolar_int(&img0alt, &img0_fixed, &roi0_fixed, false, false); + roi0alt.x = 0; + roi0alt.y = 0; + roi0alt.w = roi0_fixed.w; + roi0alt.h = roi0_fixed.h; + + img1alt.w = roi1->w; + img1alt.h = roi1->h; + img1alt.pixfmt = img1->pixfmt; + img1alt.data = fb_alloc0(image_size(&img1alt), FB_ALLOC_NO_HINT); + imlib_logpolar_int(&img1alt, img1, roi1, false, false); + roi1alt.x = 0; + roi1alt.y = 0; + roi1alt.w = roi1->w; + roi1alt.h = roi1->h; + } + + fft2d_controller_t fft0, fft1; + + fft2d_alloc(&fft0, logpolar ? &img0alt : &img0_fixed, logpolar ? &roi0alt : &roi0_fixed); + fft2d_alloc(&fft1, logpolar ? &img1alt : img1, logpolar ? &roi1alt : roi1); + + fft2d_run(&fft0); + fft2d_run(&fft1); + + int w = (1 << fft0.w_pow2); + int h = (1 << fft0.h_pow2); + + for (int i = 0, j = h * w * 2; i < j; i += 2) { + float ga_r = fft0.data[i+0]; + float ga_i = fft0.data[i+1]; + float gb_r = fft1.data[i+0]; + float gb_i = -fft1.data[i+1]; // complex conjugate... + float hp_r = (ga_r * gb_r) - (ga_i * gb_i); // hadamard product + float hp_i = (ga_r * gb_i) + (ga_i * gb_r); // hadamard product + float mag = 1 / fast_sqrtf((hp_r*hp_r)+(hp_i*hp_i)); // magnitude + fft0.data[i+0] = hp_r * mag; + fft0.data[i+1] = hp_i * mag; + } + + ifft2d_run(&fft0); + + float sum = 0; + float max = 0; + int off_x = 0; + int off_y = 0; + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + // Note that the output of the FFT is packed with real data in both + // the real and imaginary parts... (right side of the array is zero). + float f_r = fft0.data[(i * w * 2) + j]; + sum += f_r; + if (f_r > max) { + max = f_r; + off_x = j; + off_y = i; + } + } + } + + *response = max / sum; // normalize this to [0:1]. + + float f_sum = 0; + float f_off_x = 0; + float f_off_y = 0; + + for (int i = -2; i < 2; i++) { + for (int j = -2; j < 2; j++) { + + // Wrap around + int new_x = off_x + j; + if (new_x < 0) new_x += w; + if (new_x >= w) new_x -= w; + + // Wrap around + int new_y = off_y + i; + if (new_y < 0) new_y += h; + if (new_y >= h) new_y -= h; + + // Compute centroid. + float f_r = fft0.data[(new_y * w * 2) + new_x]; + f_off_x += (off_x + j) * f_r; // don't use new_x here + f_off_y += (off_y + i) * f_r; // don't use new_y here + f_sum += f_r; + } + } + + f_off_x /= f_sum; + f_off_y /= f_sum; + + // FFT Shift X + if (f_off_x >= (w/2.0f)) { + *x_translation = f_off_x - w; + } else { + *x_translation = f_off_x; + } + + // FFT Shift Y + if (f_off_y >= (h/2.0f)) { + *y_translation = -(f_off_y - h); + } else { + *y_translation = -f_off_y; + } + + if ((*x_translation < (-w/2.0f)) + || ((w/2.0f) <= *x_translation) + || (*y_translation < (-h/2.0f)) + || ((h/2.0f) <= *y_translation) + || isnanf(*x_translation) + || isinff(*x_translation) + || isnanf(*y_translation) + || isinff(*y_translation) + || isnanf(*response) + || isinff(*response)) { // Noise Filter + *x_translation = 0; + *y_translation = 0; + *response = 0; + } + + fft2d_dealloc(&fft1); // fft1 + fft2d_dealloc(&fft0); // fft0 + + if (logpolar) { + fb_free(img1alt.data); // img1alt + fb_free(img0alt.data); // img0alt + + float w_2 = roi0->w / 2.0f; + float h_2 = roi0->h / 2.0f; + float rho_scale = fast_log(fast_sqrtf((w_2 * w_2) + (h_2 * h_2))) / roi0->h; + float theta_scale = (2 * M_PI) / roi0->w; + + *rotation = *x_translation * theta_scale; + *scale = (*y_translation * rho_scale) + 1; + *x_translation = 0; + *y_translation = 0; + } + } + + if ((!logpolar) && fix_rotation_scale) fb_free(img0_fixed.pixels); +} +#endif //IMLIB_ENABLE_FIND_DISPLACEMENT diff --git a/github_source/minicv2/src/pixfmt.c b/github_source/minicv2/src/pixfmt.c new file mode 100644 index 0000000..45e3e94 --- /dev/null +++ b/github_source/minicv2/src/pixfmt.c @@ -0,0 +1,214 @@ +#include "imlib.h" + + +void imlib_pixfmt_to(image_t *dst, image_t *src, rectangle_t *roi_i) +{ + rectangle_t *roi, tmp_roi; + if(roi_i == NULL){ + roi = &tmp_roi; + roi->x = 0; + roi->y = 0; + roi->w = dst->w; + roi->h = dst->h; + }else{ + roi = roi_i; + } + switch (src->pixfmt) + { + case PIXFORMAT_BINARY: + switch (dst->pixfmt) + { + case PIXFORMAT_BINARY: + { + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + uint32_t *src_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src, roi_y_index); + uint32_t *dst_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, roi_x_index); + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + uint32_t *src_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src, roi_y_index); + uint8_t *dst_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + int pixel = COLOR_BINARY_TO_GRAYSCALE(IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, roi_x_index)); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + case PIXFORMAT_RGB565: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + uint32_t *src_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src, roi_y_index); + uint16_t *dst_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + int pixel = COLOR_BINARY_TO_RGB565(IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, roi_x_index)); + IMAGE_PUT_RGB565_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + case PIXFORMAT_RGB888: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + uint32_t *src_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(src, roi_y_index); + pixel24_t *dst_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + int pixel = COLOR_BINARY_TO_RGB888(IMAGE_GET_BINARY_PIXEL_FAST(src_row_ptr, roi_x_index)); + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + default: + break; + } + break; + case PIXFORMAT_GRAYSCALE: + switch (dst->pixfmt) + { + case PIXFORMAT_BINARY: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + uint8_t *src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src, roi_y_index); + uint32_t *dst_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + int pixel = COLOR_GRAYSCALE_TO_BINARY(IMAGE_GET_GRAYSCALE_PIXEL_FAST(src_row_ptr, roi_x_index)); + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + case PIXFORMAT_GRAYSCALE: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + uint8_t *src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src, roi_y_index); + uint8_t *dst_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(src_row_ptr, roi_x_index); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + case PIXFORMAT_RGB565: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + uint8_t *src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src, roi_y_index); + uint16_t *dst_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + int pixel = COLOR_GRAYSCALE_TO_RGB565(IMAGE_GET_GRAYSCALE_PIXEL_FAST(src_row_ptr, roi_x_index)); + IMAGE_PUT_RGB565_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + case PIXFORMAT_RGB888: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + uint8_t *src_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(src, roi_y_index); + pixel24_t *dst_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + int pixel = COLOR_GRAYSCALE_TO_RGB888(IMAGE_GET_GRAYSCALE_PIXEL_FAST(src_row_ptr, roi_x_index)); + IMAGE_PUT_RGB888_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + default: + break; + } + break; + case PIXFORMAT_RGB565: + switch (dst->pixfmt) + { + case PIXFORMAT_BINARY: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + uint16_t *src_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src, roi_y_index); + uint32_t *dst_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + int pixel = COLOR_RGB565_TO_BINARY(IMAGE_GET_RGB565_PIXEL_FAST(src_row_ptr, roi_x_index)); + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + case PIXFORMAT_GRAYSCALE: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + uint16_t *src_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src, roi_y_index); + uint8_t *dst_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + int pixel = COLOR_RGB565_TO_GRAYSCALE(IMAGE_GET_RGB565_PIXEL_FAST(src_row_ptr, roi_x_index)); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + case PIXFORMAT_RGB565: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + uint16_t *src_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src, roi_y_index); + uint16_t *dst_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(src_row_ptr, roi_x_index); + IMAGE_PUT_RGB565_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + case PIXFORMAT_RGB888: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + uint16_t *src_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(src, roi_y_index); + pixel24_t *dst_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(src_row_ptr, roi_x_index); + pixel24_t pixel_out = {.red = COLOR_RGB565_TO_R8(pixel), .green = COLOR_RGB565_TO_G8(pixel), .blue = COLOR_RGB565_TO_B8(pixel) }; + IMAGE_PUT_RGB888_PIXEL_FAST_(dst_row_ptr, dst_x_index, pixel_out); + } + } + break; + default: + break; + } + break; + case PIXFORMAT_RGB888: + switch (dst->pixfmt) + { + case PIXFORMAT_BINARY: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + pixel24_t *src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src, roi_y_index); + uint32_t *dst_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + int pixel = COLOR_RGB888_TO_BINARY(IMAGE_GET_RGB888_PIXEL_FAST(src_row_ptr, roi_x_index)); + IMAGE_PUT_BINARY_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + case PIXFORMAT_GRAYSCALE: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + pixel24_t *src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src, roi_y_index); + uint8_t *dst_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + pixel24_t s_pixel = IMAGE_GET_RGB888_PIXEL_FAST_(src_row_ptr, roi_x_index); + int pixel = COLOR_RGB888_TO_Y_(s_pixel.red, s_pixel.green, s_pixel.blue); + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + case PIXFORMAT_RGB565: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + pixel24_t *src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src, roi_y_index); + uint16_t *dst_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + pixel24_t src_pixel = IMAGE_GET_RGB888_PIXEL_FAST_(src_row_ptr, roi_x_index); + int pixel = COLOR_R8_G8_B8_TO_RGB565(src_pixel.red, src_pixel.green, src_pixel.blue); + IMAGE_PUT_RGB565_PIXEL_FAST(dst_row_ptr, dst_x_index, pixel); + } + } + break; + case PIXFORMAT_RGB888: + for(int dst_y_index = 0, roi_y_index = roi->y; dst_y_index < dst->h; ++ dst_y_index, ++ roi_y_index){ + pixel24_t *src_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(src, roi_y_index); + pixel24_t *dst_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(dst, dst_y_index); + for(int dst_x_index = 0, roi_x_index = roi->x; dst_x_index < dst->w; ++ dst_x_index, ++ roi_x_index){ + pixel24_t pixel = IMAGE_GET_RGB888_PIXEL_FAST_(src_row_ptr, roi_x_index); + IMAGE_PUT_RGB888_PIXEL_FAST_(dst_row_ptr, dst_x_index, pixel); + } + } + break; + default: + break; + } + break; + default: + break; + } +} \ No newline at end of file diff --git a/github_source/minicv2/src/png.c b/github_source/minicv2/src/png.c new file mode 100644 index 0000000..5c18e25 --- /dev/null +++ b/github_source/minicv2/src/png.c @@ -0,0 +1,378 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * PNG CODEC + */ +#include +#include "imlib.h" +// #include "py/mphal.h" +// #include "py/runtime.h" +#include "ff_wrapper.h" +#if defined(IMLIB_ENABLE_PNG_ENCODER) || defined(IMLIB_ENABLE_PNG_DECODER) +#include "lodepng.h" +// #include "umm_malloc.h" + +#define TIME_PNG (0) + +void *lodepng_malloc(size_t size) { + return xalloc(size); +} + +void *lodepng_realloc(void *ptr, size_t new_size) { + return xrealloc(ptr, new_size); +} + +void lodepng_free(void *ptr) { + return xfree(ptr); +} + +unsigned lodepng_convert_cb(unsigned char *out, const unsigned char *in, + const LodePNGColorMode *mode_out, const LodePNGColorMode *mode_in, unsigned w, unsigned h) { + unsigned error = 0; + unsigned numpixels = w * h; + + if (mode_in->colortype == LCT_CUSTOM) { + // Compression. + // Note: we're always encoding to 8 bits. + switch (mode_in->customfmt) { + case PIXFORMAT_RGB565: { + uint16_t *pixels = (uint16_t *) in; + if (mode_out->colortype == LCT_RGB) { + // RGB565 -> RGB888 + for (int i = 0; i < numpixels; i++, out += 3) { + out[0] = COLOR_RGB565_TO_R8(pixels[i]); + out[1] = COLOR_RGB565_TO_G8(pixels[i]); + out[2] = COLOR_RGB565_TO_B8(pixels[i]); + } + } else if (mode_out->colortype == LCT_RGBA) { + // RGB565 -> RGBA888 + for (int i = 0; i < numpixels; i++, out += 4) { + out[0] = COLOR_RGB565_TO_R8(pixels[i]); + out[1] = COLOR_RGB565_TO_G8(pixels[i]); + out[2] = COLOR_RGB565_TO_B8(pixels[i]); + out[3] = 255; + } + } else { + error = 56; // unsupported color mode conversion. + } + break; + } + case PIXFORMAT_YUV_ANY: + // YUV -> RGB888 + case PIXFORMAT_BAYER_ANY: + // BAYER -> RGB888 + case PIXFORMAT_RGB888: + // RGB888 -> RGB888 + memcpy(out, in, numpixels * 3); + break; + default: + error = 56; // unsupported color mode conversion. + break; + } + } else if (mode_out->colortype == LCT_CUSTOM) { + // Decompression. + // NOTE: decode from 16 bits needs to be implemented. + switch (mode_out->customfmt) { + case PIXFORMAT_RGB565: { + uint16_t *pixels = (uint16_t *) out; + if (mode_in->colortype == LCT_RGB) { + // RGB888 -> RGB565 + for (int i = 0; i < numpixels; i++, in += 3) { + pixels[i] = COLOR_R8_G8_B8_TO_RGB565(in[0], in[1], in[2]); + } + } else if (mode_in->colortype == LCT_RGBA) { + // RGBA888 -> RGB565 + for (int i = 0; i < numpixels; i++, in += 4) { + pixels[i] = COLOR_R8_G8_B8_TO_RGB565(in[0], in[1], in[2]); + } + } else if (mode_in->colortype == LCT_GREY && mode_in->bitdepth == 8) { + // GRAYSCALE -> RGB565 + for (int i = 0; i < numpixels; i++, in++) { + pixels[i] = COLOR_R8_G8_B8_TO_RGB565(in[0], in[0], in[0]); + } + } else { + error = 56; // unsupported color mode conversion. + } + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *pixels = (pixel24_t *) out; + if (mode_in->colortype == LCT_RGB) { + // RGB888 -> RGB888 + memcpy(out, in, numpixels * 3); + } else if (mode_in->colortype == LCT_RGBA) { + // RGBA888 -> RGB888 + for (int i = 0; i < numpixels; i++, in += 4) { + pixels[i] = COLOR_R8_G8_B8_TO_RGB888(in[0], in[1], in[2]); + // pixels[i] = COLOR_R8_G8_B8_TO_RGB565(in[0], in[1], in[2]); + } + } else if (mode_in->colortype == LCT_GREY && mode_in->bitdepth == 8) { + // GRAYSCALE -> RGB888 + for (int i = 0; i < numpixels; i++, in++) { + pixels[i] = COLOR_GRAYSCALE_TO_RGB888(in[0]); + // COLOR_R8_G8_B8_TO_RGB888(in[0], in[0], in[0]); + } + } else { + error = 56; // unsupported color mode conversion. + } + break; + } + default: + error = 56; // unsupported color mode conversion. + break; + } + + } else { + error = 56; // unsupported color mode conversion. + } + return error; +} + +#if defined(IMLIB_ENABLE_PNG_ENCODER) +bool png_compress(image_t *src, image_t *dst) { + #if (TIME_PNG == 1) + mp_uint_t start = mp_hal_ticks_ms(); + #endif + + if (src->is_compressed) { + return true; + } + + // umm_init_x(fb_avail()); + + LodePNGState state; + lodepng_state_init(&state); + // Invoked on custom formats. + state.lodepng_convert = &lodepng_convert_cb; + // Faster compression. + state.encoder.zlibsettings.windowsize = 1024; + + switch (src->pixfmt) { + case PIXFORMAT_BINARY: + state.info_raw.bitdepth = 1; + state.info_raw.colortype = LCT_GREY; + + state.encoder.auto_convert = false; + state.info_png.color.bitdepth = 8; + state.info_png.color.colortype = LCT_GREY; + break; + case PIXFORMAT_GRAYSCALE: + state.info_raw.bitdepth = 8; + state.info_raw.colortype = LCT_GREY; + + state.encoder.auto_convert = false; + state.info_png.color.bitdepth = 8; + state.info_png.color.colortype = LCT_GREY; + break; + case PIXFORMAT_RGB565: + state.info_raw.bitdepth = 16; + state.info_raw.colortype = LCT_CUSTOM; + state.info_raw.customfmt = PIXFORMAT_RGB565; + + state.encoder.auto_convert = false; + state.info_png.color.bitdepth = 8; + state.info_png.color.colortype = LCT_RGB; + break; + case PIXFORMAT_RGB888: + state.info_raw.bitdepth = 24; + state.info_raw.colortype = LCT_CUSTOM; + state.info_raw.customfmt = PIXFORMAT_RGB888; + + state.encoder.auto_convert = false; + state.info_png.color.bitdepth = 8; + state.info_png.color.colortype = LCT_RGB; + break; + case PIXFORMAT_YUV_ANY: + imlib_printf(0, "PIXFORMAT_YUV_ANY Input format is not supported"); + return 0; + // mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Input format is not supported")); + break; + case PIXFORMAT_BAYER_ANY: + imlib_printf(0, "PIXFORMAT_BAYER_ANY Input format is not supported"); + return 0; + // mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("Input format is not supported")); + break; + } + + size_t png_size = 0; + uint8_t *png_data = NULL; + unsigned error = lodepng_encode(&png_data, &png_size, src->data, src->w, src->h, &state); + lodepng_state_cleanup(&state); + if (error) { + // mp_raise_msg(&mp_type_RuntimeError, (mp_rom_error_text_t) lodepng_error_text(error)); + imlib_printf(0, "lodepng_error_text"); + return 0; + } + + if (dst->data == NULL) { + dst->data = png_data; + dst->size = png_size; + // fb_alloc() memory ill be free'd by called. + } else { + if (image_size(dst) <= png_size) { + dst->size = png_size; + memcpy(dst->data, png_data, png_size); + } else { + imlib_printf(0, "Failed to compress image in place"); + // mp_raise_msg_varg(&mp_type_RuntimeError, + // MP_ERROR_TEXT("Failed to compress image in place")); + } + // free fb_alloc() memory used for umm_init_x(). + // fb_free(); // umm_init_x();// 需要查询所有内存申请函数 + } + + #if (TIME_PNG == 1) + printf("time: %u ms\n", mp_hal_ticks_ms() - start); + #endif + + return false; +} +#endif // IMLIB_ENABLE_PNG_ENCODER + +#if defined(IMLIB_ENABLE_PNG_DECODER) +void png_decompress(image_t *dst, image_t *src) { + #if (TIME_PNG == 1) + mp_uint_t start = mp_hal_ticks_ms(); + #endif + + // umm_init_x(fb_avail()); + + LodePNGState state; + lodepng_state_init(&state); + // Invoked on custom formats. + state.lodepng_convert = &lodepng_convert_cb; + + switch (dst->pixfmt) { + case PIXFORMAT_BINARY: + state.info_raw.bitdepth = 1; + state.info_raw.colortype = LCT_GREY; + break; + case PIXFORMAT_GRAYSCALE: + state.info_raw.bitdepth = 8; + state.info_raw.colortype = LCT_GREY; + break; + case PIXFORMAT_RGB565: + state.info_raw.bitdepth = 16; + state.info_raw.colortype = LCT_CUSTOM; + state.info_raw.customfmt = PIXFORMAT_RGB565; + break; + case PIXFORMAT_RGB888: + state.info_raw.bitdepth = 24; + state.info_raw.colortype = LCT_CUSTOM; + state.info_raw.customfmt = PIXFORMAT_RGB888; + break; + } + + uint8_t *png_data = NULL; + uint32_t img_size = image_size(dst); + unsigned error = lodepng_decode(&png_data, (unsigned *) &dst->w, (unsigned *) &dst->h, &state, src->data, src->size); + lodepng_state_cleanup(&state); + if (error) { + // mp_raise_msg(&mp_type_RuntimeError, (mp_rom_error_text_t) lodepng_error_text(error)); + imlib_printf(0, "lodepng_error_text"); + return 0; + } + + uint32_t new_img_size = image_size(dst); + if (new_img_size <= img_size) { + memcpy(dst->data, png_data, new_img_size); + } else { + imlib_printf(0, "Failed to compress image in place"); + // mp_raise_msg_varg(&mp_type_RuntimeError, + // MP_ERROR_TEXT("Failed to compress image in place")); + } + + // free fb_alloc() memory used for umm_init_x(). + // fb_free(); // umm_init_x();// 需要查询所有内存申请函数 + + #if (TIME_PNG == 1) + printf("time: %u ms\n", mp_hal_ticks_ms() - start); + #endif +} +#endif // IMLIB_ENABLE_PNG_DECODER +#endif // IMLIB_ENABLE_PNG_ENCODER || IMLIB_ENABLE_PNG_DECODER + + +#if !defined(IMLIB_ENABLE_PNG_ENCODER) +bool png_compress(image_t *src, image_t *dst) { + imlib_printf(0, "PNG encoder is not enabled"); + // mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("PNG encoder is not enabled")); +} +#endif + +#if !defined(IMLIB_ENABLE_PNG_DECODER) +void png_decompress(image_t *dst, image_t *src) { + imlib_printf(0, "PNG decoder is not enabled"); + // mp_raise_msg_varg(&mp_type_RuntimeError, MP_ERROR_TEXT("PNG decoder is not enabled")); +} +#endif + +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) +// This function inits the geometry values of an image. +void png_read_geometry(FIL *fp, image_t *img, const char *path, png_read_settings_t *rs) { + uint32_t header; + file_seek(fp, 12); // start of IHDR + read_long(fp, &header); + if (header == 0x52444849) { + // IHDR + uint32_t width, height; + read_long(fp, &width); + read_long(fp, &height); + width = __builtin_bswap32(width); + height = __builtin_bswap32(height); + + rs->png_w = width; + rs->png_h = height; + rs->png_size = IMLIB_IMAGE_MAX_SIZE(file_fsize(fp)); + + img->w = rs->png_w; + img->h = rs->png_h; + img->size = rs->png_size; + img->pixfmt = PIXFORMAT_PNG; + } else { + ff_file_corrupted(fp); + } +} + +// This function reads the pixel values of an image. +void png_read_pixels(FIL *fp, image_t *img) { + file_seek(fp, 0); + read_data(fp, img->pixels, img->size); +} + +void png_read(image_t *img, const char *path) { + FIL fp; + png_read_settings_t rs; + + file_read_open(&fp, path); + + // Do not use file_buffer_on() here. + png_read_geometry(&fp, img, path, &rs); + + if (!img->pixels) { + img->pixels = xalloc(img->size); + } + + png_read_pixels(&fp, img); + file_close(&fp); +} + +void png_write(image_t *img, const char *path) { + FIL fp; + file_write_open(&fp, path); + if (img->pixfmt == PIXFORMAT_PNG) { + write_data(&fp, img->pixels, img->size); + } else { + image_t out = { .w = img->w, .h = img->h, .pixfmt = PIXFORMAT_PNG, .size = 0, .pixels = NULL }; // alloc in png compress + png_compress(img, &out); + write_data(&fp, out.pixels, out.size); + // fb_free(); // frees alloc in png_compress() + } + file_close(&fp); +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO) diff --git a/github_source/minicv2/src/point.c b/github_source/minicv2/src/point.c new file mode 100644 index 0000000..61f5565 --- /dev/null +++ b/github_source/minicv2/src/point.c @@ -0,0 +1,33 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Point functions. + */ +#include "imlib.h" +// #include "xalloc.h" + +point_t *point_alloc(int16_t x, int16_t y) +{ + point_t *p = xalloc(sizeof(point_t)); + p->x = x; + p->y = y; + return p; +} + +bool point_equal(point_t *p1, point_t *p2) +{ + return ((p1->x==p2->x)&&(p1->y==p2->y)); +} + +float point_distance(point_t *p1, point_t *p2) +{ + float sum=0.0f; + sum += (p1->x - p2->x) * (p1->x - p2->x); + sum += (p1->y - p2->y) * (p1->y - p2->y); + return fast_sqrtf(sum); +} diff --git a/github_source/minicv2/src/pool.c b/github_source/minicv2/src/pool.c new file mode 100644 index 0000000..fbfc58f --- /dev/null +++ b/github_source/minicv2/src/pool.c @@ -0,0 +1,200 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013-2016 Kwabena W. Agyeman + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Image pooling. + * + */ +#include "imlib.h" + +#ifdef IMLIB_ENABLE_MIDPOINT_POOLING +void imlib_midpoint_pool(image_t *img_i, image_t *img_o, int x_div, int y_div, const int bias) +{ + int min_bias = (256-bias); + int max_bias = bias; + switch (img_i->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = 0, yy = img_i->h / y_div, yyy = (img_i->h % y_div) / 2; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img_o, y); + for (int x = 0, xx = img_i->w / x_div, xxx = (img_i->w % x_div) / 2; x < xx; x++) { + int min = COLOR_BINARY_MAX, max = COLOR_BINARY_MIN; + for (int i = 0; i < y_div; i++) { + for (int j = 0; j < x_div; j++) { + int pixel = IMAGE_GET_BINARY_PIXEL(img_i, xxx + (x * x_div) + j, yyy + (y * y_div) + i); + min = IM_MIN(min, pixel); + max = IM_MAX(max, pixel); + } + } + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, + ((min*min_bias)+(max*max_bias))>>8); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = 0, yy = img_i->h, yyy = (img_i->h % y_div) / 2 / y_div; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img_o, y); + for (int x = 0, xx = img_i->w / x_div, xxx = (img_i->w % x_div) / 2; x < xx; x++) { + int min = COLOR_GRAYSCALE_MAX, max = COLOR_GRAYSCALE_MIN; + for (int i = 0; i < y_div; i++) { + for (int j = 0; j < x_div; j++) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL(img_i, xxx + (x * x_div) + j, yyy + (y * y_div) + i); + min = IM_MIN(min, pixel); + max = IM_MAX(max, pixel); + } + } + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, + ((min*min_bias)+(max*max_bias))>>8); + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = 0, yy = img_i->h / y_div, yyy = (img_i->h % y_div) / 2; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img_o, y); + for (int x = 0, xx = img_i->w / x_div, xxx = (img_i->w % x_div) / 2; x < xx; x++) { + int r_min = COLOR_R5_MAX, r_max = COLOR_R5_MIN; + int g_min = COLOR_G6_MAX, g_max = COLOR_G6_MIN; + int b_min = COLOR_B5_MAX, b_max = COLOR_B5_MIN; + for (int i = 0; i < y_div; i++) { + for (int j = 0; j < x_div; j++) { + const uint16_t pixel = IM_GET_RGB565_PIXEL(img_i, xxx + (x * x_div) + j, yyy + (y * y_div) + i); + int r = COLOR_RGB565_TO_R5(pixel); + int g = COLOR_RGB565_TO_G6(pixel); + int b = COLOR_RGB565_TO_B5(pixel); + r_min = IM_MIN(r_min, r); + r_max = IM_MAX(r_max, r); + g_min = IM_MIN(g_min, g); + g_max = IM_MAX(g_max, g); + b_min = IM_MIN(b_min, b); + b_max = IM_MAX(b_max, b); + } + } + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, + COLOR_R5_G6_B5_TO_RGB565(((r_min*min_bias)+(r_max*max_bias))>>8, + ((g_min*min_bias)+(g_max*max_bias))>>8, + ((b_min*min_bias)+(b_max*max_bias))>>8)); + } + } + break; + } + case PIXFORMAT_RGB888: { + for (int y = 0, yy = img_i->h / y_div, yyy = (img_i->h % y_div) / 2; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img_o, y); + for (int x = 0, xx = img_i->w / x_div, xxx = (img_i->w % x_div) / 2; x < xx; x++) { + int r_min = COLOR_R8_MAX, r_max = COLOR_R8_MIN; + int g_min = COLOR_G8_MAX, g_max = COLOR_G8_MIN; + int b_min = COLOR_B8_MAX, b_max = COLOR_B8_MIN; + for (int i = 0; i < y_div; i++) { + for (int j = 0; j < x_div; j++) { + const uint32_t pixel = IM_GET_RGB888_PIXEL(img_i, xxx + (x * x_div) + j, yyy + (y * y_div) + i); + int r = COLOR_RGB888_TO_R8(pixel); + int g = COLOR_RGB888_TO_G8(pixel); + int b = COLOR_RGB888_TO_B8(pixel); + r_min = IM_MIN(r_min, r); + r_max = IM_MAX(r_max, r); + g_min = IM_MIN(g_min, g); + g_max = IM_MAX(g_max, g); + b_min = IM_MIN(b_min, b); + b_max = IM_MAX(b_max, b); + } + } + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr, x, + COLOR_R8_G8_B8_TO_RGB888(((r_min*min_bias)+(r_max*max_bias))>>8, + ((g_min*min_bias)+(g_max*max_bias))>>8, + ((b_min*min_bias)+(b_max*max_bias))>>8)); + } + } + break; + } + default: { + break; + } + } +} +#endif // IMLIB_ENABLE_MIDPOINT_POOLING + +#ifdef IMLIB_ENABLE_MEAN_POOLING +void imlib_mean_pool(image_t *img_i, image_t *img_o, int x_div, int y_div) +{ + int n = x_div * y_div; + switch (img_i->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = 0, yy = img_i->h / y_div, yyy = (img_i->h % y_div) / 2; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img_o, y); + for (int x = 0, xx = img_i->w / x_div, xxx = (img_i->w % x_div) / 2; x < xx; x++) { + int acc = 0; + for (int i = 0; i < y_div; i++) { + for (int j = 0; j < x_div; j++) { + int pixel = IMAGE_GET_BINARY_PIXEL(img_i, xxx + (x * x_div) + j, yyy + (y * y_div) + i); + acc += pixel; + } + } + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr, x, acc / n); + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = 0, yy = img_i->h / y_div, yyy = (img_i->h % y_div) / 2; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img_o, y); + for (int x = 0, xx = img_i->w / x_div, xxx = (img_i->w % x_div) / 2; x < xx; x++) { + int acc = 0; + for (int i = 0; i < y_div; i++) { + for (int j = 0; j < x_div; j++) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL(img_i, xxx + (x * x_div) + j, yyy + (y * y_div) + i); + acc += pixel; + } + } + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr, x, acc / n); + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = 0, yy = img_i->h / y_div, yyy = (img_i->h % y_div) / 2; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img_o, y); + for (int x = 0, xx = img_i->w / x_div, xxx = (img_i->w % x_div) / 2; x < xx; x++) { + int r_acc = 0; + int g_acc = 0; + int b_acc = 0; + for (int i = 0; i < y_div; i++) { + for (int j = 0; j < x_div; j++) { + int pixel = IMAGE_GET_RGB565_PIXEL(img_i, xxx + (x * x_div) + j, yyy + (y * y_div) + i); + r_acc += COLOR_RGB565_TO_R5(pixel); + g_acc += COLOR_RGB565_TO_G6(pixel); + b_acc += COLOR_RGB565_TO_B5(pixel); + } + } + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr, x, COLOR_R5_G6_B5_TO_RGB565(r_acc / n, g_acc / n, b_acc / n)); + } + } + break; + } + case PIXFORMAT_RGB888: { + for (int y = 0, yy = img_i->h / y_div, yyy = (img_i->h % y_div) / 2; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img_o, y); + for (int x = 0, xx = img_i->w / x_div, xxx = (img_i->w % x_div) / 2; x < xx; x++) { + int r_acc = 0; + int g_acc = 0; + int b_acc = 0; + for (int i = 0; i < y_div; i++) { + for (int j = 0; j < x_div; j++) { + int pixel = IMAGE_GET_RGB888_PIXEL(img_i, xxx + (x * x_div) + j, yyy + (y * y_div) + i); + r_acc += COLOR_RGB888_TO_R8(pixel); + g_acc += COLOR_RGB888_TO_G8(pixel); + b_acc += COLOR_RGB888_TO_B8(pixel); + } + } + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr, x, COLOR_R8_G8_B8_TO_RGB888(r_acc / n, g_acc / n, b_acc / n)); + } + } + break; + } + default: { + break; + } + } +} +#endif // IMLIB_ENABLE_MEAN_POOLING diff --git a/github_source/minicv2/src/ppm.c b/github_source/minicv2/src/ppm.c new file mode 100644 index 0000000..4a8aa56 --- /dev/null +++ b/github_source/minicv2/src/ppm.c @@ -0,0 +1,183 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * PPM/PGM reader/writer. + */ + +#include "imlib.h" +#if defined(IMLIB_ENABLE_IMAGE_FILE_IO) + +#include +// #include "py/obj.h" +// #include "py/runtime.h" + +#include "xalloc.h" +#include "imlib.h" +#include "ff_wrapper.h" + +static void read_int_reset(ppm_read_settings_t *rs) +{ + rs->read_int_c_valid = false; +} + +static void read_int(FIL *fp, uint32_t *i, ppm_read_settings_t *rs) +{ + enum { EAT_WHITESPACE, EAT_COMMENT, EAT_NUMBER } mode = EAT_WHITESPACE; + for(*i = 0;;) { + if (!rs->read_int_c_valid) { + if (file_tell_w_buf(fp) == file_size_w_buf(fp)) return; + read_byte(fp, &rs->read_int_c); + rs->read_int_c_valid = true; + } + if (mode == EAT_WHITESPACE) { + if (rs->read_int_c == '#') { + mode = EAT_COMMENT; + } else if (('0' <= rs->read_int_c) && (rs->read_int_c <= '9')) { + *i = rs->read_int_c - '0'; + mode = EAT_NUMBER; + } + } else if (mode == EAT_COMMENT) { + if ((rs->read_int_c == '\n') || (rs->read_int_c == '\r')) { + mode = EAT_WHITESPACE; + } + } else if (mode == EAT_NUMBER) { + if (('0' <= rs->read_int_c) && (rs->read_int_c <= '9')) { + *i = (*i * 10) + rs->read_int_c - '0'; + } else { + return; // read_int_c_valid==true on exit + } + } + rs->read_int_c_valid = false; + } +} + +// This function inits the geometry values of an image. +void ppm_read_geometry(FIL *fp, image_t *img, const char *path, ppm_read_settings_t *rs) +{ + read_int_reset(rs); + read_byte_expect(fp, 'P'); + read_byte(fp, &rs->ppm_fmt); + + if ((rs->ppm_fmt!='2') && (rs->ppm_fmt!='3') && (rs->ppm_fmt!='5') && (rs->ppm_fmt!='6')) { + ff_unsupported_format(fp); + } + + img->pixfmt = ((rs->ppm_fmt == '2') || (rs->ppm_fmt == '5')) ? PIXFORMAT_GRAYSCALE : PIXFORMAT_RGB565; + + read_int(fp, (uint32_t *) &img->w, rs); + read_int(fp, (uint32_t *) &img->h, rs); + + if ((img->w == 0) || (img->h == 0)) { + ff_file_corrupted(fp); + } + + uint32_t max; + read_int(fp, &max, rs); + if (max != 255) { + ff_unsupported_format(fp); + } +} + +// This function reads the pixel values of an image. +void ppm_read_pixels(FIL *fp, image_t *img, int n_lines, ppm_read_settings_t *rs) +{ + if (rs->ppm_fmt == '2') { + for (int i = 0; i < n_lines; i++) { + for (int j = 0; j < img->w; j++) { + uint32_t pixel; + read_int(fp, &pixel, rs); + IM_SET_GS_PIXEL(img, j, i, pixel); + } + } + } else if (rs->ppm_fmt == '3') { + for (int i = 0; i < n_lines; i++) { + for (int j = 0; j < img->w; j++) { + uint32_t r, g, b; + read_int(fp, &r, rs); + read_int(fp, &g, rs); + read_int(fp, &b, rs); + IM_SET_RGB565_PIXEL(img, j, i, COLOR_R8_G8_B8_TO_RGB565(r, g, b)); + } + } + } else if (rs->ppm_fmt == '5') { + read_data(fp, img->pixels, n_lines * img->w); + } else if (rs->ppm_fmt == '6') { + for (int i = 0; i < n_lines; i++) { + for (int j = 0; j < img->w; j++) { + uint8_t r, g, b; + read_byte(fp, &r); + read_byte(fp, &g); + read_byte(fp, &b); + IM_SET_RGB565_PIXEL(img, j, i, COLOR_R8_G8_B8_TO_RGB565(r, g, b)); + } + } + } +} + +void ppm_read(image_t *img, const char *path) +{ + FIL fp; + ppm_read_settings_t rs; + + file_read_open(&fp, path); + // file_buffer_on(&fp); + ppm_read_geometry(&fp, img, path, &rs); + + if (!img->pixels) { + img->pixels = xalloc(img->w * img->h * img->bpp); + } + ppm_read_pixels(&fp, img, img->h, &rs); + + // file_buffer_off(&fp); + file_close(&fp); +} + +void ppm_write_subimg(image_t *img, const char *path, rectangle_t *r) +{ + rectangle_t rect; + if (!rectangle_subimg(img, r, &rect)) { + ERR_PRINT("OSError:No intersection!"); + // mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("No intersection!")); + } + + FIL fp; + file_write_open(&fp, path); + // file_buffer_on(&fp); + if (IM_IS_GS(img)) { + char buffer[20]; // exactly big enough for 5-digit w/h + int len = snprintf(buffer, 20, "P5\n%d %d\n255\n", rect.w, rect.h); + write_data(&fp, buffer, len); + if ((rect.x == 0) && (rect.w == img->w)) { + write_data(&fp, // Super Fast - Zoom, Zoom! + img->pixels + (rect.y * img->w), + rect.w * rect.h); + } else { + for (int i = 0; i < rect.h; i++) { + write_data(&fp, img->pixels+((rect.y+i)*img->w)+rect.x, rect.w); + } + } + } else { + char buffer[20]; // exactly big enough for 5-digit w/h + int len = snprintf(buffer, 20, "P6\n%d %d\n255\n", rect.w, rect.h); + write_data(&fp, buffer, len); + for (int i = 0; i < rect.h; i++) { + for (int j = 0; j < rect.w; j++) { + int pixel = IM_GET_RGB565_PIXEL(img, (rect.x + j), (rect.y + i)); + char buff[3]; + buff[0] = COLOR_RGB565_TO_R8(pixel); + buff[1] = COLOR_RGB565_TO_G8(pixel); + buff[2] = COLOR_RGB565_TO_B8(pixel); + write_data(&fp, buff, 3); + } + } + } + // file_buffer_off(&fp); + + file_close(&fp); +} +#endif //IMLIB_ENABLE_IMAGE_FILE_IO diff --git a/github_source/minicv2/src/qrcode.c b/github_source/minicv2/src/qrcode.c new file mode 100644 index 0000000..cba3da5 --- /dev/null +++ b/github_source/minicv2/src/qrcode.c @@ -0,0 +1,3031 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * QR-code recognition library. + */ +#include "imlib.h" +#ifdef IMLIB_ENABLE_QRCODES +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "quirc.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +struct quirc; + +/* Obtain the library version string. */ +const char *quirc_version(void); + +/* Construct a new QR-code recognizer. This function will return NULL + * if sufficient memory could not be allocated. + */ +struct quirc *quirc_new(void); + +/* Destroy a QR-code recognizer. */ +void quirc_destroy(struct quirc *q); + +/* Resize the QR-code recognizer. The size of an image must be + * specified before codes can be analyzed. + * + * This function returns 0 on success, or -1 if sufficient memory could + * not be allocated. + */ +int quirc_resize(struct quirc *q, int w, int h); + +/* These functions are used to process images for QR-code recognition. + * quirc_begin() must first be called to obtain access to a buffer into + * which the input image should be placed. Optionally, the current + * width and height may be returned. + * + * After filling the buffer, quirc_end() should be called to process + * the image for QR-code recognition. The locations and content of each + * code may be obtained using accessor functions described below. + */ +uint8_t *quirc_begin(struct quirc *q, int *w, int *h); +void quirc_end(struct quirc *q); + +/* This structure describes a location in the input image buffer. */ +struct quirc_point { + int x; + int y; +}; + +/* This enum describes the various decoder errors which may occur. */ +typedef enum { + QUIRC_SUCCESS = 0, + QUIRC_ERROR_INVALID_GRID_SIZE, + QUIRC_ERROR_INVALID_VERSION, + QUIRC_ERROR_FORMAT_ECC, + QUIRC_ERROR_DATA_ECC, + QUIRC_ERROR_UNKNOWN_DATA_TYPE, + QUIRC_ERROR_DATA_OVERFLOW, + QUIRC_ERROR_DATA_UNDERFLOW +} quirc_decode_error_t; + +/* Return a string error message for an error code. */ +const char *quirc_strerror(quirc_decode_error_t err); + +/* Limits on the maximum size of QR-codes and their content. */ +#define QUIRC_MAX_BITMAP 3917 +#define QUIRC_MAX_PAYLOAD 8896 + +/* QR-code ECC types. */ +#define QUIRC_ECC_LEVEL_M 0 +#define QUIRC_ECC_LEVEL_L 1 +#define QUIRC_ECC_LEVEL_H 2 +#define QUIRC_ECC_LEVEL_Q 3 + +/* QR-code data types. */ +#define QUIRC_DATA_TYPE_NUMERIC 1 +#define QUIRC_DATA_TYPE_ALPHA 2 +#define QUIRC_DATA_TYPE_BYTE 4 +#define QUIRC_DATA_TYPE_KANJI 8 + +/* Common character encodings */ +#define QUIRC_ECI_ISO_8859_1 1 +#define QUIRC_ECI_IBM437 2 +#define QUIRC_ECI_ISO_8859_2 4 +#define QUIRC_ECI_ISO_8859_3 5 +#define QUIRC_ECI_ISO_8859_4 6 +#define QUIRC_ECI_ISO_8859_5 7 +#define QUIRC_ECI_ISO_8859_6 8 +#define QUIRC_ECI_ISO_8859_7 9 +#define QUIRC_ECI_ISO_8859_8 10 +#define QUIRC_ECI_ISO_8859_9 11 +#define QUIRC_ECI_WINDOWS_874 13 +#define QUIRC_ECI_ISO_8859_13 15 +#define QUIRC_ECI_ISO_8859_15 17 +#define QUIRC_ECI_SHIFT_JIS 20 +#define QUIRC_ECI_UTF_8 26 + +/* This structure is used to return information about detected QR codes + * in the input image. + */ +struct quirc_code { + /* The four corners of the QR-code, from top left, clockwise */ + struct quirc_point corners[4]; + + /* The number of cells across in the QR-code. The cell bitmap + * is a bitmask giving the actual values of cells. If the cell + * at (x, y) is black, then the following bit is set: + * + * cell_bitmap[i >> 3] & (1 << (i & 7)) + * + * where i = (y * size) + x. + */ + int size; + uint8_t cell_bitmap[QUIRC_MAX_BITMAP]; +}; + +/* This structure holds the decoded QR-code data */ +struct quirc_data { + /* Various parameters of the QR-code. These can mostly be + * ignored if you only care about the data. + */ + int version; + int ecc_level; + int mask; + + /* This field is the highest-valued data type found in the QR + * code. + */ + int data_type; + + /* Data payload. For the Kanji datatype, payload is encoded as + * Shift-JIS. For all other datatypes, payload is ASCII text. + */ + uint8_t payload[QUIRC_MAX_PAYLOAD]; + int payload_len; + + /* ECI assignment number */ + uint32_t eci; +}; + +/* Return the number of QR-codes identified in the last processed + * image. + */ +int quirc_count(const struct quirc *q); + +/* Extract the QR-code specified by the given index. */ +void quirc_extract(const struct quirc *q, int index, + struct quirc_code *code); + +/* Decode a QR-code, returning the payload data. */ +quirc_decode_error_t quirc_decode(const struct quirc_code *code, + struct quirc_data *data); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "quirc_internal.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define QUIRC_PIXEL_WHITE 0 +#define QUIRC_PIXEL_BLACK 1 +#define QUIRC_PIXEL_REGION 2 + +#ifndef QUIRC_MAX_REGIONS +#define QUIRC_MAX_REGIONS 254 +#endif + +#define QUIRC_MAX_CAPSTONES 32 +#define QUIRC_MAX_GRIDS 8 + +#define QUIRC_PERSPECTIVE_PARAMS 8 + +#if QUIRC_MAX_REGIONS < UINT8_MAX +typedef uint8_t quirc_pixel_t; +#elif QUIRC_MAX_REGIONS < UINT16_MAX +typedef uint16_t quirc_pixel_t; +#else +#error "QUIRC_MAX_REGIONS > 65534 is not supported" +#endif + +struct quirc_region { + struct quirc_point seed; + int count; + int capstone; +}; + +struct quirc_capstone { + int ring; + int stone; + + struct quirc_point corners[4]; + struct quirc_point center; + float c[QUIRC_PERSPECTIVE_PARAMS]; + + int qr_grid; +}; + +struct quirc_grid { + /* Capstone indices */ + int caps[3]; + + /* Alignment pattern region and corner */ + int align_region; + struct quirc_point align; + + /* Timing pattern endpoints */ + struct quirc_point tpep[3]; + int hscan; + int vscan; + + /* Grid size and perspective transform */ + int grid_size; + float c[QUIRC_PERSPECTIVE_PARAMS]; +}; + +struct quirc { + uint8_t *image; + quirc_pixel_t *pixels; + int w; + int h; + + int num_regions; + struct quirc_region regions[QUIRC_MAX_REGIONS]; + + int num_capstones; + struct quirc_capstone capstones[QUIRC_MAX_CAPSTONES]; + + int num_grids; + struct quirc_grid grids[QUIRC_MAX_GRIDS]; +}; + +/************************************************************************ + * QR-code version information database + */ + +#define QUIRC_MAX_VERSION 40 +#define QUIRC_MAX_ALIGNMENT 7 + +struct quirc_rs_params { + uint8_t bs; /* Small block size */ + uint8_t dw; /* Small data words */ + uint8_t ns; /* Number of small blocks */ +}; + +struct quirc_version_info { + uint16_t data_bytes; + uint8_t apat[QUIRC_MAX_ALIGNMENT]; + struct quirc_rs_params ecc[4]; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "version_db.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +const struct quirc_version_info quirc_version_db[QUIRC_MAX_VERSION + 1] = { + {0}, + { /* Version 1 */ + .data_bytes = 26, + .apat = {0}, + .ecc = { + {.bs = 26, .dw = 16, .ns = 1}, + {.bs = 26, .dw = 19, .ns = 1}, + {.bs = 26, .dw = 9, .ns = 1}, + {.bs = 26, .dw = 13, .ns = 1} + } + }, + { /* Version 2 */ + .data_bytes = 44, + .apat = {6, 18, 0}, + .ecc = { + {.bs = 44, .dw = 28, .ns = 1}, + {.bs = 44, .dw = 34, .ns = 1}, + {.bs = 44, .dw = 16, .ns = 1}, + {.bs = 44, .dw = 22, .ns = 1} + } + }, + { /* Version 3 */ + .data_bytes = 70, + .apat = {6, 22, 0}, + .ecc = { + {.bs = 70, .dw = 44, .ns = 1}, + {.bs = 70, .dw = 55, .ns = 1}, + {.bs = 35, .dw = 13, .ns = 2}, + {.bs = 35, .dw = 17, .ns = 2} + } + }, + { /* Version 4 */ + .data_bytes = 100, + .apat = {6, 26, 0}, + .ecc = { + {.bs = 50, .dw = 32, .ns = 2}, + {.bs = 100, .dw = 80, .ns = 1}, + {.bs = 25, .dw = 9, .ns = 4}, + {.bs = 50, .dw = 24, .ns = 2} + } + }, + { /* Version 5 */ + .data_bytes = 134, + .apat = {6, 30, 0}, + .ecc = { + {.bs = 67, .dw = 43, .ns = 2}, + {.bs = 134, .dw = 108, .ns = 1}, + {.bs = 33, .dw = 11, .ns = 2}, + {.bs = 33, .dw = 15, .ns = 2} + } + }, + { /* Version 6 */ + .data_bytes = 172, + .apat = {6, 34, 0}, + .ecc = { + {.bs = 43, .dw = 27, .ns = 4}, + {.bs = 86, .dw = 68, .ns = 2}, + {.bs = 43, .dw = 15, .ns = 4}, + {.bs = 43, .dw = 19, .ns = 4} + } + }, + { /* Version 7 */ + .data_bytes = 196, + .apat = {6, 22, 38, 0}, + .ecc = { + {.bs = 49, .dw = 31, .ns = 4}, + {.bs = 98, .dw = 78, .ns = 2}, + {.bs = 39, .dw = 13, .ns = 4}, + {.bs = 32, .dw = 14, .ns = 2} + } + }, + { /* Version 8 */ + .data_bytes = 242, + .apat = {6, 24, 42, 0}, + .ecc = { + {.bs = 60, .dw = 38, .ns = 2}, + {.bs = 121, .dw = 97, .ns = 2}, + {.bs = 40, .dw = 14, .ns = 4}, + {.bs = 40, .dw = 18, .ns = 4} + } + }, + { /* Version 9 */ + .data_bytes = 292, + .apat = {6, 26, 46, 0}, + .ecc = { + {.bs = 58, .dw = 36, .ns = 3}, + {.bs = 146, .dw = 116, .ns = 2}, + {.bs = 36, .dw = 12, .ns = 4}, + {.bs = 36, .dw = 16, .ns = 4} + } + }, + { /* Version 10 */ + .data_bytes = 346, + .apat = {6, 28, 50, 0}, + .ecc = { + {.bs = 69, .dw = 43, .ns = 4}, + {.bs = 86, .dw = 68, .ns = 2}, + {.bs = 43, .dw = 15, .ns = 6}, + {.bs = 43, .dw = 19, .ns = 6} + } + }, + { /* Version 11 */ + .data_bytes = 404, + .apat = {6, 30, 54, 0}, + .ecc = { + {.bs = 80, .dw = 50, .ns = 1}, + {.bs = 101, .dw = 81, .ns = 4}, + {.bs = 36, .dw = 12, .ns = 3}, + {.bs = 50, .dw = 22, .ns = 4} + } + }, + { /* Version 12 */ + .data_bytes = 466, + .apat = {6, 32, 58, 0}, + .ecc = { + {.bs = 58, .dw = 36, .ns = 6}, + {.bs = 116, .dw = 92, .ns = 2}, + {.bs = 42, .dw = 14, .ns = 7}, + {.bs = 46, .dw = 20, .ns = 4} + } + }, + { /* Version 13 */ + .data_bytes = 532, + .apat = {6, 34, 62, 0}, + .ecc = { + {.bs = 59, .dw = 37, .ns = 8}, + {.bs = 133, .dw = 107, .ns = 4}, + {.bs = 33, .dw = 11, .ns = 12}, + {.bs = 44, .dw = 20, .ns = 8} + } + }, + { /* Version 14 */ + .data_bytes = 581, + .apat = {6, 26, 46, 66, 0}, + .ecc = { + {.bs = 64, .dw = 40, .ns = 4}, + {.bs = 145, .dw = 115, .ns = 3}, + {.bs = 36, .dw = 12, .ns = 11}, + {.bs = 36, .dw = 16, .ns = 11} + } + }, + { /* Version 15 */ + .data_bytes = 655, + .apat = {6, 26, 48, 70, 0}, + .ecc = { + {.bs = 65, .dw = 41, .ns = 5}, + {.bs = 109, .dw = 87, .ns = 5}, + {.bs = 36, .dw = 12, .ns = 11}, + {.bs = 54, .dw = 24, .ns = 5} + } + }, + { /* Version 16 */ + .data_bytes = 733, + .apat = {6, 26, 50, 74, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ns = 7}, + {.bs = 122, .dw = 98, .ns = 5}, + {.bs = 45, .dw = 15, .ns = 3}, + {.bs = 43, .dw = 19, .ns = 15} + } + }, + { /* Version 17 */ + .data_bytes = 815, + .apat = {6, 30, 54, 78, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 10}, + {.bs = 135, .dw = 107, .ns = 1}, + {.bs = 42, .dw = 14, .ns = 2}, + {.bs = 50, .dw = 22, .ns = 1} + } + }, + { /* Version 18 */ + .data_bytes = 901, + .apat = {6, 30, 56, 82, 0}, + .ecc = { + {.bs = 69, .dw = 43, .ns = 9}, + {.bs = 150, .dw = 120, .ns = 5}, + {.bs = 42, .dw = 14, .ns = 2}, + {.bs = 50, .dw = 22, .ns = 17} + } + }, + { /* Version 19 */ + .data_bytes = 991, + .apat = {6, 30, 58, 86, 0}, + .ecc = { + {.bs = 70, .dw = 44, .ns = 3}, + {.bs = 141, .dw = 113, .ns = 3}, + {.bs = 39, .dw = 13, .ns = 9}, + {.bs = 47, .dw = 21, .ns = 17} + } + }, + { /* Version 20 */ + .data_bytes = 1085, + .apat = {6, 34, 62, 90, 0}, + .ecc = { + {.bs = 67, .dw = 41, .ns = 3}, + {.bs = 135, .dw = 107, .ns = 3}, + {.bs = 43, .dw = 15, .ns = 15}, + {.bs = 54, .dw = 24, .ns = 15} + } + }, + { /* Version 21 */ + .data_bytes = 1156, + .apat = {6, 28, 50, 72, 92, 0}, + .ecc = { + {.bs = 68, .dw = 42, .ns = 17}, + {.bs = 144, .dw = 116, .ns = 4}, + {.bs = 46, .dw = 16, .ns = 19}, + {.bs = 50, .dw = 22, .ns = 17} + } + }, + { /* Version 22 */ + .data_bytes = 1258, + .apat = {6, 26, 50, 74, 98, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 17}, + {.bs = 139, .dw = 111, .ns = 2}, + {.bs = 37, .dw = 13, .ns = 34}, + {.bs = 54, .dw = 24, .ns = 7} + } + }, + { /* Version 23 */ + .data_bytes = 1364, + .apat = {6, 30, 54, 78, 102, 0}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 4}, + {.bs = 151, .dw = 121, .ns = 4}, + {.bs = 45, .dw = 15, .ns = 16}, + {.bs = 54, .dw = 24, .ns = 11} + } + }, + { /* Version 24 */ + .data_bytes = 1474, + .apat = {6, 28, 54, 80, 106, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ns = 6}, + {.bs = 147, .dw = 117, .ns = 6}, + {.bs = 46, .dw = 16, .ns = 30}, + {.bs = 54, .dw = 24, .ns = 11} + } + }, + { /* Version 25 */ + .data_bytes = 1588, + .apat = {6, 32, 58, 84, 110, 0}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 8}, + {.bs = 132, .dw = 106, .ns = 8}, + {.bs = 45, .dw = 15, .ns = 22}, + {.bs = 54, .dw = 24, .ns = 7} + } + }, + { /* Version 26 */ + .data_bytes = 1706, + .apat = {6, 30, 58, 86, 114, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 19}, + {.bs = 142, .dw = 114, .ns = 10}, + {.bs = 46, .dw = 16, .ns = 33}, + {.bs = 50, .dw = 22, .ns = 28} + } + }, + { /* Version 27 */ + .data_bytes = 1828, + .apat = {6, 34, 62, 90, 118, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ns = 22}, + {.bs = 152, .dw = 122, .ns = 8}, + {.bs = 45, .dw = 15, .ns = 12}, + {.bs = 53, .dw = 23, .ns = 8} + } + }, + { /* Version 28 */ + .data_bytes = 1921, + .apat = {6, 26, 50, 74, 98, 122, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ns = 3}, + {.bs = 147, .dw = 117, .ns = 3}, + {.bs = 45, .dw = 15, .ns = 11}, + {.bs = 54, .dw = 24, .ns = 4} + } + }, + { /* Version 29 */ + .data_bytes = 2051, + .apat = {6, 30, 54, 78, 102, 126, 0}, + .ecc = { + {.bs = 73, .dw = 45, .ns = 21}, + {.bs = 146, .dw = 116, .ns = 7}, + {.bs = 45, .dw = 15, .ns = 19}, + {.bs = 53, .dw = 23, .ns = 1} + } + }, + { /* Version 30 */ + .data_bytes = 2185, + .apat = {6, 26, 52, 78, 104, 130, 0}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 19}, + {.bs = 145, .dw = 115, .ns = 5}, + {.bs = 45, .dw = 15, .ns = 23}, + {.bs = 54, .dw = 24, .ns = 15} + } + }, + { /* Version 31 */ + .data_bytes = 2323, + .apat = {6, 30, 56, 82, 108, 134, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 2}, + {.bs = 145, .dw = 115, .ns = 13}, + {.bs = 45, .dw = 15, .ns = 23}, + {.bs = 54, .dw = 24, .ns = 42} + } + }, + { /* Version 32 */ + .data_bytes = 2465, + .apat = {6, 34, 60, 86, 112, 138, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 10}, + {.bs = 145, .dw = 115, .ns = 17}, + {.bs = 45, .dw = 15, .ns = 19}, + {.bs = 54, .dw = 24, .ns = 10} + } + }, + { /* Version 33 */ + .data_bytes = 2611, + .apat = {6, 30, 58, 86, 114, 142, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 14}, + {.bs = 145, .dw = 115, .ns = 17}, + {.bs = 45, .dw = 15, .ns = 11}, + {.bs = 54, .dw = 24, .ns = 29} + } + }, + { /* Version 34 */ + .data_bytes = 2761, + .apat = {6, 34, 62, 90, 118, 146, 0}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 14}, + {.bs = 145, .dw = 115, .ns = 13}, + {.bs = 46, .dw = 16, .ns = 59}, + {.bs = 54, .dw = 24, .ns = 44} + } + }, + { /* Version 35 */ + .data_bytes = 2876, + .apat = {6, 30, 54, 78, 102, 126, 150}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 12}, + {.bs = 151, .dw = 121, .ns = 12}, + {.bs = 45, .dw = 15, .ns = 22}, + {.bs = 54, .dw = 24, .ns = 39} + } + }, + { /* Version 36 */ + .data_bytes = 3034, + .apat = {6, 24, 50, 76, 102, 128, 154}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 6}, + {.bs = 151, .dw = 121, .ns = 6}, + {.bs = 45, .dw = 15, .ns = 2}, + {.bs = 54, .dw = 24, .ns = 46} + } + }, + { /* Version 37 */ + .data_bytes = 3196, + .apat = {6, 28, 54, 80, 106, 132, 158}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 29}, + {.bs = 152, .dw = 122, .ns = 17}, + {.bs = 45, .dw = 15, .ns = 24}, + {.bs = 54, .dw = 24, .ns = 49} + } + }, + { /* Version 38 */ + .data_bytes = 3362, + .apat = {6, 32, 58, 84, 110, 136, 162}, + .ecc = { + {.bs = 74, .dw = 46, .ns = 13}, + {.bs = 152, .dw = 122, .ns = 4}, + {.bs = 45, .dw = 15, .ns = 42}, + {.bs = 54, .dw = 24, .ns = 48} + } + }, + { /* Version 39 */ + .data_bytes = 3532, + .apat = {6, 26, 54, 82, 110, 138, 166}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 40}, + {.bs = 147, .dw = 117, .ns = 20}, + {.bs = 45, .dw = 15, .ns = 10}, + {.bs = 54, .dw = 24, .ns = 43} + } + }, + { /* Version 40 */ + .data_bytes = 3706, + .apat = {6, 30, 58, 86, 114, 142, 170}, + .ecc = { + {.bs = 75, .dw = 47, .ns = 18}, + {.bs = 148, .dw = 118, .ns = 19}, + {.bs = 45, .dw = 15, .ns = 20}, + {.bs = 54, .dw = 24, .ns = 34} + } + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "indentify.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/* quirc - QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/************************************************************************ + * Linear algebra routines + */ + +static int line_intersect(const struct quirc_point *p0, + const struct quirc_point *p1, + const struct quirc_point *q0, + const struct quirc_point *q1, + struct quirc_point *r) +{ + /* (a, b) is perpendicular to line p */ + int a = -(p1->y - p0->y); + int b = p1->x - p0->x; + + /* (c, d) is perpendicular to line q */ + int c = -(q1->y - q0->y); + int d = q1->x - q0->x; + + /* e and f are dot products of the respective vectors with p and q */ + int e = a * p1->x + b * p1->y; + int f = c * q1->x + d * q1->y; + + /* Now we need to solve: + * [a b] [rx] [e] + * [c d] [ry] = [f] + * + * We do this by inverting the matrix and applying it to (e, f): + * [ d -b] [e] [rx] + * 1/det [-c a] [f] = [ry] + */ + int det = (a * d) - (b * c); + + if (!det) + return 0; + + r->x = (d * e - b * f) / det; + r->y = (-c * e + a * f) / det; + + return 1; +} + +static void perspective_setup(float *c, + const struct quirc_point *rect, + float w, float h) +{ + float x0 = rect[0].x; + float y0 = rect[0].y; + float x1 = rect[1].x; + float y1 = rect[1].y; + float x2 = rect[2].x; + float y2 = rect[2].y; + float x3 = rect[3].x; + float y3 = rect[3].y; + + float wden = w * (x2*y3 - x3*y2 + (x3-x2)*y1 + x1*(y2-y3)); + float hden = h * (x2*y3 + x1*(y2-y3) - x3*y2 + (x3-x2)*y1); + + c[0] = (x1*(x2*y3-x3*y2) + x0*(-x2*y3+x3*y2+(x2-x3)*y1) + + x1*(x3-x2)*y0) / wden; + c[1] = -(x0*(x2*y3+x1*(y2-y3)-x2*y1) - x1*x3*y2 + x2*x3*y1 + + (x1*x3-x2*x3)*y0) / hden; + c[2] = x0; + c[3] = (y0*(x1*(y3-y2)-x2*y3+x3*y2) + y1*(x2*y3-x3*y2) + + x0*y1*(y2-y3)) / wden; + c[4] = (x0*(y1*y3-y2*y3) + x1*y2*y3 - x2*y1*y3 + + y0*(x3*y2-x1*y2+(x2-x3)*y1)) / hden; + c[5] = y0; + c[6] = (x1*(y3-y2) + x0*(y2-y3) + (x2-x3)*y1 + (x3-x2)*y0) / wden; + c[7] = (-x2*y3 + x1*y3 + x3*y2 + x0*(y1-y2) - x3*y1 + (x2-x1)*y0) / + hden; +} + +static void perspective_map(const float *c, + float u, float v, struct quirc_point *ret) +{ + float den = c[6]*u + c[7]*v + 1.0; + float x = (c[0]*u + c[1]*v + c[2]) / den; + float y = (c[3]*u + c[4]*v + c[5]) / den; + + ret->x = fast_roundf(x); + ret->y = fast_roundf(y); +} + +static void perspective_unmap(const float *c, + const struct quirc_point *in, + float *u, float *v) +{ + float x = in->x; + float y = in->y; + float den = -c[0]*c[7]*y + c[1]*c[6]*y + (c[3]*c[7]-c[4]*c[6])*x + + c[0]*c[4] - c[1]*c[3]; + + *u = -(c[1]*(y-c[5]) - c[2]*c[7]*y + (c[5]*c[7]-c[4])*x + c[2]*c[4]) / + den; + *v = (c[0]*(y-c[5]) - c[2]*c[6]*y + (c[5]*c[6]-c[3])*x + c[2]*c[3]) / + den; +} + +/************************************************************************ + * Span-based floodfill routine + */ + +typedef void (*span_func_t)(void *user_data, int y, int left, int right); + +typedef struct xylf +{ + int16_t x, y, l, r; +} +xylf_t; + +static void lifo_enqueue_fast(lifo_t *ptr, void *data) +{ +// we know the structure size is 8 bytes, so don't waste time calling memcpy + uint32_t *d = (uint32_t *)(ptr->data + (ptr->len * ptr->data_len)); + uint32_t *s = (uint32_t *)data; +// memcpy(ptr->data + (ptr->len * ptr->data_len), data, ptr->data_len); + d[0] = s[0]; d[1] = s[1]; // copy 8 bytes + ptr->len += 1; +} + +static void lifo_dequeue_fast(lifo_t *ptr, void *data) +{ + // we know the structure size is 8 bytes, so don't waste time calling memcpy + uint32_t *s = (uint32_t *)(ptr->data + ((ptr->len-1) * ptr->data_len)); + uint32_t *d = (uint32_t *)data; +// if (data) { +// memcpy(data, ptr->data + ((ptr->len - 1) * ptr->data_len), ptr->data_len); +// } + d[0] = s[0]; d[1] = s[1]; // copy 8 bytes + ptr->len -= 1; +} + +static void flood_fill_seed(struct quirc *q, int x, int y, int from, int to, + span_func_t func, void *user_data, + int depth) +{ + (void) depth; // unused + uint8_t from8 = from, to8=to; + + lifo_t lifo; + size_t lifo_len; + lifo_alloc_all(&lifo, &lifo_len, sizeof(xylf_t)); + + for(;;) { + int left = x; + int right = x; + int i; + quirc_pixel_t *row = q->pixels + y * q->w; + + while (left > 0 && row[left - 1] == from8) + left--; + + while (right < q->w - 1 && row[right + 1] == from8) + right++; + + /* Fill the extent */ + for (i = left; i <= right; i++) + row[i] = to8; + + if (func) + func(user_data, y, left, right); + + for(;;) { + if (/*lifo_size(&lifo)*/ lifo.len < lifo_len) { + /* Seed new flood-fills */ + if (y > 0) { + row = q->pixels + (y - 1) * q->w; + + bool recurse = false; + for (i = left; i <= right; i++) + if (row[i] == from8) { + xylf_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + lifo_enqueue_fast(&lifo, &context); + x = i; + y = y - 1; + recurse = true; + break; + } + if (recurse) + break; + } + + if (y < q->h - 1) { + row = q->pixels + (y + 1) * q->w; + + bool recurse = false; + for (i = left; i <= right; i++) + if (row[i] == from8) { + xylf_t context; + context.x = x; + context.y = y; + context.l = left; + context.r = right; + lifo_enqueue_fast(&lifo, &context); + x = i; + y = y + 1; + recurse = true; + break; + } + if (recurse) + break; + } + } + + if (!lifo.len /*lifo_size(&lifo)*/) { + lifo_free(&lifo); + return; + } + + xylf_t context; + lifo_dequeue_fast(&lifo, &context); + x = context.x; + y = context.y; + left = context.l; + right = context.r; + } + } +} + +/************************************************************************ + * Adaptive thresholding + */ + +#define THRESHOLD_S_MIN 1 +#define THRESHOLD_S_DEN 8 +#define THRESHOLD_T 5 + +static void threshold(struct quirc *q) +{ + int x, y; + int avg_w = 0; + int avg_u = 0; + int threshold_s = q->w / THRESHOLD_S_DEN; + int fracmul, fracmul2; + quirc_pixel_t *row = q->pixels; + int width = q->w; + + /* + * Ensure a sane, non-zero value for threshold_s. + * + * threshold_s can be zero if the image width is small. We need to avoid + * SIGFPE as it will be used as divisor. + */ + if (threshold_s < THRESHOLD_S_MIN) + threshold_s = THRESHOLD_S_MIN; + + fracmul = (32768 * (threshold_s - 1)) / threshold_s; // to use multipy instead of divide (not too many bits or we'll overflow) + // to get the effect used below (a fraction of threshold_s-1/threshold_s + // The second constant is to reduce the averaged values to compare with the current pixel + fracmul2 = (0x100000 * (100 - THRESHOLD_T)) / (200 * threshold_s); // use as many bits as possible without overflowing + + for (y = 0; y < q->h; y++) { + int row_average[q->w]; + + memset(row_average, 0, sizeof(row_average)); + + for (x = 0; x < width; x++) { + int w, u; + + if (y & 1) { + w = x; + u = width - 1 - x; + } else { + w = width - 1 - x; + u = x; + } + +// avg_w = (avg_w * (threshold_s - 1)) / threshold_s + row[w]; +// avg_u = (avg_u * (threshold_s - 1)) / threshold_s + row[u]; + // The original mul/div operation sought to reduce the average value by a small fraction (e.g. 1/79) + // This mul/shift approximation achieves the same goal with only a small percentage difference + avg_w = ((avg_w * fracmul) >> 15) + row[w]; + avg_u = ((avg_u * fracmul) >> 15) + row[u]; + + row_average[w] += avg_w; + row_average[u] += avg_u; + } + + for (x = 0; x < width; x++) { + // if (row[x] < row_average[x] * (100 - THRESHOLD_T) / (200 * threshold_s)) + if (row[x] < ((row_average[x] * fracmul2) >> 20)) + row[x] = QUIRC_PIXEL_BLACK; + else + row[x] = QUIRC_PIXEL_WHITE; + } + + row += width; + } +} /* threshold() */ + +static void area_count(void *user_data, int y, int left, int right) +{ + ((struct quirc_region *)user_data)->count += right - left + 1; +} + +static int region_code(struct quirc *q, int x, int y) +{ + int pixel; + struct quirc_region *box; + int region; + + if (x < 0 || y < 0 || x >= q->w || y >= q->h) + return -1; + + pixel = q->pixels[y * q->w + x]; + + if (pixel >= QUIRC_PIXEL_REGION) + return pixel; + + if (pixel == QUIRC_PIXEL_WHITE) + return -1; + + if (q->num_regions >= QUIRC_MAX_REGIONS) + return -1; + + region = q->num_regions; + box = &q->regions[q->num_regions++]; + + memset(box, 0, sizeof(*box)); + + box->seed.x = x; + box->seed.y = y; + box->capstone = -1; + + flood_fill_seed(q, x, y, pixel, region, area_count, box, 0); + + return region; +} + +struct polygon_score_data { + struct quirc_point ref; + + int scores[4]; + struct quirc_point *corners; +}; + +static void find_one_corner(void *user_data, int y, int left, int right) +{ + struct polygon_score_data *psd = + (struct polygon_score_data *)user_data; + int xs[2] = {left, right}; + int dy = y - psd->ref.y; + int i; + + for (i = 0; i < 2; i++) { + int dx = xs[i] - psd->ref.x; + int d = dx * dx + dy * dy; + + if (d > psd->scores[0]) { + psd->scores[0] = d; + psd->corners[0].x = xs[i]; + psd->corners[0].y = y; + } + } +} + +static void find_other_corners(void *user_data, int y, int left, int right) +{ + struct polygon_score_data *psd = + (struct polygon_score_data *)user_data; + int xs[2] = {left, right}; + int i; + + for (i = 0; i < 2; i++) { + int up = xs[i] * psd->ref.x + y * psd->ref.y; + int right = xs[i] * -psd->ref.y + y * psd->ref.x; + int scores[4] = {up, right, -up, -right}; + int j; + + for (j = 0; j < 4; j++) { + if (scores[j] > psd->scores[j]) { + psd->scores[j] = scores[j]; + psd->corners[j].x = xs[i]; + psd->corners[j].y = y; + } + } + } +} + +static void find_region_corners(struct quirc *q, + int rcode, const struct quirc_point *ref, + struct quirc_point *corners) +{ + struct quirc_region *region = &q->regions[rcode]; + struct polygon_score_data psd; + int i; + + memset(&psd, 0, sizeof(psd)); + psd.corners = corners; + + memcpy(&psd.ref, ref, sizeof(psd.ref)); + psd.scores[0] = -1; + flood_fill_seed(q, region->seed.x, region->seed.y, + rcode, QUIRC_PIXEL_BLACK, + find_one_corner, &psd, 0); + + psd.ref.x = psd.corners[0].x - psd.ref.x; + psd.ref.y = psd.corners[0].y - psd.ref.y; + + for (i = 0; i < 4; i++) + memcpy(&psd.corners[i], ®ion->seed, + sizeof(psd.corners[i])); + + i = region->seed.x * psd.ref.x + region->seed.y * psd.ref.y; + psd.scores[0] = i; + psd.scores[2] = -i; + i = region->seed.x * -psd.ref.y + region->seed.y * psd.ref.x; + psd.scores[1] = i; + psd.scores[3] = -i; + + flood_fill_seed(q, region->seed.x, region->seed.y, + QUIRC_PIXEL_BLACK, rcode, + find_other_corners, &psd, 0); +} + +static void record_capstone(struct quirc *q, int ring, int stone) +{ + struct quirc_region *stone_reg = &q->regions[stone]; + struct quirc_region *ring_reg = &q->regions[ring]; + struct quirc_capstone *capstone; + int cs_index; + + if (q->num_capstones >= QUIRC_MAX_CAPSTONES) + return; + + cs_index = q->num_capstones; + capstone = &q->capstones[q->num_capstones++]; + + memset(capstone, 0, sizeof(*capstone)); + + capstone->qr_grid = -1; + capstone->ring = ring; + capstone->stone = stone; + stone_reg->capstone = cs_index; + ring_reg->capstone = cs_index; + + /* Find the corners of the ring */ + find_region_corners(q, ring, &stone_reg->seed, capstone->corners); + + /* Set up the perspective transform and find the center */ + perspective_setup(capstone->c, capstone->corners, 7.0, 7.0); + perspective_map(capstone->c, 3.5, 3.5, &capstone->center); +} + +static void test_capstone(struct quirc *q, int x, int y, int *pb) +{ + int ring_right, ring_left, stone; + ring_right = region_code(q, x - pb[4], y); + ring_left = region_code(q, x - pb[4] - pb[3] - + pb[2] - pb[1] - pb[0], + y); + struct quirc_region *stone_reg; + struct quirc_region *ring_reg; + int ratio; + + if (ring_left < 0 || ring_right < 0)// || stone < 0) + return; + + /* Left and ring of ring should be connected */ + if (ring_left != ring_right) // <-- most of the time, it exits here + return; + + stone = region_code(q, x - pb[4] - pb[3] - pb[2], y); + if (stone < 0) + return; + + /* Ring should be disconnected from stone */ + if (ring_left == stone) + return; + + stone_reg = &q->regions[stone]; + ring_reg = &q->regions[ring_left]; + + /* Already detected */ + if (stone_reg->capstone >= 0 || ring_reg->capstone >= 0) + return; + + /* Ratio should ideally be 37.5 */ + ratio = stone_reg->count * 100 / ring_reg->count; + if (ratio < 10 || ratio > 70) + return; + + record_capstone(q, ring_left, stone); +} + +static void finder_scan(struct quirc *q, int y) +{ + quirc_pixel_t *row = q->pixels + y * q->w; + int x; + uint8_t color, last_color; + int run_length = 1; + int run_count = 0; + int pb[5]; + + memset(pb, 0, sizeof(pb)); + last_color = row[0]; + for (x = 1; x < q->w; x++) { + color = row[x]; + + if (/* x && */ color != last_color) { + memmove(pb, pb + 1, sizeof(pb[0]) * 4); + pb[4] = run_length; + run_length = 0; + run_count++; + + if (!color && run_count >= 5) { + static int check[5] = {1, 1, 3, 1, 1}; + int avg, err; + int i; + int ok = 1; + + avg = (pb[0] + pb[1] + pb[3] + pb[4]) / 4; + err = avg * 3 / 4; + + for (i = 0; i < 5; i++) + if (pb[i] < check[i] * avg - err || + pb[i] > check[i] * avg + err) + ok = 0; + + if (ok) + test_capstone(q, x, y, pb); + } + } + + run_length++; + last_color = color; + } +} + +static void find_alignment_pattern(struct quirc *q, int index) +{ + struct quirc_grid *qr = &q->grids[index]; + struct quirc_capstone *c0 = &q->capstones[qr->caps[0]]; + struct quirc_capstone *c2 = &q->capstones[qr->caps[2]]; + struct quirc_point a; + struct quirc_point b; + struct quirc_point c; + int size_estimate; + int step_size = 1; + int dir = 0; + float u, v; + + /* Grab our previous estimate of the alignment pattern corner */ + memcpy(&b, &qr->align, sizeof(b)); + + /* Guess another two corners of the alignment pattern so that we + * can estimate its size. + */ + perspective_unmap(c0->c, &b, &u, &v); + perspective_map(c0->c, u, v + 1.0, &a); + perspective_unmap(c2->c, &b, &u, &v); + perspective_map(c2->c, u + 1.0, v, &c); + + size_estimate = abs((a.x - b.x) * -(c.y - b.y) + + (a.y - b.y) * (c.x - b.x)); + + /* Spiral outwards from the estimate point until we find something + * roughly the right size. Don't look too far from the estimate + * point. + */ + while (step_size * step_size < size_estimate * 100) { + static const int dx_map[] = {1, 0, -1, 0}; + static const int dy_map[] = {0, -1, 0, 1}; + int i; + + for (i = 0; i < step_size; i++) { + int code = region_code(q, b.x, b.y); + + if (code >= 0) { + struct quirc_region *reg = &q->regions[code]; + + if (reg->count >= size_estimate / 2 && + reg->count <= size_estimate * 2) { + qr->align_region = code; + return; + } + } + + b.x += dx_map[dir]; + b.y += dy_map[dir]; + } + + dir = (dir + 1) % 4; + if (!(dir & 1)) + step_size++; + } +} + +static void find_leftmost_to_line(void *user_data, int y, int left, int right) +{ + struct polygon_score_data *psd = + (struct polygon_score_data *)user_data; + int xs[2] = {left, right}; + int i; + + for (i = 0; i < 2; i++) { + int d = -psd->ref.y * xs[i] + psd->ref.x * y; + + if (d < psd->scores[0]) { + psd->scores[0] = d; + psd->corners[0].x = xs[i]; + psd->corners[0].y = y; + } + } +} + +/* Do a Bresenham scan from one point to another and count the number + * of black/white transitions. + */ +static int timing_scan(const struct quirc *q, + const struct quirc_point *p0, + const struct quirc_point *p1) +{ + int n = p1->x - p0->x; + int d = p1->y - p0->y; + int x = p0->x; + int y = p0->y; + int *dom, *nondom; + int dom_step; + int nondom_step; + int a = 0; + int i; + int run_length = 0; + int count = 0; + + if (p0->x < 0 || p0->y < 0 || p0->x >= q->w || p0->y >= q->h) + return -1; + if (p1->x < 0 || p1->y < 0 || p1->x >= q->w || p1->y >= q->h) + return -1; + + if (abs(n) > abs(d)) { + int swap = n; + + n = d; + d = swap; + + dom = &x; + nondom = &y; + } else { + dom = &y; + nondom = &x; + } + + if (n < 0) { + n = -n; + nondom_step = -1; + } else { + nondom_step = 1; + } + + if (d < 0) { + d = -d; + dom_step = -1; + } else { + dom_step = 1; + } + + x = p0->x; + y = p0->y; + for (i = 0; i <= d; i++) { + int pixel; + + if (y < 0 || y >= q->h || x < 0 || x >= q->w) + break; + + pixel = q->pixels[y * q->w + x]; + + if (pixel) { + if (run_length >= 2) + count++; + run_length = 0; + } else { + run_length++; + } + + a += n; + *dom += dom_step; + if (a >= d) { + *nondom += nondom_step; + a -= d; + } + } + + return count; +} + +/* Try the measure the timing pattern for a given QR code. This does + * not require the global perspective to have been set up, but it + * does require that the capstone corners have been set to their + * canonical rotation. + * + * For each capstone, we find a point in the middle of the ring band + * which is nearest the centre of the code. Using these points, we do + * a horizontal and a vertical timing scan. + */ +static int measure_timing_pattern(struct quirc *q, int index) +{ + struct quirc_grid *qr = &q->grids[index]; + int i; + int scan; + int ver; + int size; + + for (i = 0; i < 3; i++) { + static const float us[] = {6.5, 6.5, 0.5}; + static const float vs[] = {0.5, 6.5, 6.5}; + struct quirc_capstone *cap = &q->capstones[qr->caps[i]]; + + perspective_map(cap->c, us[i], vs[i], &qr->tpep[i]); + } + + qr->hscan = timing_scan(q, &qr->tpep[1], &qr->tpep[2]); + qr->vscan = timing_scan(q, &qr->tpep[1], &qr->tpep[0]); + + scan = qr->hscan; + if (qr->vscan > scan) + scan = qr->vscan; + + /* If neither scan worked, we can't go any further. */ + if (scan < 0) + return -1; + + /* Choose the nearest allowable grid size */ + size = scan * 2 + 13; + ver = (size - 15) / 4; + qr->grid_size = ver * 4 + 17; + + return 0; +} + +/* Read a cell from a grid using the currently set perspective + * transform. Returns +/- 1 for black/white, 0 for cells which are + * out of image bounds. + */ +static int read_cell(const struct quirc *q, int index, int x, int y) +{ + const struct quirc_grid *qr = &q->grids[index]; + struct quirc_point p; + + perspective_map(qr->c, x + 0.5, y + 0.5, &p); + if (p.y < 0 || p.y >= q->h || p.x < 0 || p.x >= q->w) + return 0; + + return q->pixels[p.y * q->w + p.x] ? 1 : -1; +} + +static int fitness_cell(const struct quirc *q, int index, int x, int y) +{ + const struct quirc_grid *qr = &q->grids[index]; + int score = 0; + int u, v; + + for (v = 0; v < 3; v++) + for (u = 0; u < 3; u++) { + static const float offsets[] = {0.3, 0.5, 0.7}; + struct quirc_point p; + + perspective_map(qr->c, x + offsets[u], + y + offsets[v], &p); + if (p.y < 0 || p.y >= q->h || p.x < 0 || p.x >= q->w) + continue; + + if (q->pixels[p.y * q->w + p.x]) + score++; + else + score--; + } + + return score; +} + +static int fitness_ring(const struct quirc *q, int index, int cx, int cy, + int radius) +{ + int i; + int score = 0; + + for (i = 0; i < radius * 2; i++) { + score += fitness_cell(q, index, cx - radius + i, cy - radius); + score += fitness_cell(q, index, cx - radius, cy + radius - i); + score += fitness_cell(q, index, cx + radius, cy - radius + i); + score += fitness_cell(q, index, cx + radius - i, cy + radius); + } + + return score; +} + +static int fitness_apat(const struct quirc *q, int index, int cx, int cy) +{ + return fitness_cell(q, index, cx, cy) - + fitness_ring(q, index, cx, cy, 1) + + fitness_ring(q, index, cx, cy, 2); +} + +static int fitness_capstone(const struct quirc *q, int index, int x, int y) +{ + x += 3; + y += 3; + + return fitness_cell(q, index, x, y) + + fitness_ring(q, index, x, y, 1) - + fitness_ring(q, index, x, y, 2) + + fitness_ring(q, index, x, y, 3); +} + +/* Compute a fitness score for the currently configured perspective + * transform, using the features we expect to find by scanning the + * grid. + */ +static int fitness_all(const struct quirc *q, int index) +{ + const struct quirc_grid *qr = &q->grids[index]; + int version = (qr->grid_size - 17) / 4; + const struct quirc_version_info *info = &quirc_version_db[version]; + int score = 0; + int i, j; + int ap_count; + + /* Check the timing pattern */ + for (i = 0; i < qr->grid_size - 14; i++) { + int expect = (i & 1) ? 1 : -1; + + score += fitness_cell(q, index, i + 7, 6) * expect; + score += fitness_cell(q, index, 6, i + 7) * expect; + } + + /* Check capstones */ + score += fitness_capstone(q, index, 0, 0); + score += fitness_capstone(q, index, qr->grid_size - 7, 0); + score += fitness_capstone(q, index, 0, qr->grid_size - 7); + + if (version < 0 || version > QUIRC_MAX_VERSION) + return score; + + /* Check alignment patterns */ + ap_count = 0; + while ((ap_count < QUIRC_MAX_ALIGNMENT) && info->apat[ap_count]) + ap_count++; + + for (i = 1; i + 1 < ap_count; i++) { + score += fitness_apat(q, index, 6, info->apat[i]); + score += fitness_apat(q, index, info->apat[i], 6); + } + + for (i = 1; i < ap_count; i++) + for (j = 1; j < ap_count; j++) + score += fitness_apat(q, index, + info->apat[i], info->apat[j]); + + return score; +} + +static void jiggle_perspective(struct quirc *q, int index) +{ + struct quirc_grid *qr = &q->grids[index]; + int best = fitness_all(q, index); + int pass; + float adjustments[8]; + int i; + + for (i = 0; i < 8; i++) + adjustments[i] = qr->c[i] * 0.02; + + for (pass = 0; pass < 5; pass++) { + for (i = 0; i < 16; i++) { + int j = i >> 1; + int test; + float old = qr->c[j]; + float step = adjustments[j]; + float new; + + if (i & 1) + new = old + step; + else + new = old - step; + + qr->c[j] = new; + test = fitness_all(q, index); + + if (test > best) + best = test; + else + qr->c[j] = old; + } + + for (i = 0; i < 8; i++) + adjustments[i] *= 0.5; + } +} + +/* Once the capstones are in place and an alignment point has been + * chosen, we call this function to set up a grid-reading perspective + * transform. + */ +static void setup_qr_perspective(struct quirc *q, int index) +{ + struct quirc_grid *qr = &q->grids[index]; + struct quirc_point rect[4]; + + /* Set up the perspective map for reading the grid */ + memcpy(&rect[0], &q->capstones[qr->caps[1]].corners[0], + sizeof(rect[0])); + memcpy(&rect[1], &q->capstones[qr->caps[2]].corners[0], + sizeof(rect[0])); + memcpy(&rect[2], &qr->align, sizeof(rect[0])); + memcpy(&rect[3], &q->capstones[qr->caps[0]].corners[0], + sizeof(rect[0])); + perspective_setup(qr->c, rect, qr->grid_size - 7, qr->grid_size - 7); + + jiggle_perspective(q, index); +} + +/* Rotate the capstone with so that corner 0 is the leftmost with respect + * to the given reference line. + */ +static void rotate_capstone(struct quirc_capstone *cap, + const struct quirc_point *h0, + const struct quirc_point *hd) +{ + struct quirc_point copy[4]; + int j; + int best = 0; + int best_score = 0; + + for (j = 0; j < 4; j++) { + struct quirc_point *p = &cap->corners[j]; + int score = (p->x - h0->x) * -hd->y + + (p->y - h0->y) * hd->x; + + if (!j || score < best_score) { + best = j; + best_score = score; + } + } + + /* Rotate the capstone */ + for (j = 0; j < 4; j++) + memcpy(©[j], &cap->corners[(j + best) % 4], + sizeof(copy[j])); + memcpy(cap->corners, copy, sizeof(cap->corners)); + perspective_setup(cap->c, cap->corners, 7.0, 7.0); +} + +static void record_qr_grid(struct quirc *q, int a, int b, int c) +{ + struct quirc_point h0, hd; + int i; + int qr_index; + struct quirc_grid *qr; + + if (q->num_grids >= QUIRC_MAX_GRIDS) + return; + + /* Construct the hypotenuse line from A to C. B should be to + * the left of this line. + */ + memcpy(&h0, &q->capstones[a].center, sizeof(h0)); + hd.x = q->capstones[c].center.x - q->capstones[a].center.x; + hd.y = q->capstones[c].center.y - q->capstones[a].center.y; + + /* Make sure A-B-C is clockwise */ + if ((q->capstones[b].center.x - h0.x) * -hd.y + + (q->capstones[b].center.y - h0.y) * hd.x > 0) { + int swap = a; + + a = c; + c = swap; + hd.x = -hd.x; + hd.y = -hd.y; + } + + /* Record the grid and its components */ + qr_index = q->num_grids; + qr = &q->grids[q->num_grids++]; + + memset(qr, 0, sizeof(*qr)); + qr->caps[0] = a; + qr->caps[1] = b; + qr->caps[2] = c; + qr->align_region = -1; + + /* Rotate each capstone so that corner 0 is top-left with respect + * to the grid. + */ + for (i = 0; i < 3; i++) { + struct quirc_capstone *cap = &q->capstones[qr->caps[i]]; + + rotate_capstone(cap, &h0, &hd); + cap->qr_grid = qr_index; + } + + /* Check the timing pattern. This doesn't require a perspective + * transform. + */ + if (measure_timing_pattern(q, qr_index) < 0) + goto fail; + + /* Make an estimate based for the alignment pattern based on extending + * lines from capstones A and C. + */ + if (!line_intersect(&q->capstones[a].corners[0], + &q->capstones[a].corners[1], + &q->capstones[c].corners[0], + &q->capstones[c].corners[3], + &qr->align)) + goto fail; + + /* On V2+ grids, we should use the alignment pattern. */ + if (qr->grid_size > 21) { + /* Try to find the actual location of the alignment pattern. */ + find_alignment_pattern(q, qr_index); + + /* Find the point of the alignment pattern closest to the + * top-left of the QR grid. + */ + if (qr->align_region >= 0) { + struct polygon_score_data psd; + struct quirc_region *reg = + &q->regions[qr->align_region]; + + /* Start from some point inside the alignment pattern */ + memcpy(&qr->align, ®->seed, sizeof(qr->align)); + + memcpy(&psd.ref, &hd, sizeof(psd.ref)); + psd.corners = &qr->align; + psd.scores[0] = -hd.y * qr->align.x + + hd.x * qr->align.y; + + flood_fill_seed(q, reg->seed.x, reg->seed.y, + qr->align_region, QUIRC_PIXEL_BLACK, + NULL, NULL, 0); + flood_fill_seed(q, reg->seed.x, reg->seed.y, + QUIRC_PIXEL_BLACK, qr->align_region, + find_leftmost_to_line, &psd, 0); + } + } + + setup_qr_perspective(q, qr_index); + return; + +fail: + /* We've been unable to complete setup for this grid. Undo what we've + * recorded and pretend it never happened. + */ + for (i = 0; i < 3; i++) + q->capstones[qr->caps[i]].qr_grid = -1; + q->num_grids--; +} + +struct neighbour { + int index; + float distance; +}; + +struct neighbour_list { + struct neighbour n[QUIRC_MAX_CAPSTONES]; + int count; +}; + +static void test_neighbours(struct quirc *q, int i, + const struct neighbour_list *hlist, + const struct neighbour_list *vlist) +{ + int j, k; + float best_score = 0.0; + int best_h = -1, best_v = -1; + + /* Test each possible grouping */ + for (j = 0; j < hlist->count; j++) + for (k = 0; k < vlist->count; k++) { + const struct neighbour *hn = &hlist->n[j]; + const struct neighbour *vn = &vlist->n[k]; + float score = fast_fabsf(1.0 - hn->distance / vn->distance); + + if (score > 2.5) + continue; + + if (best_h < 0 || score < best_score) { + best_h = hn->index; + best_v = vn->index; + best_score = score; + } + } + + if (best_h < 0 || best_v < 0) + return; + + record_qr_grid(q, best_h, i, best_v); +} + +static void test_grouping(struct quirc *q, int i) +{ + struct quirc_capstone *c1 = &q->capstones[i]; + int j; + struct neighbour_list hlist; + struct neighbour_list vlist; + + if (c1->qr_grid >= 0) + return; + + hlist.count = 0; + vlist.count = 0; + + /* Look for potential neighbours by examining the relative gradients + * from this capstone to others. + */ + for (j = 0; j < q->num_capstones; j++) { + struct quirc_capstone *c2 = &q->capstones[j]; + float u, v; + + if (i == j || c2->qr_grid >= 0) + continue; + + perspective_unmap(c1->c, &c2->center, &u, &v); + + u = fast_fabsf(u - 3.5); + v = fast_fabsf(v - 3.5); + + if (u < 0.2 * v) { + struct neighbour *n = &hlist.n[hlist.count++]; + + n->index = j; + n->distance = v; + } + + if (v < 0.2 * u) { + struct neighbour *n = &vlist.n[vlist.count++]; + + n->index = j; + n->distance = u; + } + } + + if (!(hlist.count && vlist.count)) + return; + + test_neighbours(q, i, &hlist, &vlist); +} + +static void pixels_setup(struct quirc *q) +{ + if (sizeof(*q->image) == sizeof(*q->pixels)) { + q->pixels = (quirc_pixel_t *)q->image; + } else { + int x, y; + for (y = 0; y < q->h; y++) { + for (x = 0; x < q->w; x++) { + q->pixels[y * q->w + x] = q->image[y * q->w + x]; + } + } + } +} + +uint8_t *quirc_begin(struct quirc *q, int *w, int *h) +{ + q->num_regions = QUIRC_PIXEL_REGION; + q->num_capstones = 0; + q->num_grids = 0; + + if (w) + *w = q->w; + if (h) + *h = q->h; + + return q->image; +} + +void quirc_end(struct quirc *q) +{ + int i; + + pixels_setup(q); + threshold(q); + + for (i = 0; i < q->h; i++) + finder_scan(q, i); + + for (i = 0; i < q->num_capstones; i++) + test_grouping(q, i); +} + +void quirc_extract(const struct quirc *q, int index, + struct quirc_code *code) +{ + const struct quirc_grid *qr = &q->grids[index]; + int y; + int i = 0; + + if (index < 0 || index > q->num_grids) + return; + + memset(code, 0, sizeof(*code)); + + perspective_map(qr->c, 0.0, 0.0, &code->corners[0]); + perspective_map(qr->c, qr->grid_size, 0.0, &code->corners[1]); + perspective_map(qr->c, qr->grid_size, qr->grid_size, + &code->corners[2]); + perspective_map(qr->c, 0.0, qr->grid_size, &code->corners[3]); + + code->size = qr->grid_size; + + for (y = 0; y < qr->grid_size; y++) { + int x; + + for (x = 0; x < qr->grid_size; x++) { + if (read_cell(q, index, x, y) > 0) + code->cell_bitmap[i >> 3] |= (1 << (i & 7)); + + i++; + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "decode.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#define MAX_POLY 64 + +/************************************************************************ + * Galois fields + */ + +struct galois_field { + int p; + const uint8_t *log; + const uint8_t *exp; +}; + +static const uint8_t gf16_exp[16] = { + 0x01, 0x02, 0x04, 0x08, 0x03, 0x06, 0x0c, 0x0b, + 0x05, 0x0a, 0x07, 0x0e, 0x0f, 0x0d, 0x09, 0x01 +}; + +static const uint8_t gf16_log[16] = { + 0x00, 0x0f, 0x01, 0x04, 0x02, 0x08, 0x05, 0x0a, + 0x03, 0x0e, 0x09, 0x07, 0x06, 0x0d, 0x0b, 0x0c +}; + +static const struct galois_field gf16 = { + .p = 15, + .log = gf16_log, + .exp = gf16_exp +}; + +static const uint8_t gf256_exp[256] = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, + 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, + 0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, + 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, + 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, + 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, + 0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, + 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, + 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, + 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, + 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, + 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, + 0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, + 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, + 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, + 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, + 0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, + 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, + 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, + 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, + 0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, + 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, + 0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, + 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, + 0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, + 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, + 0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, + 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, + 0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, + 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, + 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, + 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x01 +}; + +static const uint8_t gf256_log[256] = { + 0x00, 0xff, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, + 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, + 0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, + 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71, + 0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, + 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, + 0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, + 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6, + 0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, + 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, + 0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, + 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, + 0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, + 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, + 0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, + 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, + 0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, + 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, + 0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, + 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, + 0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, + 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, + 0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, + 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, + 0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, + 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6, + 0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, + 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, + 0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, + 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, + 0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, + 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf +}; + +const static struct galois_field gf256 = { + .p = 255, + .log = gf256_log, + .exp = gf256_exp +}; + +/************************************************************************ + * Polynomial operations + */ + +static void poly_add(uint8_t *dst, const uint8_t *src, uint8_t c, + int shift, const struct galois_field *gf) +{ + int i; + int log_c = gf->log[c]; + + if (!c) + return; + + for (i = 0; i < MAX_POLY; i++) { + int p = i + shift; + uint8_t v = src[i]; + + if (p < 0 || p >= MAX_POLY) + continue; + if (!v) + continue; + + dst[p] ^= gf->exp[(gf->log[v] + log_c) % gf->p]; + } +} + +static uint8_t poly_eval(const uint8_t *s, uint8_t x, + const struct galois_field *gf) +{ + int i; + uint8_t sum = 0; + uint8_t log_x = gf->log[x]; + + if (!x) + return s[0]; + + for (i = 0; i < MAX_POLY; i++) { + uint8_t c = s[i]; + + if (!c) + continue; + + sum ^= gf->exp[(gf->log[c] + log_x * i) % gf->p]; + } + + return sum; +} + +/************************************************************************ + * Berlekamp-Massey algorithm for finding error locator polynomials. + */ + +static void berlekamp_massey(const uint8_t *s, int N, + const struct galois_field *gf, + uint8_t *sigma) +{ + uint8_t C[MAX_POLY]; + uint8_t B[MAX_POLY]; + int L = 0; + int m = 1; + uint8_t b = 1; + int n; + + memset(B, 0, sizeof(B)); + memset(C, 0, sizeof(C)); + B[0] = 1; + C[0] = 1; + + for (n = 0; n < N; n++) { + uint8_t d = s[n]; + uint8_t mult; + int i; + + for (i = 1; i <= L; i++) { + if (!(C[i] && s[n - i])) + continue; + + d ^= gf->exp[(gf->log[C[i]] + + gf->log[s[n - i]]) % + gf->p]; + } + + mult = gf->exp[(gf->p - gf->log[b] + gf->log[d]) % gf->p]; + + if (!d) { + m++; + } else if (L * 2 <= n) { + uint8_t T[MAX_POLY]; + + memcpy(T, C, sizeof(T)); + poly_add(C, B, mult, m, gf); + memcpy(B, T, sizeof(B)); + L = n + 1 - L; + b = d; + m = 1; + } else { + poly_add(C, B, mult, m, gf); + m++; + } + } + + memcpy(sigma, C, MAX_POLY); +} + +/************************************************************************ + * Code stream error correction + * + * Generator polynomial for GF(2^8) is x^8 + x^4 + x^3 + x^2 + 1 + */ + +static int block_syndromes(const uint8_t *data, int bs, int npar, uint8_t *s) +{ + int nonzero = 0; + int i; + + memset(s, 0, MAX_POLY); + + for (i = 0; i < npar; i++) { + int j; + + for (j = 0; j < bs; j++) { + uint8_t c = data[bs - j - 1]; + + if (!c) + continue; + + s[i] ^= gf256_exp[((int)gf256_log[c] + + i * j) % 255]; + } + + if (s[i]) + nonzero = 1; + } + + return nonzero; +} + +static void eloc_poly(uint8_t *omega, + const uint8_t *s, const uint8_t *sigma, + int npar) +{ + int i; + + memset(omega, 0, MAX_POLY); + + for (i = 0; i < npar; i++) { + const uint8_t a = sigma[i]; + const uint8_t log_a = gf256_log[a]; + int j; + + if (!a) + continue; + + for (j = 0; j + 1 < MAX_POLY; j++) { + const uint8_t b = s[j + 1]; + + if (i + j >= npar) + break; + + if (!b) + continue; + + omega[i + j] ^= + gf256_exp[(log_a + gf256_log[b]) % 255]; + } + } +} + +static quirc_decode_error_t correct_block(uint8_t *data, + const struct quirc_rs_params *ecc) +{ + int npar = ecc->bs - ecc->dw; + uint8_t s[MAX_POLY]; + uint8_t sigma[MAX_POLY]; + uint8_t sigma_deriv[MAX_POLY]; + uint8_t omega[MAX_POLY]; + int i; + + /* Compute syndrome vector */ + if (!block_syndromes(data, ecc->bs, npar, s)) + return QUIRC_SUCCESS; + + berlekamp_massey(s, npar, &gf256, sigma); + + /* Compute derivative of sigma */ + memset(sigma_deriv, 0, MAX_POLY); + for (i = 0; i + 1 < MAX_POLY; i += 2) + sigma_deriv[i] = sigma[i + 1]; + + /* Compute error evaluator polynomial */ + eloc_poly(omega, s, sigma, npar - 1); + + /* Find error locations and magnitudes */ + for (i = 0; i < ecc->bs; i++) { + uint8_t xinv = gf256_exp[255 - i]; + + if (!poly_eval(sigma, xinv, &gf256)) { + uint8_t sd_x = poly_eval(sigma_deriv, xinv, &gf256); + uint8_t omega_x = poly_eval(omega, xinv, &gf256); + uint8_t error = gf256_exp[(255 - gf256_log[sd_x] + + gf256_log[omega_x]) % 255]; + + data[ecc->bs - i - 1] ^= error; + } + } + + if (block_syndromes(data, ecc->bs, npar, s)) + return QUIRC_ERROR_DATA_ECC; + + return QUIRC_SUCCESS; +} + +/************************************************************************ + * Format value error correction + * + * Generator polynomial for GF(2^4) is x^4 + x + 1 + */ + +#define FORMAT_MAX_ERROR 3 +#define FORMAT_SYNDROMES (FORMAT_MAX_ERROR * 2) +#define FORMAT_BITS 15 + +static int format_syndromes(uint16_t u, uint8_t *s) +{ + int i; + int nonzero = 0; + + memset(s, 0, MAX_POLY); + + for (i = 0; i < FORMAT_SYNDROMES; i++) { + int j; + + s[i] = 0; + for (j = 0; j < FORMAT_BITS; j++) + if (u & (1 << j)) + s[i] ^= gf16_exp[((i + 1) * j) % 15]; + + if (s[i]) + nonzero = 1; + } + + return nonzero; +} + +static quirc_decode_error_t correct_format(uint16_t *f_ret) +{ + uint16_t u = *f_ret; + int i; + uint8_t s[MAX_POLY]; + uint8_t sigma[MAX_POLY]; + + /* Evaluate U (received codeword) at each of alpha_1 .. alpha_6 + * to get S_1 .. S_6 (but we index them from 0). + */ + if (!format_syndromes(u, s)) + return QUIRC_SUCCESS; + + berlekamp_massey(s, FORMAT_SYNDROMES, &gf16, sigma); + + /* Now, find the roots of the polynomial */ + for (i = 0; i < 15; i++) + if (!poly_eval(sigma, gf16_exp[15 - i], &gf16)) + u ^= (1 << i); + + if (format_syndromes(u, s)) + return QUIRC_ERROR_FORMAT_ECC; + + *f_ret = u; + return QUIRC_SUCCESS; +} + +/************************************************************************ + * Decoder algorithm + */ + +struct datastream { + uint8_t raw[QUIRC_MAX_PAYLOAD]; + int data_bits; + int ptr; + + uint8_t data[QUIRC_MAX_PAYLOAD]; +}; + +static inline int grid_bit(const struct quirc_code *code, int x, int y) +{ + int p = y * code->size + x; + + return (code->cell_bitmap[p >> 3] >> (p & 7)) & 1; +} + +static quirc_decode_error_t read_format(const struct quirc_code *code, + struct quirc_data *data, int which) +{ + int i; + uint16_t format = 0; + uint16_t fdata; + quirc_decode_error_t err; + + if (which) { + for (i = 0; i < 7; i++) + format = (format << 1) | + grid_bit(code, 8, code->size - 1 - i); + for (i = 0; i < 8; i++) + format = (format << 1) | + grid_bit(code, code->size - 8 + i, 8); + } else { + static const int xs[15] = { + 8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0 + }; + static const int ys[15] = { + 0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8 + }; + + for (i = 14; i >= 0; i--) + format = (format << 1) | grid_bit(code, xs[i], ys[i]); + } + + format ^= 0x5412; + + err = correct_format(&format); + if (err) + return err; + + fdata = format >> 10; + data->ecc_level = fdata >> 3; + data->mask = fdata & 7; + + return QUIRC_SUCCESS; +} + +static int mask_bit(int mask, int i, int j) +{ + switch (mask) { + case 0: return !((i + j) % 2); + case 1: return !(i % 2); + case 2: return !(j % 3); + case 3: return !((i + j) % 3); + case 4: return !(((i / 2) + (j / 3)) % 2); + case 5: return !((i * j) % 2 + (i * j) % 3); + case 6: return !(((i * j) % 2 + (i * j) % 3) % 2); + case 7: return !(((i * j) % 3 + (i + j) % 2) % 2); + } + + return 0; +} + +static int reserved_cell(int version, int i, int j) +{ + const struct quirc_version_info *ver = &quirc_version_db[version]; + int size = version * 4 + 17; + int ai = -1, aj = -1, a; + + /* Finder + format: top left */ + if (i < 9 && j < 9) + return 1; + + /* Finder + format: bottom left */ + if (i + 8 >= size && j < 9) + return 1; + + /* Finder + format: top right */ + if (i < 9 && j + 8 >= size) + return 1; + + /* Exclude timing patterns */ + if (i == 6 || j == 6) + return 1; + + /* Exclude version info, if it exists. Version info sits adjacent to + * the top-right and bottom-left finders in three rows, bounded by + * the timing pattern. + */ + if (version >= 7) { + if (i < 6 && j + 11 >= size) + return 1; + if (i + 11 >= size && j < 6) + return 1; + } + + /* Exclude alignment patterns */ + for (a = 0; a < QUIRC_MAX_ALIGNMENT && ver->apat[a]; a++) { + int p = ver->apat[a]; + + if (abs(p - i) < 3) + ai = a; + if (abs(p - j) < 3) + aj = a; + } + + if (ai >= 0 && aj >= 0) { + a--; + if (ai > 0 && ai < a) + return 1; + if (aj > 0 && aj < a) + return 1; + if (aj == a && ai == a) + return 1; + } + + return 0; +} + +static void read_bit(const struct quirc_code *code, + struct quirc_data *data, + struct datastream *ds, int i, int j) +{ + int bitpos = ds->data_bits & 7; + int bytepos = ds->data_bits >> 3; + int v = grid_bit(code, j, i); + + if (mask_bit(data->mask, i, j)) + v ^= 1; + + if (v) + ds->raw[bytepos] |= (0x80 >> bitpos); + + ds->data_bits++; +} + +static void read_data_(const struct quirc_code *code, + struct quirc_data *data, + struct datastream *ds) +{ + int y = code->size - 1; + int x = code->size - 1; + int dir = -1; + + while (x > 0) { + if (x == 6) + x--; + + if (!reserved_cell(data->version, y, x)) + read_bit(code, data, ds, y, x); + + if (!reserved_cell(data->version, y, x - 1)) + read_bit(code, data, ds, y, x - 1); + + y += dir; + if (y < 0 || y >= code->size) { + dir = -dir; + x -= 2; + y += dir; + } + } +} + +static quirc_decode_error_t codestream_ecc(struct quirc_data *data, + struct datastream *ds) +{ + const struct quirc_version_info *ver = + &quirc_version_db[data->version]; + const struct quirc_rs_params *sb_ecc = &ver->ecc[data->ecc_level]; + struct quirc_rs_params lb_ecc; + const int lb_count = + (ver->data_bytes - sb_ecc->bs * sb_ecc->ns) / (sb_ecc->bs + 1); + const int bc = lb_count + sb_ecc->ns; + const int ecc_offset = sb_ecc->dw * bc + lb_count; + int dst_offset = 0; + int i; + + memcpy(&lb_ecc, sb_ecc, sizeof(lb_ecc)); + lb_ecc.dw++; + lb_ecc.bs++; + + for (i = 0; i < bc; i++) { + uint8_t *dst = ds->data + dst_offset; + const struct quirc_rs_params *ecc = + (i < sb_ecc->ns) ? sb_ecc : &lb_ecc; + const int num_ec = ecc->bs - ecc->dw; + quirc_decode_error_t err; + int j; + + for (j = 0; j < ecc->dw; j++) + dst[j] = ds->raw[j * bc + i]; + for (j = 0; j < num_ec; j++) + dst[ecc->dw + j] = ds->raw[ecc_offset + j * bc + i]; + + err = correct_block(dst, ecc); + if (err) + return err; + + dst_offset += ecc->dw; + } + + ds->data_bits = dst_offset * 8; + + return QUIRC_SUCCESS; +} + +static inline int bits_remaining(const struct datastream *ds) +{ + return ds->data_bits - ds->ptr; +} + +static int take_bits(struct datastream *ds, int len) +{ + int ret = 0; + + while (len && (ds->ptr < ds->data_bits)) { + uint8_t b = ds->data[ds->ptr >> 3]; + int bitpos = ds->ptr & 7; + + ret <<= 1; + if ((b << bitpos) & 0x80) + ret |= 1; + + ds->ptr++; + len--; + } + + return ret; +} + +static int numeric_tuple(struct quirc_data *data, + struct datastream *ds, + int bits, int digits) +{ + int tuple; + int i; + + if (bits_remaining(ds) < bits) + return -1; + + tuple = take_bits(ds, bits); + + for (i = digits - 1; i >= 0; i--) { + data->payload[data->payload_len + i] = tuple % 10 + '0'; + tuple /= 10; + } + + data->payload_len += digits; + return 0; +} + +static quirc_decode_error_t decode_numeric(struct quirc_data *data, + struct datastream *ds) +{ + int bits = 14; + int count; + + if (data->version < 10) + bits = 10; + else if (data->version < 27) + bits = 12; + + count = take_bits(ds, bits); + if (data->payload_len + count + 1 > QUIRC_MAX_PAYLOAD) + return QUIRC_ERROR_DATA_OVERFLOW; + + while (count >= 3) { + if (numeric_tuple(data, ds, 10, 3) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count -= 3; + } + + if (count >= 2) { + if (numeric_tuple(data, ds, 7, 2) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count -= 2; + } + + if (count) { + if (numeric_tuple(data, ds, 4, 1) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count--; + } + + return QUIRC_SUCCESS; +} + +static int alpha_tuple(struct quirc_data *data, + struct datastream *ds, + int bits, int digits) +{ + int tuple; + int i; + + if (bits_remaining(ds) < bits) + return -1; + + tuple = take_bits(ds, bits); + + for (i = 0; i < digits; i++) { + static const char *alpha_map = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + + data->payload[data->payload_len + digits - i - 1] = + alpha_map[tuple % 45]; + tuple /= 45; + } + + data->payload_len += digits; + return 0; +} + +static quirc_decode_error_t decode_alpha(struct quirc_data *data, + struct datastream *ds) +{ + int bits = 13; + int count; + + if (data->version < 10) + bits = 9; + else if (data->version < 27) + bits = 11; + + count = take_bits(ds, bits); + if (data->payload_len + count + 1 > QUIRC_MAX_PAYLOAD) + return QUIRC_ERROR_DATA_OVERFLOW; + + while (count >= 2) { + if (alpha_tuple(data, ds, 11, 2) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count -= 2; + } + + if (count) { + if (alpha_tuple(data, ds, 6, 1) < 0) + return QUIRC_ERROR_DATA_UNDERFLOW; + count--; + } + + return QUIRC_SUCCESS; +} + +static quirc_decode_error_t decode_byte(struct quirc_data *data, + struct datastream *ds) +{ + int bits = 16; + int count; + int i; + + if (data->version < 10) + bits = 8; + + count = take_bits(ds, bits); + if (data->payload_len + count + 1 > QUIRC_MAX_PAYLOAD) + return QUIRC_ERROR_DATA_OVERFLOW; + if (bits_remaining(ds) < count * 8) + return QUIRC_ERROR_DATA_UNDERFLOW; + + for (i = 0; i < count; i++) + data->payload[data->payload_len++] = take_bits(ds, 8); + + return QUIRC_SUCCESS; +} + +static quirc_decode_error_t decode_kanji(struct quirc_data *data, + struct datastream *ds) +{ + int bits = 12; + int count; + int i; + + if (data->version < 10) + bits = 8; + else if (data->version < 27) + bits = 10; + + count = take_bits(ds, bits); + if (data->payload_len + count * 2 + 1 > QUIRC_MAX_PAYLOAD) + return QUIRC_ERROR_DATA_OVERFLOW; + if (bits_remaining(ds) < count * 13) + return QUIRC_ERROR_DATA_UNDERFLOW; + + for (i = 0; i < count; i++) { + int d = take_bits(ds, 13); + int msB = d / 0xc0; + int lsB = d % 0xc0; + int intermediate = (msB << 8) | lsB; + uint16_t sjw; + + if (intermediate + 0x8140 <= 0x9ffc) { + /* bytes are in the range 0x8140 to 0x9FFC */ + sjw = intermediate + 0x8140; + } else { + /* bytes are in the range 0xE040 to 0xEBBF */ + sjw = intermediate + 0xc140; + } + + data->payload[data->payload_len++] = sjw >> 8; + data->payload[data->payload_len++] = sjw & 0xff; + } + + return QUIRC_SUCCESS; +} + +static quirc_decode_error_t decode_eci(struct quirc_data *data, + struct datastream *ds) +{ + if (bits_remaining(ds) < 8) + return QUIRC_ERROR_DATA_UNDERFLOW; + + data->eci = take_bits(ds, 8); + + if ((data->eci & 0xc0) == 0x80) { + if (bits_remaining(ds) < 8) + return QUIRC_ERROR_DATA_UNDERFLOW; + + data->eci = (data->eci << 8) | take_bits(ds, 8); + } else if ((data->eci & 0xe0) == 0xc0) { + if (bits_remaining(ds) < 16) + return QUIRC_ERROR_DATA_UNDERFLOW; + + data->eci = (data->eci << 16) | take_bits(ds, 16); + } + + return QUIRC_SUCCESS; +} + +static quirc_decode_error_t decode_payload(struct quirc_data *data, + struct datastream *ds) +{ + while (bits_remaining(ds) >= 4) { + quirc_decode_error_t err = QUIRC_SUCCESS; + int type = take_bits(ds, 4); + + switch (type) { + case QUIRC_DATA_TYPE_NUMERIC: + err = decode_numeric(data, ds); + break; + + case QUIRC_DATA_TYPE_ALPHA: + err = decode_alpha(data, ds); + break; + + case QUIRC_DATA_TYPE_BYTE: + err = decode_byte(data, ds); + break; + + case QUIRC_DATA_TYPE_KANJI: + err = decode_kanji(data, ds); + break; + + case 7: + err = decode_eci(data, ds); + break; + + default: + goto done; + } + + if (err) + return err; + + if (!(type & (type - 1)) && (type > data->data_type)) + data->data_type = type; + } + +done: + + /* Add nul terminator to all payloads */ + if (data->payload_len >= sizeof(data->payload)) + data->payload_len--; + data->payload[data->payload_len] = 0; + + return QUIRC_SUCCESS; +} + +quirc_decode_error_t quirc_decode(const struct quirc_code *code, + struct quirc_data *data) +{ + quirc_decode_error_t err; + struct datastream *ds = fb_alloc(sizeof(struct datastream), FB_ALLOC_NO_HINT); + + if ((code->size - 17) % 4) + { fb_free(ds); return QUIRC_ERROR_INVALID_GRID_SIZE; } + + memset(data, 0, sizeof(*data)); + memset(ds, 0, sizeof(*ds)); + + data->version = (code->size - 17) / 4; + + if (data->version < 1 || + data->version > QUIRC_MAX_VERSION) + { fb_free(ds); return QUIRC_ERROR_INVALID_VERSION; } + + /* Read format information -- try both locations */ + err = read_format(code, data, 0); + if (err) + err = read_format(code, data, 1); + if (err) + { fb_free(ds); return err; } + + read_data_(code, data, ds); + err = codestream_ecc(data, ds); + if (err) + { fb_free(ds); return err; } + + err = decode_payload(data, ds); + if (err) + { fb_free(ds); return err; } + + fb_free(ds); return QUIRC_SUCCESS; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "quirc.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/* quirc -- QR-code recognition library + * Copyright (C) 2010-2012 Daniel Beer + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +const char *quirc_version(void) +{ + return "1.0"; +} + +struct quirc *quirc_new(void) +{ + struct quirc *q = fb_alloc(sizeof(*q), FB_ALLOC_NO_HINT); + + if (!q) + return NULL; + + memset(q, 0, sizeof(*q)); + return q; +} + +void quirc_destroy(struct quirc *q) +{ + if (q->image) + if (q->image) fb_free(q->image); + if (sizeof(*q->image) != sizeof(*q->pixels)) + if (q->pixels) fb_free(q->pixels); + + if (q) fb_free(q); +} + +int quirc_resize(struct quirc *q, int w, int h) +{ + if (q->image) fb_free(q->image); + uint8_t *new_image = fb_alloc(w * h, FB_ALLOC_NO_HINT); + + if (!new_image) + return -1; + + if (sizeof(*q->image) != sizeof(*q->pixels)) { + size_t new_size = w * h * sizeof(quirc_pixel_t); + if (q->pixels) fb_free(new_image); + quirc_pixel_t *new_pixels = fb_alloc(new_size, FB_ALLOC_NO_HINT); + if (!new_pixels) { + fb_free(new_pixels); + return -1; + } + q->pixels = new_pixels; + } + + q->image = new_image; + q->w = w; + q->h = h; + + return 0; +} + +int quirc_count(const struct quirc *q) +{ + return q->num_grids; +} + +static const char *const error_table[] = { + [QUIRC_SUCCESS] = "Success", + [QUIRC_ERROR_INVALID_GRID_SIZE] = "Invalid grid size", + [QUIRC_ERROR_INVALID_VERSION] = "Invalid version", + [QUIRC_ERROR_FORMAT_ECC] = "Format data ECC failure", + [QUIRC_ERROR_DATA_ECC] = "ECC failure", + [QUIRC_ERROR_UNKNOWN_DATA_TYPE] = "Unknown data type", + [QUIRC_ERROR_DATA_OVERFLOW] = "Data overflow", + [QUIRC_ERROR_DATA_UNDERFLOW] = "Data underflow" +}; + +const char *quirc_strerror(quirc_decode_error_t err) +{ + if (err >= 0 && err < sizeof(error_table) / sizeof(error_table[0])) + return error_table[err]; + + return "Unknown error"; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void imlib_find_qrcodes(list_t *out, image_t *ptr, rectangle_t *roi) +{ + struct quirc *controller = quirc_new(); + quirc_resize(controller, roi->w, roi->h); + uint8_t *grayscale_image = quirc_begin(controller, NULL, NULL); + + image_t img; + img.w = roi->w; + img.h = roi->h; + img.pixfmt = PIXFORMAT_GRAYSCALE; + img.data = grayscale_image; + img.size = img.w * img.h; + imlib_pixfmt_to(&img, ptr, roi); + // imlib_draw_image(&img, ptr, 0, 0, 1.f, 1.f, roi, -1, 256, NULL, NULL, 0, NULL, NULL); + + quirc_end(controller); + imlib_list_init(out, sizeof(find_qrcodes_list_lnk_data_t)); + + for (int i = 0, j = quirc_count(controller); i < j; i++) { + struct quirc_code *code = fb_alloc(sizeof(struct quirc_code), FB_ALLOC_NO_HINT); + struct quirc_data *data = fb_alloc(sizeof(struct quirc_data), FB_ALLOC_NO_HINT); + quirc_extract(controller, i, code); + + if(quirc_decode(code, data) == QUIRC_SUCCESS) { + find_qrcodes_list_lnk_data_t lnk_data; + rectangle_init(&(lnk_data.rect), code->corners[0].x + roi->x, code->corners[0].y + roi->y, 0, 0); + + for (size_t k = 1, l = (sizeof(code->corners) / sizeof(code->corners[0])); k < l; k++) { + rectangle_t temp; + rectangle_init(&temp, code->corners[k].x + roi->x, code->corners[k].y + roi->y, 0, 0); + rectangle_united(&(lnk_data.rect), &temp); + } + + // Add corners... + lnk_data.corners[0].x = fast_roundf(code->corners[0].x) + roi->x; // top-left + lnk_data.corners[0].y = fast_roundf(code->corners[0].y) + roi->y; // top-left + lnk_data.corners[1].x = fast_roundf(code->corners[1].x) + roi->x; // top-right + lnk_data.corners[1].y = fast_roundf(code->corners[1].y) + roi->y; // top-right + lnk_data.corners[2].x = fast_roundf(code->corners[2].x) + roi->x; // bottom-right + lnk_data.corners[2].y = fast_roundf(code->corners[2].y) + roi->y; // bottom-right + lnk_data.corners[3].x = fast_roundf(code->corners[3].x) + roi->x; // bottom-left + lnk_data.corners[3].y = fast_roundf(code->corners[3].y) + roi->y; // bottom-left + + // Payload is already null terminated. + lnk_data.payload_len = data->payload_len; + lnk_data.payload = xalloc(data->payload_len); + memcpy(lnk_data.payload, data->payload, data->payload_len); + + lnk_data.version = data->version; + lnk_data.ecc_level = data->ecc_level; + lnk_data.mask = data->mask; + lnk_data.data_type = data->data_type; + lnk_data.eci = data->eci; + + list_push_back(out, &lnk_data); + } + + fb_free(data); + fb_free(code); + } + + quirc_destroy(controller); +} +#endif //IMLIB_ENABLE_QRCODES diff --git a/github_source/minicv2/src/qsort.c b/github_source/minicv2/src/qsort.c new file mode 100644 index 0000000..b2def63 --- /dev/null +++ b/github_source/minicv2/src/qsort.c @@ -0,0 +1,152 @@ +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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. + */ + +#include +#define min(a, b) (a) < (b) ? a : b +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ +#define swapcode(TYPE, parmi, parmj, n) { \ + size_t i = (n) / sizeof (TYPE); \ + TYPE *pi = (TYPE *) (parmi); \ + TYPE *pj = (TYPE *) (parmj); \ + do { \ + TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ +} + +#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \ + es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1; + +static __inline void swapfunc(char *a, char *b, size_t n, int swaptype) +{ + if (swaptype <= 1) + swapcode(long, a, b, n) + else + swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long *)(a); \ + *(long *)(a) = *(long *)(b); \ + *(long *)(b) = t; \ + } else \ + swapfunc(a, b, es, swaptype) + +#define vecswap(a, b, n) if ((n) > 0) swapfunc(a, b, n, swaptype) + +static __inline char *med3(char *a, char *b, char *c, int (*cmp)(const void *, const void *)) +{ + return cmp(a, b) < 0 ? + (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a )) + :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c )); +} + +void qsort(void *aa, size_t n, size_t es, int (*cmp)(const void *, const void *)) +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int cmp_result, swaptype, swap_cnt; + size_t d, r; + char *a = (char *)aa; + +loop: SWAPINIT(a, es); + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *) a + n * es; pm += es) + for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = (char *)a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp); + pm = med3(pm - d, pm, pm + d, cmp); + pn = med3(pn - 2 * d, pn - d, pn, cmp); + } + pm = med3(pl, pm, pn, cmp); + } + swap(a, pm); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (cmp_result = cmp(pb, a)) <= 0) { + if (cmp_result == 0) { + swap_cnt = 1; + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (cmp_result = cmp(pc, a)) >= 0) { + if (cmp_result == 0) { + swap_cnt = 1; + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swap(pb, pc); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *) a + es; pm < (char *) a + n * es; pm += es) + for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0; + pl -= es) + swap(pl, pl - es); + return; + } + + pn = (char *)a + n * es; + r = min(pa - (char *)a, pb - pa); + vecswap(a, pb - r, r); + r = min(pd - pc, pn - pd - es); + vecswap(pb, pn - r, r); + if ((r = pb - pa) > es) + qsort(a, r / es, es, cmp); + if ((r = pd - pc) > es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } +/* qsort(pn - r, r / es, es, cmp);*/ +} diff --git a/github_source/minicv2/src/rainbow_tab.c b/github_source/minicv2/src/rainbow_tab.c new file mode 100644 index 0000000..0593131 --- /dev/null +++ b/github_source/minicv2/src/rainbow_tab.c @@ -0,0 +1,69 @@ +#include +const uint16_t rainbow_table[256] = { + 0x063F, 0x063F, 0x065F, 0x067F, 0x069F, 0x06BF, 0x06BF, 0x06DF, + 0x06FF, 0x071F, 0x073F, 0x073F, 0x075F, 0x077F, 0x079F, 0x07BF, + 0x07BF, 0x07DF, 0x07FF, 0x07FF, 0x07FE, 0x07FE, 0x07FD, 0x07FD, + 0x07FD, 0x07FC, 0x07FC, 0x07FB, 0x07FB, 0x07FB, 0x07FA, 0x07FA, + 0x07FA, 0x07F9, 0x07F9, 0x07F8, 0x07F8, 0x07F8, 0x07F7, 0x07F7, + 0x07F6, 0x07F6, 0x07F6, 0x07F5, 0x07F5, 0x07F4, 0x07F4, 0x07F4, + 0x07F3, 0x07F3, 0x07F2, 0x07F2, 0x07F2, 0x07F1, 0x07F1, 0x07F1, + 0x07F0, 0x07F0, 0x07EF, 0x07EF, 0x07EF, 0x07EE, 0x07EE, 0x07ED, + 0x07ED, 0x07ED, 0x07EC, 0x07EC, 0x07EB, 0x07EB, 0x07EB, 0x07EA, + 0x07EA, 0x07EA, 0x07E9, 0x07E9, 0x07E8, 0x07E8, 0x07E8, 0x07E7, + 0x07E7, 0x07E6, 0x07E6, 0x07E6, 0x07E5, 0x07E5, 0x07E4, 0x07E4, + 0x07E4, 0x07E3, 0x07E3, 0x07E2, 0x07E2, 0x07E2, 0x07E1, 0x07E1, + 0x07E1, 0x07E0, 0x07E0, 0x0FE0, 0x0FE0, 0x0FE0, 0x17E0, 0x17E0, + 0x1FE0, 0x1FE0, 0x1FE0, 0x27E0, 0x27E0, 0x2FE0, 0x2FE0, 0x2FE0, + 0x37E0, 0x37E0, 0x3FE0, 0x3FE0, 0x3FE0, 0x47E0, 0x47E0, 0x47E0, + 0x4FE0, 0x4FE0, 0x57E0, 0x57E0, 0x57E0, 0x5FE0, 0x5FE0, 0x67E0, + 0x67E0, 0x67E0, 0x6FE0, 0x6FE0, 0x77E0, 0x77E0, 0x77E0, 0x7FE0, + 0x7FE0, 0x87E0, 0x87E0, 0x87E0, 0x8FE0, 0x8FE0, 0x8FE0, 0x97E0, + 0x97E0, 0x9FE0, 0x9FE0, 0x9FE0, 0xA7E0, 0xA7E0, 0xAFE0, 0xAFE0, + 0xAFE0, 0xB7E0, 0xB7E0, 0xBFE0, 0xBFE0, 0xBFE0, 0xC7E0, 0xC7E0, + 0xC7E0, 0xCFE0, 0xCFE0, 0xD7E0, 0xD7E0, 0xD7E0, 0xDFE0, 0xDFE0, + 0xE7E0, 0xE7E0, 0xE7E0, 0xEFE0, 0xEFE0, 0xF7E0, 0xF7E0, 0xF7E0, + 0xFFE0, 0xFFE0, 0xFFC0, 0xFFA0, 0xFF80, 0xFF80, 0xFF60, 0xFF40, + 0xFF20, 0xFF00, 0xFF00, 0xFEE0, 0xFEC0, 0xFEA0, 0xFE80, 0xFE80, + 0xFE60, 0xFE40, 0xFE20, 0xFE00, 0xFE00, 0xFDE0, 0xFDC0, 0xFDA0, + 0xFD80, 0xFD80, 0xFD60, 0xFD40, 0xFD20, 0xFD00, 0xFD00, 0xFCE0, + 0xFCC0, 0xFCA0, 0xFCA0, 0xFC80, 0xFC60, 0xFC40, 0xFC20, 0xFC20, + 0xFC00, 0xFBE0, 0xFBC0, 0xFBA0, 0xFBA0, 0xFB80, 0xFB60, 0xFB40, + 0xFB20, 0xFB20, 0xFB00, 0xFAE0, 0xFAC0, 0xFAA0, 0xFAA0, 0xFA80, + 0xFA60, 0xFA40, 0xFA20, 0xFA20, 0xFA00, 0xF9E0, 0xF9C0, 0xF9A0, + 0xF9A0, 0xF980, 0xF960, 0xF940, 0xF940, 0xF920, 0xF900, 0xF8E0, + 0xF8C0, 0xF8C0, 0xF8A0, 0xF880, 0xF860, 0xF840, 0xF840, 0xF820 +}; +const uint16_t ironbow_table[256] = { + 0xFFFF, 0xFFFF, 0xFFDF, 0xF7DE, 0xF7BE, 0xF7BE, 0xF79E, 0xEF9D, + 0xEF7D, 0xEF7D, 0xEF5D, 0xE75C, 0xE73C, 0xE73C, 0xE71C, 0xDF1B, + 0xDEFB, 0xDEFB, 0xDEDB, 0xD6DA, 0xD6BA, 0xD6BA, 0xD69A, 0xCE99, + 0xCE79, 0xCE79, 0xCE59, 0xC658, 0xC638, 0xC638, 0xC618, 0xBE17, + 0xBDF7, 0xBDF7, 0xBDD7, 0xB5D6, 0xB5B6, 0xB5B6, 0xB596, 0xB596, + 0xAD75, 0xAD75, 0xAD55, 0xAD55, 0xA534, 0xA534, 0xA514, 0xA514, + 0x9CF3, 0x9CF3, 0x9CD3, 0x9CD3, 0x94B2, 0x94B2, 0x9492, 0x9492, + 0x8C71, 0x8C71, 0x8C51, 0x8C51, 0x8430, 0x8430, 0x8410, 0x8410, + 0x7BEF, 0x7BEF, 0x7BCF, 0x7BCF, 0x73AE, 0x73AE, 0x738E, 0x738E, + 0x6B6D, 0x6B6D, 0x6B4D, 0x6B4D, 0x632C, 0x632C, 0x630C, 0x630C, + 0x5AEB, 0x5AEB, 0x5ACB, 0x5ACB, 0x52AA, 0x52AA, 0x528A, 0x528A, + 0x4A69, 0x4A69, 0x4A49, 0x4A49, 0x4A29, 0x4228, 0x4208, 0x4208, + 0x41E8, 0x39E7, 0x39C7, 0x39C7, 0x39A7, 0x31A6, 0x3186, 0x3186, + 0x3166, 0x2965, 0x2945, 0x2945, 0x2925, 0x2124, 0x2104, 0x2104, + 0x20E4, 0x18E3, 0x18C3, 0x18C3, 0x18A3, 0x10A2, 0x1082, 0x1082, + 0x1062, 0x0861, 0x0841, 0x0841, 0x0821, 0x0020, 0x0000, 0x0000, + 0x0001, 0x0002, 0x0003, 0x0804, 0x0805, 0x0805, 0x0806, 0x1007, + 0x1008, 0x1009, 0x180A, 0x180B, 0x180C, 0x180D, 0x200D, 0x200E, + 0x200F, 0x280F, 0x300F, 0x300F, 0x380F, 0x380F, 0x400F, 0x480F, + 0x4810, 0x5010, 0x5010, 0x5810, 0x6010, 0x6010, 0x6810, 0x6810, + 0x7011, 0x7811, 0x7811, 0x8011, 0x8031, 0x8831, 0x9031, 0x9031, + 0x9831, 0x9831, 0xA031, 0xA031, 0xA831, 0xB031, 0xB051, 0xB851, + 0xB851, 0xB870, 0xC08F, 0xC0AF, 0xC0AE, 0xC0CD, 0xC8ED, 0xC90C, + 0xC90C, 0xC92B, 0xD14A, 0xD16A, 0xD169, 0xD988, 0xD9A8, 0xD9C7, + 0xD9C6, 0xD9E6, 0xDA05, 0xE225, 0xE245, 0xE244, 0xE264, 0xE284, + 0xE2A4, 0xE2C3, 0xE2E3, 0xE2E3, 0xEB02, 0xEB22, 0xEB42, 0xEB61, + 0xEB81, 0xEBA1, 0xEBA1, 0xEBC1, 0xEBE1, 0xEC01, 0xEC21, 0xF441, + 0xF462, 0xF482, 0xF4A2, 0xF4C2, 0xF4E2, 0xF502, 0xF502, 0xF522, + 0xF542, 0xF562, 0xF582, 0xF5A2, 0xF5C2, 0xF5E2, 0xF5E2, 0xFE03, + 0xFE23, 0xFE43, 0xFE63, 0xFE83, 0xFEA3, 0xFEC3, 0xFEC4, 0xFEE4, + 0xFF05, 0xFF26, 0xFF28, 0xFF4A, 0xFF4C, 0xFF4D, 0xFF6F, 0xFF71, + 0xFF92, 0xFF94, 0xFFB6, 0xFFB7, 0xFFD9, 0xFFDB, 0xFFFD, 0xFFE3 +}; diff --git a/github_source/minicv2/src/rectangle.c b/github_source/minicv2/src/rectangle.c new file mode 100644 index 0000000..a7d47e0 --- /dev/null +++ b/github_source/minicv2/src/rectangle.c @@ -0,0 +1,125 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Rectangle functions. + */ +#include "imlib.h" +#include "array.h" +#include "xalloc.h" + +rectangle_t *rectangle_alloc(int16_t x, int16_t y, int16_t w, int16_t h) +{ + rectangle_t *r = xalloc(sizeof(rectangle_t)); + r->x = x; + r->y = y; + r->w = w; + r->h = h; + return r; +} + +bool rectangle_equal(rectangle_t *r1, rectangle_t *r2) +{ + return ((r1->x==r2->x)&&(r1->y==r2->y)&&(r1->w==r2->w)&&(r1->h==r2->h)); +} + +bool rectangle_intersects(rectangle_t *r1, rectangle_t *r2) +{ + return ((r1->x < (r2->x+r2->w)) && + (r1->y < (r2->y+r2->h)) && + ((r1->x+r1->w) > r2->x) && + ((r1->y+r1->h) > r2->y)); +} + +// Determine subimg even if it is going off the edge of the main image. +bool rectangle_subimg(image_t *img, rectangle_t *r, rectangle_t *r_out) +{ + rectangle_t r_img; + r_img.x = 0; + r_img.y = 0; + r_img.w = img->w; + r_img.h = img->h; + bool result = rectangle_intersects(&r_img, r); + if (result) { + int r_img_x2 = r_img.x + r_img.w; + int r_img_y2 = r_img.y + r_img.h; + int r_x2 = r->x + r->w; + int r_y2 = r->y + r->h; + r_out->x = IM_MAX(r_img.x, r->x); + r_out->y = IM_MAX(r_img.y, r->y); + r_out->w = IM_MIN(r_img_x2, r_x2) - r_out->x; + r_out->h = IM_MIN(r_img_y2, r_y2) - r_out->y; + } + return result; +} + +// This isn't for actually combining the rects standardly, but, to instead +// find the average rectangle between a bunch of overlapping rectangles. +static void rectangle_add(rectangle_t *r1, rectangle_t *r2) +{ + r1->x += r2->x; + r1->y += r2->y; + r1->w += r2->w; + r1->h += r2->h; +} + +// This isn't for actually combining the rects standardly, but, to instead +// find the average rectangle between a bunch of overlapping rectangles. +static void rectangle_div(rectangle_t *r, int c) +{ + r->x /= c; + r->y /= c; + r->w /= c; + r->h /= c; +} + +array_t *rectangle_merge(array_t *rectangles) +{ + array_t *objects; array_alloc(&objects, xfree); + array_t *overlap; array_alloc(&overlap, xfree); + /* merge overlaping detections */ + while (array_length(rectangles)) { + /* check for overlaping detections */ + rectangle_t *rect = (rectangle_t *) array_take(rectangles, 0); + for (int j=0; jx) { + r->x = x; + } + if (y < r->y) { + r->y = y; + } + if (x > r->w) { + r->w = x; + } + if (y > r->h) { + r->h = y; + } +} diff --git a/github_source/minicv2/src/ringbuf.c b/github_source/minicv2/src/ringbuf.c new file mode 100644 index 0000000..93194b0 --- /dev/null +++ b/github_source/minicv2/src/ringbuf.c @@ -0,0 +1,46 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Simple Ring Buffer implementation. + */ +#include +#include "ringbuf.h" + +void ring_buf_init(ring_buf_t *buf) +{ + memset(buf, 0, sizeof(*buf)); +} + +int ring_buf_empty(ring_buf_t *buf) +{ + return (buf->head == buf->tail); +} + +void ring_buf_put(ring_buf_t *buf, uint8_t c) +{ + if ((buf->tail + 1) % BUFFER_SIZE == buf->head) { + /*buffer is full*/ + return; + } + + buf->data[buf->tail] = c; + buf->tail = (buf->tail + 1) % BUFFER_SIZE; +} + +uint8_t ring_buf_get(ring_buf_t *buf) +{ + uint8_t c; + if (buf->head == buf->tail) { + /*buffer is empty*/ + return 0; + } + + c = buf->data[buf->head]; + buf->head = (buf->head + 1) % BUFFER_SIZE; + return c; +} diff --git a/github_source/minicv2/src/selective_search.c b/github_source/minicv2/src/selective_search.c new file mode 100644 index 0000000..c593872 --- /dev/null +++ b/github_source/minicv2/src/selective_search.c @@ -0,0 +1,465 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Selective search. + */ +#include +#include +#include +#include +#include "imlib.h" +#include "fb_alloc.h" +#include "xalloc.h" +#ifdef IMLIB_ENABLE_SELECTIVE_SEARCH + +#define THRESHOLD(size, c) (c/size) +typedef struct { + uint16_t y; + uint16_t h; + uint16_t x; + uint16_t w; +} region; + +typedef struct { + uint16_t p; + uint16_t rank; + uint16_t size; +} uni_elt; + +typedef struct { + int num; + uni_elt *elts; +} universe; + +typedef struct { + float w; + uint16_t a; + uint16_t b; +} edge; + +static inline int min (int a, int b) { return (a < b) ? a : b; } +static inline int max (int a, int b) { return (a > b) ? a : b; } +static inline float minf (float a, float b) { return (a < b) ? a : b; } +static inline float maxf (float a, float b) { return (a > b) ? a : b; } +extern uint32_t rng_randint(uint32_t min, uint32_t max); + +static universe *universe_create(int elements) +{ + universe * uni = (universe*) fb_alloc(sizeof(universe), FB_ALLOC_NO_HINT); + uni->elts = (uni_elt*) fb_alloc(sizeof(uni_elt)*elements, FB_ALLOC_NO_HINT); + uni->num = elements; + for (int i=0; ielts[i].p = i; + uni->elts[i].rank = 0; + uni->elts[i].size = 1; + } + return uni; +} + +static int universe_size(universe * uni, int x) +{ + return uni->elts[x].size; +} + +static int universe_num_sets(universe * uni) +{ + return uni->num; +} + +static int universe_find(universe * uni, int x) +{ + int y = x; + while (y != uni->elts[y].p) { + y = uni->elts[y].p; + } + // Path compression + uni->elts[x].p = y; + return y; +} + +static void universe_join (universe * uni, int x, int y) +{ + if (uni->elts[x].rank > uni->elts[y].rank) { + uni->elts[y].p = x; + uni->elts[x].size += uni->elts[y].size; + } else { + uni->elts[x].p = y; + uni->elts[y].size += uni->elts[x].size; + if (uni->elts[x].rank == uni->elts[y].rank) { + uni->elts[y].rank++; + } + } + uni->num--; +} + +static int universe_get_id(universe * this, int x) +{ + return this->elts[x].rank; +} + +static void universe_set_id(universe * this, int x, int id) +{ + this->elts[x].rank = id; +} + +static inline float color_similarity (float * hist1, float * hist2) +{ + float sim = 0; + for (int i = 0; i < 75; ++i) { + sim += minf(hist1[i], hist2[i]); + } + return sim; +} + +static inline float size_similarity (int a, int b, int size) +{ + return 1.0f - (a + b)/size; +} + +static inline float fill_similarity (region * ra, region * rb, int a, int b, int size) +{ + int width = max(ra->w, rb->w) - min(ra->x, rb->x); + int height = max(ra->h, rb->h) - min(ra->y, rb->y); + return 1.0f - (width*height - a - b)/size; +} + +static inline float square(float x) { return x*x; }; + +static inline float diff(image_t *img, int x1, int y1, int x2, int y2) +{ + uint16_t p1 = IMAGE_GET_RGB565_PIXEL(img, x1, y1); + uint16_t p2 = IMAGE_GET_RGB565_PIXEL(img, x2, y2); + uint8_t r1 = COLOR_RGB565_TO_R8(p1); + uint8_t r2 = COLOR_RGB565_TO_R8(p2); + + uint8_t g1 = COLOR_RGB565_TO_G8(p1); + uint8_t g2 = COLOR_RGB565_TO_G8(p2); + + uint8_t b1 = COLOR_RGB565_TO_B8(p1); + uint8_t b2 = COLOR_RGB565_TO_B8(p2); + // dissimilarity measure between pixels + return sqrtf((r1-r2) * (r1-r2) + (g1-g2) * (g1-g2) + (b1-b2) * (b1-b2)); +} +static inline float diff888(image_t *img, int x1, int y1, int x2, int y2) +{ + uint32_t p1 = IMAGE_GET_RGB888_PIXEL(img, x1, y1); + uint32_t p2 = IMAGE_GET_RGB888_PIXEL(img, x2, y2); + uint8_t r1 = COLOR_RGB888_TO_R8(p1); + uint8_t r2 = COLOR_RGB888_TO_R8(p2); + + uint8_t g1 = COLOR_RGB888_TO_G8(p1); + uint8_t g2 = COLOR_RGB888_TO_G8(p2); + + uint8_t b1 = COLOR_RGB888_TO_B8(p1); + uint8_t b2 = COLOR_RGB888_TO_B8(p2); + // dissimilarity measure between pixels + return sqrtf((r1-r2) * (r1-r2) + (g1-g2) * (g1-g2) + (b1-b2) * (b1-b2)); +} +int comp (const void * elem1, const void * elem2) +{ + edge *f = (edge*) elem1; + edge *s = (edge*) elem2; + if (f->w > s->w) return 1; + if (f->w < s->w) return -1; + return 0; +} + +static void segment_graph(universe *u, int num_vertices, int num_edges, edge *edges, float c) +{ + qsort (edges, num_edges, sizeof(edge), comp); + + float *threshold = fb_alloc(num_vertices * sizeof(float), FB_ALLOC_NO_HINT); + for (int i=0; ia); + int b = universe_find (u, pedge->b); + if (a != b) { + if ((pedge->w <= threshold[a]) && (pedge->w <= threshold[b])) { + universe_join (u, a, b); + a = universe_find (u, a); + threshold[a] = pedge->w + THRESHOLD(universe_size (u, a), c); + } + } + } + + // Free thresholds. + fb_free(threshold); +} + +static void image_scale(image_t *src, image_t *dst) +{ + int x_ratio = (int)((src->w<<16)/dst->w) +1; + int y_ratio = (int)((src->h<<16)/dst->h) +1; + + for (int y=0; yh; y++) { + int sy = (y*y_ratio)>>16; + for (int x=0; xw; x++) { + int sx = (x*x_ratio)>>16; + ((uint16_t*)dst->pixels)[y*dst->w+x] = ((uint16_t*)src->pixels)[sy*src->w+sx]; + } + } +} + +array_t *imlib_selective_search(image_t *src, float t, int min_size, float a1, float a2, float a3) +{ + int i,j; + int num = 0; + int width=0, height=0; + image_t *img = NULL; + + fb_alloc_mark(); + + if ((src->w * src->h) <= (80 * 60)) { + img = src; + width = src->w; + height = src->h; + } else { + // Down scale image + width = src->w / 4; + height = src->h / 4; + img = fb_alloc(sizeof(image_t), FB_ALLOC_NO_HINT); + img->w = width; + img->h = height; + img->pixels = fb_alloc(width * height * 2, FB_ALLOC_NO_HINT); + image_scale(src, img); + } + + // Region proposals array + array_t *proposals; + array_alloc(&proposals, xfree); + + universe *u = universe_create (width * height); + edge *edges = (edge*) fb_alloc(width * height * sizeof(edge) * 4, FB_ALLOC_NO_HINT); + + for (int y=0; y 0)) { + edges[num].a = y * width + x; + edges[num].b = (y-1) * width + (x+1); + edges[num].w = diff(img, x, y, x+1, y-1); + num++; + } + } + } + + segment_graph(u, width * height, num, edges, t); + + for (i=0; iy = min(r->y, y); + r->h = max(r->h, y); + r->x = min(r->x, x); + r->w = max(r->w, x); + + uint16_t p = IMAGE_GET_RGB565_PIXEL(img, x, y); + int r_bin = min(COLOR_RGB565_TO_R8(p), 240)/10; + int g_bin = min(COLOR_RGB565_TO_G8(p), 240)/10; + int b_bin = min(COLOR_RGB565_TO_B8(p), 240)/10; + + histogram[75*component_id + 0 + r_bin]++; + histogram[75*component_id + 25 + g_bin]++; + histogram[75*component_id + 50 + b_bin]++; + counts[component_id]++; + } + } + + // Normalize histograms + for (i=0; i 1) { + int best_i = -1; + int best_j = -1; + float best_similarity = 0; + for (i=0; i best_similarity) { + best_similarity = similarity; + best_i = i; + best_j = j; + } + } + } + + if (best_i == -1) { + printf("failed to build tree\n"); + break; + } + + // update regions, histograms, counts, adjacency, similarity + regions[best_i].x = min(regions[best_i].x, regions[best_j].x); + regions[best_i].y = min(regions[best_i].y, regions[best_j].y); + regions[best_i].w = max(regions[best_i].w, regions[best_j].w); + regions[best_i].h = max(regions[best_i].h, regions[best_j].h); + + bool add = true; + for (i=0; ix && regions[best_i].y == r->y && + regions[best_i].w == r->w && regions[best_i].h == r->h) { + add = false; + break; + } + } + if (add) { + array_push_back(proposals, rectangle_alloc(regions[best_i].x, + regions[best_i].y, regions[best_i].w, regions[best_i].h)); + } + + + for (i=0; i<75; i++) { + histogram[75*best_i + i] = (counts[best_i] * histogram[75*best_i + i] + + counts[best_j] * histogram[75*best_j + i])/(counts[best_i] + counts[best_j]); + } + counts[best_i] += counts[best_j]; + + for (i=0; iw = r->w - r->x; + r->h = r->h - r->y; + if ((src->w * src->h) > (80 * 60)) { + r->x *=4; + r->y *=4; + r->w *=4; + r->h *=4; + } + } + fb_alloc_free_till_mark(); + return proposals; +} +#endif //IMLIB_ENABLE_SELECTIVE_SEARCH diff --git a/github_source/minicv2/src/sincos_tab.c b/github_source/minicv2/src/sincos_tab.c new file mode 100644 index 0000000..73cfd9a --- /dev/null +++ b/github_source/minicv2/src/sincos_tab.c @@ -0,0 +1,95 @@ +const float sin_table[360] = { + 0.000000f, 0.017452f, 0.034899f, 0.052336f, 0.069756f, 0.087156f, 0.104528f, 0.121869f, + 0.139173f, 0.156434f, 0.173648f, 0.190809f, 0.207912f, 0.224951f, 0.241922f, 0.258819f, + 0.275637f, 0.292372f, 0.309017f, 0.325568f, 0.342020f, 0.358368f, 0.374607f, 0.390731f, + 0.406737f, 0.422618f, 0.438371f, 0.453990f, 0.469472f, 0.484810f, 0.500000f, 0.515038f, + 0.529919f, 0.544639f, 0.559193f, 0.573576f, 0.587785f, 0.601815f, 0.615661f, 0.629320f, + 0.642788f, 0.656059f, 0.669131f, 0.681998f, 0.694658f, 0.707107f, 0.719340f, 0.731354f, + 0.743145f, 0.754710f, 0.766044f, 0.777146f, 0.788011f, 0.798636f, 0.809017f, 0.819152f, + 0.829038f, 0.838671f, 0.848048f, 0.857167f, 0.866025f, 0.874620f, 0.882948f, 0.891007f, + 0.898794f, 0.906308f, 0.913545f, 0.920505f, 0.927184f, 0.933580f, 0.939693f, 0.945519f, + 0.951057f, 0.956305f, 0.961262f, 0.965926f, 0.970296f, 0.974370f, 0.978148f, 0.981627f, + 0.984808f, 0.987688f, 0.990268f, 0.992546f, 0.994522f, 0.996195f, 0.997564f, 0.998630f, + 0.999391f, 0.999848f, 1.000000f, 0.999848f, 0.999391f, 0.998630f, 0.997564f, 0.996195f, + 0.994522f, 0.992546f, 0.990268f, 0.987688f, 0.984808f, 0.981627f, 0.978148f, 0.974370f, + 0.970296f, 0.965926f, 0.961262f, 0.956305f, 0.951057f, 0.945519f, 0.939693f, 0.933580f, + 0.927184f, 0.920505f, 0.913545f, 0.906308f, 0.898794f, 0.891007f, 0.882948f, 0.874620f, + 0.866025f, 0.857167f, 0.848048f, 0.838671f, 0.829038f, 0.819152f, 0.809017f, 0.798636f, + 0.788011f, 0.777146f, 0.766044f, 0.754710f, 0.743145f, 0.731354f, 0.719340f, 0.707107f, + 0.694658f, 0.681998f, 0.669131f, 0.656059f, 0.642788f, 0.629320f, 0.615661f, 0.601815f, + 0.587785f, 0.573576f, 0.559193f, 0.544639f, 0.529919f, 0.515038f, 0.500000f, 0.484810f, + 0.469472f, 0.453990f, 0.438371f, 0.422618f, 0.406737f, 0.390731f, 0.374607f, 0.358368f, + 0.342020f, 0.325568f, 0.309017f, 0.292372f, 0.275637f, 0.258819f, 0.241922f, 0.224951f, + 0.207912f, 0.190809f, 0.173648f, 0.156434f, 0.139173f, 0.121869f, 0.104528f, 0.087156f, + 0.069756f, 0.052336f, 0.034899f, 0.017452f, 0.000000f, -0.017452f, -0.034899f, -0.052336f, + -0.069756f, -0.087156f, -0.104528f, -0.121869f, -0.139173f, -0.156434f, -0.173648f, -0.190809f, + -0.207912f, -0.224951f, -0.241922f, -0.258819f, -0.275637f, -0.292372f, -0.309017f, -0.325568f, + -0.342020f, -0.358368f, -0.374607f, -0.390731f, -0.406737f, -0.422618f, -0.438371f, -0.453990f, + -0.469472f, -0.484810f, -0.500000f, -0.515038f, -0.529919f, -0.544639f, -0.559193f, -0.573576f, + -0.587785f, -0.601815f, -0.615661f, -0.629320f, -0.642788f, -0.656059f, -0.669131f, -0.681998f, + -0.694658f, -0.707107f, -0.719340f, -0.731354f, -0.743145f, -0.754710f, -0.766044f, -0.777146f, + -0.788011f, -0.798636f, -0.809017f, -0.819152f, -0.829038f, -0.838671f, -0.848048f, -0.857167f, + -0.866025f, -0.874620f, -0.882948f, -0.891007f, -0.898794f, -0.906308f, -0.913545f, -0.920505f, + -0.927184f, -0.933580f, -0.939693f, -0.945519f, -0.951057f, -0.956305f, -0.961262f, -0.965926f, + -0.970296f, -0.974370f, -0.978148f, -0.981627f, -0.984808f, -0.987688f, -0.990268f, -0.992546f, + -0.994522f, -0.996195f, -0.997564f, -0.998630f, -0.999391f, -0.999848f, -1.000000f, -0.999848f, + -0.999391f, -0.998630f, -0.997564f, -0.996195f, -0.994522f, -0.992546f, -0.990268f, -0.987688f, + -0.984808f, -0.981627f, -0.978148f, -0.974370f, -0.970296f, -0.965926f, -0.961262f, -0.956305f, + -0.951057f, -0.945519f, -0.939693f, -0.933580f, -0.927184f, -0.920505f, -0.913545f, -0.906308f, + -0.898794f, -0.891007f, -0.882948f, -0.874620f, -0.866025f, -0.857167f, -0.848048f, -0.838671f, + -0.829038f, -0.819152f, -0.809017f, -0.798636f, -0.788011f, -0.777146f, -0.766044f, -0.754710f, + -0.743145f, -0.731354f, -0.719340f, -0.707107f, -0.694658f, -0.681998f, -0.669131f, -0.656059f, + -0.642788f, -0.629320f, -0.615661f, -0.601815f, -0.587785f, -0.573576f, -0.559193f, -0.544639f, + -0.529919f, -0.515038f, -0.500000f, -0.484810f, -0.469472f, -0.453990f, -0.438371f, -0.422618f, + -0.406737f, -0.390731f, -0.374607f, -0.358368f, -0.342020f, -0.325568f, -0.309017f, -0.292372f, + -0.275637f, -0.258819f, -0.241922f, -0.224951f, -0.207912f, -0.190809f, -0.173648f, -0.156434f, + -0.139173f, -0.121869f, -0.104528f, -0.087156f, -0.069756f, -0.052336f, -0.034899f, -0.017452f +}; + +const float cos_table[360] = { + 1.000000f, 0.999848f, 0.999391f, 0.998630f, 0.997564f, 0.996195f, 0.994522f, 0.992546f, + 0.990268f, 0.987688f, 0.984808f, 0.981627f, 0.978148f, 0.974370f, 0.970296f, 0.965926f, + 0.961262f, 0.956305f, 0.951057f, 0.945519f, 0.939693f, 0.933580f, 0.927184f, 0.920505f, + 0.913545f, 0.906308f, 0.898794f, 0.891007f, 0.882948f, 0.874620f, 0.866025f, 0.857167f, + 0.848048f, 0.838671f, 0.829038f, 0.819152f, 0.809017f, 0.798636f, 0.788011f, 0.777146f, + 0.766044f, 0.754710f, 0.743145f, 0.731354f, 0.719340f, 0.707107f, 0.694658f, 0.681998f, + 0.669131f, 0.656059f, 0.642788f, 0.629320f, 0.615661f, 0.601815f, 0.587785f, 0.573576f, + 0.559193f, 0.544639f, 0.529919f, 0.515038f, 0.500000f, 0.484810f, 0.469472f, 0.453990f, + 0.438371f, 0.422618f, 0.406737f, 0.390731f, 0.374607f, 0.358368f, 0.342020f, 0.325568f, + 0.309017f, 0.292372f, 0.275637f, 0.258819f, 0.241922f, 0.224951f, 0.207912f, 0.190809f, + 0.173648f, 0.156434f, 0.139173f, 0.121869f, 0.104528f, 0.087156f, 0.069756f, 0.052336f, + 0.034899f, 0.017452f, 0.000000f, -0.017452f, -0.034899f, -0.052336f, -0.069756f, -0.087156f, + -0.104528f, -0.121869f, -0.139173f, -0.156434f, -0.173648f, -0.190809f, -0.207912f, -0.224951f, + -0.241922f, -0.258819f, -0.275637f, -0.292372f, -0.309017f, -0.325568f, -0.342020f, -0.358368f, + -0.374607f, -0.390731f, -0.406737f, -0.422618f, -0.438371f, -0.453990f, -0.469472f, -0.484810f, + -0.500000f, -0.515038f, -0.529919f, -0.544639f, -0.559193f, -0.573576f, -0.587785f, -0.601815f, + -0.615661f, -0.629320f, -0.642788f, -0.656059f, -0.669131f, -0.681998f, -0.694658f, -0.707107f, + -0.719340f, -0.731354f, -0.743145f, -0.754710f, -0.766044f, -0.777146f, -0.788011f, -0.798636f, + -0.809017f, -0.819152f, -0.829038f, -0.838671f, -0.848048f, -0.857167f, -0.866025f, -0.874620f, + -0.882948f, -0.891007f, -0.898794f, -0.906308f, -0.913545f, -0.920505f, -0.927184f, -0.933580f, + -0.939693f, -0.945519f, -0.951057f, -0.956305f, -0.961262f, -0.965926f, -0.970296f, -0.974370f, + -0.978148f, -0.981627f, -0.984808f, -0.987688f, -0.990268f, -0.992546f, -0.994522f, -0.996195f, + -0.997564f, -0.998630f, -0.999391f, -0.999848f, -1.000000f, -0.999848f, -0.999391f, -0.998630f, + -0.997564f, -0.996195f, -0.994522f, -0.992546f, -0.990268f, -0.987688f, -0.984808f, -0.981627f, + -0.978148f, -0.974370f, -0.970296f, -0.965926f, -0.961262f, -0.956305f, -0.951057f, -0.945519f, + -0.939693f, -0.933580f, -0.927184f, -0.920505f, -0.913545f, -0.906308f, -0.898794f, -0.891007f, + -0.882948f, -0.874620f, -0.866025f, -0.857167f, -0.848048f, -0.838671f, -0.829038f, -0.819152f, + -0.809017f, -0.798636f, -0.788011f, -0.777146f, -0.766044f, -0.754710f, -0.743145f, -0.731354f, + -0.719340f, -0.707107f, -0.694658f, -0.681998f, -0.669131f, -0.656059f, -0.642788f, -0.629320f, + -0.615661f, -0.601815f, -0.587785f, -0.573576f, -0.559193f, -0.544639f, -0.529919f, -0.515038f, + -0.500000f, -0.484810f, -0.469472f, -0.453990f, -0.438371f, -0.422618f, -0.406737f, -0.390731f, + -0.374607f, -0.358368f, -0.342020f, -0.325568f, -0.309017f, -0.292372f, -0.275637f, -0.258819f, + -0.241922f, -0.224951f, -0.207912f, -0.190809f, -0.173648f, -0.156434f, -0.139173f, -0.121869f, + -0.104528f, -0.087156f, -0.069756f, -0.052336f, -0.034899f, -0.017452f, -0.000000f, 0.017452f, + 0.034899f, 0.052336f, 0.069756f, 0.087156f, 0.104528f, 0.121869f, 0.139173f, 0.156434f, + 0.173648f, 0.190809f, 0.207912f, 0.224951f, 0.241922f, 0.258819f, 0.275637f, 0.292372f, + 0.309017f, 0.325568f, 0.342020f, 0.358368f, 0.374607f, 0.390731f, 0.406737f, 0.422618f, + 0.438371f, 0.453990f, 0.469472f, 0.484810f, 0.500000f, 0.515038f, 0.529919f, 0.544639f, + 0.559193f, 0.573576f, 0.587785f, 0.601815f, 0.615661f, 0.629320f, 0.642788f, 0.656059f, + 0.669131f, 0.681998f, 0.694658f, 0.707107f, 0.719340f, 0.731354f, 0.743145f, 0.754710f, + 0.766044f, 0.777146f, 0.788011f, 0.798636f, 0.809017f, 0.819152f, 0.829038f, 0.838671f, + 0.848048f, 0.857167f, 0.866025f, 0.874620f, 0.882948f, 0.891007f, 0.898794f, 0.906308f, + 0.913545f, 0.920505f, 0.927184f, 0.933580f, 0.939693f, 0.945519f, 0.951057f, 0.956305f, + 0.961262f, 0.965926f, 0.970296f, 0.974370f, 0.978148f, 0.981627f, 0.984808f, 0.987688f, + 0.990268f, 0.992546f, 0.994522f, 0.996195f, 0.997564f, 0.998630f, 0.999391f, 0.999848f +}; diff --git a/github_source/minicv2/src/stats.c b/github_source/minicv2/src/stats.c new file mode 100644 index 0000000..f53932b --- /dev/null +++ b/github_source/minicv2/src/stats.c @@ -0,0 +1,1499 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Statistics functions. + */ +#include "imlib.h" + +#ifdef IMLIB_ENABLE_GET_SIMILARITY +typedef struct imlib_similatiry_line_op_state { + int *sumBucketsOfX, *sumBucketsOfY, *sum2BucketsOfX, *sum2BucketsOfY, *sum2Buckets; + float similarity_sum, similarity_sum_2, similarity_min, similarity_max; + int lines_processed; +} imlib_similatiry_line_op_state_t; + +void imlib_similarity_line_op(image_t *img, int line, void *other, void *data, bool vflipped) +{ + imlib_similatiry_line_op_state_t *state = (imlib_similatiry_line_op_state_t *) data; vflipped = vflipped; + float c1 = 0, c2 = 0; + + switch (img->pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(img, line); + uint32_t *other_row_ptr = (uint32_t *) other; + for (int x = 0, xx = (img->w + 7) / 8; x < xx; x++) { + for (int i = 0, ii = IM_MIN((img->w - (x * 8)), 8); i < ii; i++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x + i); + int other_pixel = IMAGE_GET_BINARY_PIXEL_FAST(other_row_ptr, x + i); + state->sumBucketsOfX[x] += pixel; + state->sumBucketsOfY[x] += other_pixel; + state->sum2BucketsOfX[x] += pixel * pixel; + state->sum2BucketsOfY[x] += other_pixel * other_pixel; + state->sum2Buckets[x] += pixel * other_pixel; + } + } + c1 = COLOR_BINARY_MAX * 0.01f; + c2 = COLOR_BINARY_MAX * 0.03f; + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(img, line); + uint8_t *other_row_ptr = (uint8_t *) other; + for (int x = 0, xx = (img->w + 7) / 8; x < xx; x++) { + for (int i = 0, ii = IM_MIN((img->w - (x * 8)), 8); i < ii; i++) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x + i); + int other_pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(other_row_ptr, x + i); + state->sumBucketsOfX[x] += pixel; + state->sumBucketsOfY[x] += other_pixel; + state->sum2BucketsOfX[x] += pixel * pixel; + state->sum2BucketsOfY[x] += other_pixel * other_pixel; + state->sum2Buckets[x] += pixel * other_pixel; + } + } + c1 = COLOR_GRAYSCALE_MAX * 0.01f; + c2 = COLOR_GRAYSCALE_MAX * 0.03f; + break; + } + case PIXFORMAT_RGB565: { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(img, line); + uint16_t *other_row_ptr = (uint16_t *) other; + for (int x = 0, xx = (img->w + 7) / 8; x < xx; x++) { + for (int i = 0, ii = IM_MIN((img->w - (x * 8)), 8); i < ii; i++) { + int pixel = COLOR_RGB565_TO_L(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x + i)); + int other_pixel = COLOR_RGB565_TO_L(IMAGE_GET_RGB565_PIXEL_FAST(other_row_ptr, x + i)); + state->sumBucketsOfX[x] += pixel; + state->sumBucketsOfY[x] += other_pixel; + state->sum2BucketsOfX[x] += pixel * pixel; + state->sum2BucketsOfY[x] += other_pixel * other_pixel; + state->sum2Buckets[x] += pixel * other_pixel; + } + } + c1 = COLOR_L_MAX * 0.01f; + c2 = COLOR_L_MAX * 0.03f; + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(img, line); + pixel24_t *other_row_ptr = (pixel24_t *) other; + for (int x = 0, xx = (img->w + 7) / 8; x < xx; x++) { + for (int i = 0, ii = IM_MIN((img->w - (x * 8)), 8); i < ii; i++) { + int pixel = COLOR_RGB888_TO_L(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x + i)); + int other_pixel = COLOR_RGB888_TO_L(IMAGE_GET_RGB888_PIXEL_FAST(other_row_ptr, x + i)); + state->sumBucketsOfX[x] += pixel; + state->sumBucketsOfY[x] += other_pixel; + state->sum2BucketsOfX[x] += pixel * pixel; + state->sum2BucketsOfY[x] += other_pixel * other_pixel; + state->sum2Buckets[x] += pixel * other_pixel; + } + } + c1 = COLOR_L_MAX * 0.01f; + c2 = COLOR_L_MAX * 0.03f; + break; + } + default: { + break; + } + } + + // https://en.wikipedia.org/wiki/Structural_similarity + if (((state->lines_processed + 1) == img->h) || (!((state->lines_processed + 1) % 8))) { + for (int x = 0, xx = (img->w + 7) / 8; x < xx; x++) { + int w = IM_MIN((img->w - (x * 8)), 8); + int h = IM_MIN((img->h - ((state->lines_processed / 8) * 8)), 8); + int size = w * h; + + int mx = state->sumBucketsOfX[x] / size; + int my = state->sumBucketsOfY[x] / size; + int vx = state->sum2BucketsOfX[x] - ((mx * state->sumBucketsOfX[x]) + (mx * state->sumBucketsOfX[x])) + (size * mx * mx); + int vy = state->sum2BucketsOfY[x] - ((my * state->sumBucketsOfY[x]) + (my * state->sumBucketsOfY[x])) + (size * my * my); + int vxy = state->sum2Buckets[x] - ((mx * state->sumBucketsOfY[x]) + (my * state->sumBucketsOfX[x])) + (size * mx * my); + + float ssim = ( ((2*mx*my) + c1) * ((2*vxy) + c2) ) / ( ((mx*mx) + (my*my) + c1) * (vx + vy + c2) ); + + state->similarity_sum += ssim; + state->similarity_sum_2 += ssim * ssim; + state->similarity_min = IM_MIN(state->similarity_min, ssim); + state->similarity_max = IM_MAX(state->similarity_max, ssim); + + state->sumBucketsOfX[x] = 0; + state->sumBucketsOfY[x] = 0; + state->sum2BucketsOfX[x] = 0; + state->sum2BucketsOfY[x] = 0; + state->sum2Buckets[x] = 0; + } + } + + state->lines_processed += 1; +} + +void imlib_get_similarity(image_t *img, const char *path, image_t *other, int scalar, float *avg, float *std, float *min, float *max) +{ + int h_blocks = (img->w + 7) / 8; + int v_blocks = (img->h + 7) / 8; + int blocks = h_blocks * v_blocks; + + int int_h_blocks = h_blocks * sizeof(int); + imlib_similatiry_line_op_state_t state; + state.sumBucketsOfX = fb_alloc0(int_h_blocks, FB_ALLOC_NO_HINT); + state.sumBucketsOfY = fb_alloc0(int_h_blocks, FB_ALLOC_NO_HINT); + state.sum2BucketsOfX = fb_alloc0(int_h_blocks, FB_ALLOC_NO_HINT); + state.sum2BucketsOfY = fb_alloc0(int_h_blocks, FB_ALLOC_NO_HINT); + state.sum2Buckets = fb_alloc0(int_h_blocks, FB_ALLOC_NO_HINT); + state.similarity_sum = 0.0f; + state.similarity_sum_2 = 0.0f; + state.similarity_min = FLT_MAX; + state.similarity_max = -FLT_MAX; + state.lines_processed = 0; + + imlib_image_operation(img, path, other, scalar, imlib_similarity_line_op, &state); + *avg = state.similarity_sum / blocks; + *std = fast_sqrtf((state.similarity_sum_2 / blocks) - ((*avg) * (*avg))); + *min = state.similarity_min; + *max = state.similarity_max; + + fb_free(state.sum2Buckets); + fb_free(state.sum2BucketsOfY); + fb_free(state.sum2BucketsOfX); + fb_free(state.sumBucketsOfY); + fb_free(state.sumBucketsOfX); +} +#endif //IMLIB_ENABLE_GET_SIMILARITY + +void imlib_get_histogram(histogram_t *out, image_t *ptr, rectangle_t *roi, list_t *thresholds, bool invert, image_t *other) +{ + switch (ptr->pixfmt) { + case PIXFORMAT_BINARY: { + memset(out->LBins, 0, out->LBinCount * sizeof(uint32_t)); + + int pixel_count = roi->w * roi->h; + float mult = (out->LBinCount - 1) / ((float) (COLOR_BINARY_MAX - COLOR_BINARY_MIN)); + + if ((!thresholds) || (!list_size(thresholds))) { + // Fast histogram code when no color thresholds list... + if (!other) { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x); + ((uint32_t *) out->LBins)[fast_roundf((pixel - COLOR_BINARY_MIN) * mult)]++; // needs to be roundf + } + } + } else { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y), *other_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(other, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x) ^ IMAGE_GET_BINARY_PIXEL_FAST(other_row_ptr, x); + ((uint32_t *) out->LBins)[fast_roundf((pixel - COLOR_BINARY_MIN) * mult)]++; // needs to be roundf + } + } + } + } else { + // Reset pixel count. + pixel_count = 0; + if (!other) { + for (list_lnk_t *it = iterator_start_from_head(thresholds); it; it = iterator_next(it)) { + color_thresholds_list_lnk_data_t lnk_data; + iterator_get(thresholds, it, &lnk_data); + + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x); + if (COLOR_THRESHOLD_BINARY(pixel, &lnk_data, invert)) { + ((uint32_t *) out->LBins)[fast_roundf((pixel - COLOR_BINARY_MIN) * mult)]++; // needs to be roundf + pixel_count++; + } + } + } + } + } else { + for (list_lnk_t *it = iterator_start_from_head(thresholds); it; it = iterator_next(it)) { + color_thresholds_list_lnk_data_t lnk_data; + iterator_get(thresholds, it, &lnk_data); + + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y), *other_row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(other, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x) ^ IMAGE_GET_BINARY_PIXEL_FAST(other_row_ptr, x); + if (COLOR_THRESHOLD_BINARY(pixel, &lnk_data, invert)) { + ((uint32_t *) out->LBins)[fast_roundf((pixel - COLOR_BINARY_MIN) * mult)]++; // needs to be roundf + pixel_count++; + } + } + } + } + } + } + + float pixels = IM_DIV(1, ((float) pixel_count)); + + for (int i = 0, j = out->LBinCount; i < j; i++) { + out->LBins[i] = ((uint32_t *) out->LBins)[i] * pixels; + } + + break; + } + case PIXFORMAT_GRAYSCALE: { + memset(out->LBins, 0, out->LBinCount * sizeof(uint32_t)); + + int pixel_count = roi->w * roi->h; + float mult = (out->LBinCount - 1) / ((float) (COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN)); + + if ((!thresholds) || (!list_size(thresholds))) { + // Fast histogram code when no color thresholds list... + if (!other) { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + ((uint32_t *) out->LBins)[fast_roundf((pixel - COLOR_GRAYSCALE_MIN) * mult)]++; // needs to be roundf + } + } + } else { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y), *other_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(other, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = abs(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x) - IMAGE_GET_GRAYSCALE_PIXEL_FAST(other_row_ptr, x)); + ((uint32_t *) out->LBins)[fast_roundf((pixel - COLOR_GRAYSCALE_MIN) * mult)]++; // needs to be roundf + } + } + } + } else { + // Reset pixel count. + pixel_count = 0; + if (!other) { + for (list_lnk_t *it = iterator_start_from_head(thresholds); it; it = iterator_next(it)) { + color_thresholds_list_lnk_data_t lnk_data; + iterator_get(thresholds, it, &lnk_data); + + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x); + if (COLOR_THRESHOLD_GRAYSCALE(pixel, &lnk_data, invert)) { + ((uint32_t *) out->LBins)[fast_roundf((pixel - COLOR_GRAYSCALE_MIN) * mult)]++; // needs to be roundf + pixel_count++; + } + } + } + } + } else { + for (list_lnk_t *it = iterator_start_from_head(thresholds); it; it = iterator_next(it)) { + color_thresholds_list_lnk_data_t lnk_data; + iterator_get(thresholds, it, &lnk_data); + + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y), *other_row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(other, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = abs(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x) - IMAGE_GET_GRAYSCALE_PIXEL_FAST(other_row_ptr, x)); + if (COLOR_THRESHOLD_GRAYSCALE(pixel, &lnk_data, invert)) { + ((uint32_t *) out->LBins)[fast_roundf((pixel - COLOR_GRAYSCALE_MIN) * mult)]++; // needs to be roundf + pixel_count++; + } + } + } + } + } + } + + float pixels = IM_DIV(1, ((float) pixel_count)); + + for (int i = 0, j = out->LBinCount; i < j; i++) { + out->LBins[i] = ((uint32_t *) out->LBins)[i] * pixels; + } + + break; + } + case PIXFORMAT_RGB565: { + memset(out->LBins, 0, out->LBinCount * sizeof(uint32_t)); + memset(out->ABins, 0, out->ABinCount * sizeof(uint32_t)); + memset(out->BBins, 0, out->BBinCount * sizeof(uint32_t)); + + int pixel_count = roi->w * roi->h; + float l_mult = (out->LBinCount - 1) / ((float) (COLOR_L_MAX - COLOR_L_MIN)); + float a_mult = (out->ABinCount - 1) / ((float) (COLOR_A_MAX - COLOR_A_MIN)); + float b_mult = (out->BBinCount - 1) / ((float) (COLOR_B_MAX - COLOR_B_MIN)); + + if ((!thresholds) || (!list_size(thresholds))) { + // Fast histogram code when no color thresholds list... + if (!other) { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); + ((uint32_t *) out->LBins)[fast_roundf((COLOR_RGB565_TO_L(pixel) - COLOR_L_MIN) * l_mult)]++; // needs to be roundf + ((uint32_t *) out->ABins)[fast_roundf((COLOR_RGB565_TO_A(pixel) - COLOR_A_MIN) * a_mult)]++; // needs to be roundf + ((uint32_t *) out->BBins)[fast_roundf((COLOR_RGB565_TO_B(pixel) - COLOR_B_MIN) * b_mult)]++; // needs to be roundf + } + } + } else { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y), *other_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(other, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x), other_pixel = IMAGE_GET_RGB565_PIXEL_FAST(other_row_ptr, x); + int r = abs(COLOR_RGB565_TO_R5(pixel) - COLOR_RGB565_TO_R5(other_pixel)); + int g = abs(COLOR_RGB565_TO_G6(pixel) - COLOR_RGB565_TO_G6(other_pixel)); + int b = abs(COLOR_RGB565_TO_B5(pixel) - COLOR_RGB565_TO_B5(other_pixel)); + pixel = COLOR_R5_G6_B5_TO_RGB565(r, g, b); + ((uint32_t *) out->LBins)[fast_roundf((COLOR_RGB565_TO_L(pixel) - COLOR_L_MIN) * l_mult)]++; // needs to be roundf + ((uint32_t *) out->ABins)[fast_roundf((COLOR_RGB565_TO_A(pixel) - COLOR_A_MIN) * a_mult)]++; // needs to be roundf + ((uint32_t *) out->BBins)[fast_roundf((COLOR_RGB565_TO_B(pixel) - COLOR_B_MIN) * b_mult)]++; // needs to be roundf + } + } + } + } else { + // Reset pixel count. + pixel_count = 0; + if (!other) { + for (list_lnk_t *it = iterator_start_from_head(thresholds); it; it = iterator_next(it)) { + color_thresholds_list_lnk_data_t lnk_data; + iterator_get(thresholds, it, &lnk_data); + + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x); + if (COLOR_THRESHOLD_RGB565(pixel, &lnk_data, invert)) { + ((uint32_t *) out->LBins)[fast_roundf((COLOR_RGB565_TO_L(pixel) - COLOR_L_MIN) * l_mult)]++; // needs to be roundf + ((uint32_t *) out->ABins)[fast_roundf((COLOR_RGB565_TO_A(pixel) - COLOR_A_MIN) * a_mult)]++; // needs to be roundf + ((uint32_t *) out->BBins)[fast_roundf((COLOR_RGB565_TO_B(pixel) - COLOR_B_MIN) * b_mult)]++; // needs to be roundf + pixel_count++; + } + } + } + } + } else { + for (list_lnk_t *it = iterator_start_from_head(thresholds); it; it = iterator_next(it)) { + color_thresholds_list_lnk_data_t lnk_data; + iterator_get(thresholds, it, &lnk_data); + + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y), *other_row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(other, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x), other_pixel = IMAGE_GET_RGB565_PIXEL_FAST(other_row_ptr, x); + int r = abs(COLOR_RGB565_TO_R5(pixel) - COLOR_RGB565_TO_R5(other_pixel)); + int g = abs(COLOR_RGB565_TO_G6(pixel) - COLOR_RGB565_TO_G6(other_pixel)); + int b = abs(COLOR_RGB565_TO_B5(pixel) - COLOR_RGB565_TO_B5(other_pixel)); + pixel = COLOR_R5_G6_B5_TO_RGB565(r, g, b); + if (COLOR_THRESHOLD_RGB565(pixel, &lnk_data, invert)) { + ((uint32_t *) out->LBins)[fast_roundf((COLOR_RGB565_TO_L(pixel) - COLOR_L_MIN) * l_mult)]++; // needs to be roundf + ((uint32_t *) out->ABins)[fast_roundf((COLOR_RGB565_TO_A(pixel) - COLOR_A_MIN) * a_mult)]++; // needs to be roundf + ((uint32_t *) out->BBins)[fast_roundf((COLOR_RGB565_TO_B(pixel) - COLOR_B_MIN) * b_mult)]++; // needs to be roundf + pixel_count++; + } + } + } + } + } + } + + float pixels = IM_DIV(1, ((float) pixel_count)); + + for (int i = 0, j = out->LBinCount; i < j; i++) { + out->LBins[i] = ((uint32_t *) out->LBins)[i] * pixels; + } + + for (int i = 0, j = out->ABinCount; i < j; i++) { + out->ABins[i] = ((uint32_t *) out->ABins)[i] * pixels; + } + + for (int i = 0, j = out->BBinCount; i < j; i++) { + out->BBins[i] = ((uint32_t *) out->BBins)[i] * pixels; + } + + break; + } + case PIXFORMAT_RGB888: { + memset(out->LBins, 0, out->LBinCount * sizeof(uint32_t)); + memset(out->ABins, 0, out->ABinCount * sizeof(uint32_t)); + memset(out->BBins, 0, out->BBinCount * sizeof(uint32_t)); + + int pixel_count = roi->w * roi->h; + float l_mult = (out->LBinCount - 1) / ((float) (COLOR_L_MAX - COLOR_L_MIN)); + float a_mult = (out->ABinCount - 1) / ((float) (COLOR_A_MAX - COLOR_A_MIN)); + float b_mult = (out->BBinCount - 1) / ((float) (COLOR_B_MAX - COLOR_B_MIN)); + + if ((!thresholds) || (!list_size(thresholds))) { + // Fast histogram code when no color thresholds list... + if (!other) { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + ((uint32_t *) out->LBins)[fast_roundf((COLOR_RGB888_TO_L(pixel) - COLOR_L_MIN) * l_mult)]++; // needs to be roundf + ((uint32_t *) out->ABins)[fast_roundf((COLOR_RGB888_TO_A(pixel) - COLOR_A_MIN) * a_mult)]++; // needs to be roundf + ((uint32_t *) out->BBins)[fast_roundf((COLOR_RGB888_TO_B(pixel) - COLOR_B_MIN) * b_mult)]++; // needs to be roundf + } + } + } else { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y), *other_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(other, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x), other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(other_row_ptr, x); + int r = abs(COLOR_RGB888_TO_R8(pixel) - COLOR_RGB888_TO_R8(other_pixel)); + int g = abs(COLOR_RGB888_TO_G8(pixel) - COLOR_RGB888_TO_G8(other_pixel)); + int b = abs(COLOR_RGB888_TO_B8(pixel) - COLOR_RGB888_TO_B8(other_pixel)); + pixel = COLOR_R8_G8_B8_TO_RGB888(r, g, b); + ((uint32_t *) out->LBins)[fast_roundf((COLOR_RGB888_TO_L(pixel) - COLOR_L_MIN) * l_mult)]++; // needs to be roundf + ((uint32_t *) out->ABins)[fast_roundf((COLOR_RGB888_TO_A(pixel) - COLOR_A_MIN) * a_mult)]++; // needs to be roundf + ((uint32_t *) out->BBins)[fast_roundf((COLOR_RGB888_TO_B(pixel) - COLOR_B_MIN) * b_mult)]++; // needs to be roundf + } + } + } + } else { + // Reset pixel count. + pixel_count = 0; + if (!other) { + for (list_lnk_t *it = iterator_start_from_head(thresholds); it; it = iterator_next(it)) { + color_thresholds_list_lnk_data_t lnk_data; + iterator_get(thresholds, it, &lnk_data); + + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x); + if (COLOR_THRESHOLD_RGB888(pixel, &lnk_data, invert)) { + ((uint32_t *) out->LBins)[fast_roundf((COLOR_RGB888_TO_L(pixel) - COLOR_L_MIN) * l_mult)]++; // needs to be roundf + ((uint32_t *) out->ABins)[fast_roundf((COLOR_RGB888_TO_A(pixel) - COLOR_A_MIN) * a_mult)]++; // needs to be roundf + ((uint32_t *) out->BBins)[fast_roundf((COLOR_RGB888_TO_B(pixel) - COLOR_B_MIN) * b_mult)]++; // needs to be roundf + pixel_count++; + } + } + } + } + } else { + for (list_lnk_t *it = iterator_start_from_head(thresholds); it; it = iterator_next(it)) { + color_thresholds_list_lnk_data_t lnk_data; + iterator_get(thresholds, it, &lnk_data); + + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y++) { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y), *other_row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(other, y); + for (int x = roi->x, xx = roi->x + roi->w; x < xx; x++) { + int pixel = IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x), other_pixel = IMAGE_GET_RGB888_PIXEL_FAST(other_row_ptr, x); + int r = abs(COLOR_RGB888_TO_R8(pixel) - COLOR_RGB888_TO_R8(other_pixel)); + int g = abs(COLOR_RGB888_TO_G8(pixel) - COLOR_RGB888_TO_G8(other_pixel)); + int b = abs(COLOR_RGB888_TO_B8(pixel) - COLOR_RGB888_TO_B8(other_pixel)); + pixel = COLOR_R8_G8_B8_TO_RGB888(r, g, b); + if (COLOR_THRESHOLD_RGB888(pixel, &lnk_data, invert)) { + ((uint32_t *) out->LBins)[fast_roundf((COLOR_RGB888_TO_L(pixel) - COLOR_L_MIN) * l_mult)]++; // needs to be roundf + ((uint32_t *) out->ABins)[fast_roundf((COLOR_RGB888_TO_A(pixel) - COLOR_A_MIN) * a_mult)]++; // needs to be roundf + ((uint32_t *) out->BBins)[fast_roundf((COLOR_RGB888_TO_B(pixel) - COLOR_B_MIN) * b_mult)]++; // needs to be roundf + pixel_count++; + } + } + } + } + } + } + + float pixels = IM_DIV(1, ((float) pixel_count)); + + for (int i = 0, j = out->LBinCount; i < j; i++) { + out->LBins[i] = ((uint32_t *) out->LBins)[i] * pixels; + } + + for (int i = 0, j = out->ABinCount; i < j; i++) { + out->ABins[i] = ((uint32_t *) out->ABins)[i] * pixels; + } + + for (int i = 0, j = out->BBinCount; i < j; i++) { + out->BBins[i] = ((uint32_t *) out->BBins)[i] * pixels; + } + + break; + } + default: { + break; + } + } +} + +void imlib_get_percentile(percentile_t *out, pixformat_t pixfmt, histogram_t *ptr, float percentile) +{ + memset(out, 0, sizeof(percentile_t)); + switch (pixfmt) { + case PIXFORMAT_BINARY: { + float mult = (COLOR_BINARY_MAX - COLOR_BINARY_MIN) / ((float) (ptr->LBinCount - 1)); + float median_count = 0; + + for (int i = 0, j = ptr->LBinCount; i < j; i++) { + if ((median_count < percentile) && (percentile <= (median_count + ptr->LBins[i]))) { + out->LValue = fast_floorf((i * mult) + COLOR_BINARY_MIN); + break; + } + + median_count += ptr->LBins[i]; + } + break; + } + case PIXFORMAT_GRAYSCALE: { + float mult = (COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN) / ((float) (ptr->LBinCount - 1)); + float median_count = 0; + + for (int i = 0, j = ptr->LBinCount; i < j; i++) { + if ((median_count < percentile) && (percentile <= (median_count + ptr->LBins[i]))) { + out->LValue = fast_floorf((i * mult) + COLOR_GRAYSCALE_MIN); + break; + } + + median_count += ptr->LBins[i]; + } + break; + } + case PIXFORMAT_RGB565: { + { + float mult = (COLOR_L_MAX - COLOR_L_MIN) / ((float) (ptr->LBinCount - 1)); + float median_count = 0; + + for (int i = 0, j = ptr->LBinCount; i < j; i++) { + if ((median_count < percentile) && (percentile <= (median_count + ptr->LBins[i]))) { + out->LValue = fast_floorf((i * mult) + COLOR_L_MIN); + break; + } + + median_count += ptr->LBins[i]; + } + } + { + float mult = (COLOR_A_MAX - COLOR_A_MIN) / ((float) (ptr->ABinCount - 1)); + float median_count = 0; + + for (int i = 0, j = ptr->ABinCount; i < j; i++) { + if ((median_count < percentile) && (percentile <= (median_count + ptr->ABins[i]))) { + out->AValue = fast_floorf((i * mult) + COLOR_A_MIN); + break; + } + + median_count += ptr->ABins[i]; + } + } + { + float mult = (COLOR_B_MAX - COLOR_B_MIN) / ((float) (ptr->BBinCount - 1)); + float median_count = 0; + + for (int i = 0, j = ptr->BBinCount; i < j; i++) { + if ((median_count < percentile) && (percentile <= (median_count + ptr->BBins[i]))) { + out->BValue = fast_floorf((i * mult) + COLOR_B_MIN); + break; + } + + median_count += ptr->BBins[i]; + } + } + break; + } + case PIXFORMAT_RGB888: { + { + float mult = (COLOR_L_MAX - COLOR_L_MIN) / ((float) (ptr->LBinCount - 1)); + float median_count = 0; + + for (int i = 0, j = ptr->LBinCount; i < j; i++) { + if ((median_count < percentile) && (percentile <= (median_count + ptr->LBins[i]))) { + out->LValue = fast_floorf((i * mult) + COLOR_L_MIN); + break; + } + + median_count += ptr->LBins[i]; + } + } + { + float mult = (COLOR_A_MAX - COLOR_A_MIN) / ((float) (ptr->ABinCount - 1)); + float median_count = 0; + + for (int i = 0, j = ptr->ABinCount; i < j; i++) { + if ((median_count < percentile) && (percentile <= (median_count + ptr->ABins[i]))) { + out->AValue = fast_floorf((i * mult) + COLOR_A_MIN); + break; + } + + median_count += ptr->ABins[i]; + } + } + { + float mult = (COLOR_B_MAX - COLOR_B_MIN) / ((float) (ptr->BBinCount - 1)); + float median_count = 0; + + for (int i = 0, j = ptr->BBinCount; i < j; i++) { + if ((median_count < percentile) && (percentile <= (median_count + ptr->BBins[i]))) { + out->BValue = fast_floorf((i * mult) + COLOR_B_MIN); + break; + } + + median_count += ptr->BBins[i]; + } + } + break; + } + default: { + break; + } + } +} + +static int ostu(int bincount, float *bins) +{ + float cdf[bincount]; memset(cdf, 0, bincount * sizeof(float)); + float weighted_cdf[bincount]; memset(weighted_cdf, 0, bincount * sizeof(float)); + + cdf[0] = bins[0]; + weighted_cdf[0] = 0 * bins[0]; + + for (int i = 1; i < bincount; i++) { + cdf[i] = cdf[i - 1] + bins[i]; + weighted_cdf[i] = weighted_cdf[i - 1] + (i * bins[i]); + } + + float variance[bincount]; memset(variance, 0, bincount * sizeof(float)); + float max_variance = 0.0f; + int threshold = 0; + + for (int i = 0, ii = bincount - 1; i < ii; i++) { + + if ((cdf[i] != 0.0f) && (cdf[i] != 1.0f)) { + variance[i] = powf((cdf[i] * weighted_cdf[bincount - 1]) - weighted_cdf[i], 2.0f) / (cdf[i] * (1.0f - cdf[i])); + } else { + variance[i] = 0.0f; + } + + if (variance[i] > max_variance) { + max_variance = variance[i]; + threshold = i; + } + } + + return threshold; +} + +void imlib_get_threshold(threshold_t *out, pixformat_t pixfmt, histogram_t *ptr) +{ + memset(out, 0, sizeof(threshold_t)); + switch (pixfmt) { + case PIXFORMAT_BINARY: { + out->LValue = (ostu(ptr->LBinCount, ptr->LBins) * (COLOR_BINARY_MAX - COLOR_BINARY_MIN)) / (ptr->LBinCount - 1); + break; + } + case PIXFORMAT_GRAYSCALE: { + out->LValue = (ostu(ptr->LBinCount, ptr->LBins) * (COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN)) / (ptr->LBinCount - 1); + break; + } + case PIXFORMAT_RGB565: { + out->LValue = (ostu(ptr->LBinCount, ptr->LBins) * (COLOR_L_MAX - COLOR_L_MIN)) / (ptr->LBinCount - 1); + out->AValue = (ostu(ptr->ABinCount, ptr->ABins) * (COLOR_A_MAX - COLOR_A_MIN)) / (ptr->ABinCount - 1); + out->BValue = (ostu(ptr->BBinCount, ptr->BBins) * (COLOR_B_MAX - COLOR_B_MIN)) / (ptr->BBinCount - 1); + break; + } + case PIXFORMAT_RGB888: { + out->LValue = (ostu(ptr->LBinCount, ptr->LBins) * (COLOR_L_MAX - COLOR_L_MIN)) / (ptr->LBinCount - 1); + out->AValue = (ostu(ptr->ABinCount, ptr->ABins) * (COLOR_A_MAX - COLOR_A_MIN)) / (ptr->ABinCount - 1); + out->BValue = (ostu(ptr->BBinCount, ptr->BBins) * (COLOR_B_MAX - COLOR_B_MIN)) / (ptr->BBinCount - 1); + break; + } + default: { + break; + } + } +} + +void imlib_get_statistics(statistics_t *out, pixformat_t pixfmt, histogram_t *ptr) +{ + memset(out, 0, sizeof(statistics_t)); + switch (pixfmt) { + case PIXFORMAT_BINARY: { + float mult = (COLOR_BINARY_MAX - COLOR_BINARY_MIN) / ((float) (ptr->LBinCount - 1)); + + float avg = 0; + float stdev = 0; + float median_count = 0; + float mode_count = 0; + bool min_flag = false; + + for (int i = 0, j = ptr->LBinCount; i < j; i++) { + float value_f = (i * mult) + COLOR_BINARY_MIN; + int value = fast_floorf(value_f); + + avg += value_f * ptr->LBins[i]; + stdev += value_f * value_f * ptr->LBins[i]; + + if ((median_count < 0.25f) && (0.25f <= (median_count + ptr->LBins[i]))) { + out->LLQ = value; + } + + if ((median_count < 0.5f) && (0.5f <= (median_count + ptr->LBins[i]))) { + out->LMedian = value; + } + + if ((median_count < 0.75f) && (0.75f <= (median_count + ptr->LBins[i]))) { + out->LUQ = value; + } + + if (ptr->LBins[i] > mode_count) { + mode_count = ptr->LBins[i]; + out->LMode = value; + } + + if ((ptr->LBins[i] > 0.0f) && (!min_flag)) { + min_flag = true; + out->LMin = value; + } + + if (ptr->LBins[i] > 0.0f) { + out->LMax = value; + } + + median_count += ptr->LBins[i]; + } + + out->LMean = fast_floorf(avg); + out->LSTDev = fast_floorf(fast_sqrtf(stdev - (avg * avg))); + break; + } + case PIXFORMAT_GRAYSCALE: { + float mult = (COLOR_GRAYSCALE_MAX - COLOR_GRAYSCALE_MIN) / ((float) (ptr->LBinCount - 1)); + + float avg = 0; + float stdev = 0; + float median_count = 0; + float mode_count = 0; + bool min_flag = false; + + for (int i = 0, j = ptr->LBinCount; i < j; i++) { + float value_f = (i * mult) + COLOR_GRAYSCALE_MIN; + int value = fast_floorf(value_f); + + avg += value_f * ptr->LBins[i]; + stdev += value_f * value_f * ptr->LBins[i]; + + if ((median_count < 0.25f) && (0.25f <= (median_count + ptr->LBins[i]))) { + out->LLQ = value; + } + + if ((median_count < 0.5f) && (0.5f <= (median_count + ptr->LBins[i]))) { + out->LMedian = value; + } + + if ((median_count < 0.75f) && (0.75f <= (median_count + ptr->LBins[i]))) { + out->LUQ = value; + } + + if (ptr->LBins[i] > mode_count) { + mode_count = ptr->LBins[i]; + out->LMode = value; + } + + if ((ptr->LBins[i] > 0.0f) && (!min_flag)) { + min_flag = true; + out->LMin = value; + } + + if (ptr->LBins[i] > 0.0f) { + out->LMax = value; + } + + median_count += ptr->LBins[i]; + } + + out->LMean = fast_floorf(avg); + out->LSTDev = fast_floorf(fast_sqrtf(stdev - (avg * avg))); + break; + } + case PIXFORMAT_RGB565: { + { + float mult = (COLOR_L_MAX - COLOR_L_MIN) / ((float) (ptr->LBinCount - 1)); + + float avg = 0; + float stdev = 0; + float median_count = 0; + float mode_count = 0; + bool min_flag = false; + + for (int i = 0, j = ptr->LBinCount; i < j; i++) { + float value_f = (i * mult) + COLOR_L_MIN; + int value = fast_floorf(value_f); + + avg += value_f * ptr->LBins[i]; + stdev += value_f * value_f * ptr->LBins[i]; + + if ((median_count < 0.25f) && (0.25f <= (median_count + ptr->LBins[i]))) { + out->LLQ = value; + } + + if ((median_count < 0.5f) && (0.5f <= (median_count + ptr->LBins[i]))) { + out->LMedian = value; + } + + if ((median_count < 0.75f) && (0.75f <= (median_count + ptr->LBins[i]))) { + out->LUQ = value; + } + + if (ptr->LBins[i] > mode_count) { + mode_count = ptr->LBins[i]; + out->LMode = value; + } + + if ((ptr->LBins[i] > 0.0f) && (!min_flag)) { + min_flag = true; + out->LMin = value; + } + + if (ptr->LBins[i] > 0.0f) { + out->LMax = value; + } + + median_count += ptr->LBins[i]; + } + + out->LMean = fast_floorf(avg); + out->LSTDev = fast_floorf(fast_sqrtf(stdev - (avg * avg))); + } + { + float mult = (COLOR_A_MAX - COLOR_A_MIN) / ((float) (ptr->ABinCount - 1)); + + float avg = 0; + float stdev = 0; + float median_count = 0; + float mode_count = 0; + bool min_flag = false; + + for (int i = 0, j = ptr->ABinCount; i < j; i++) { + float value_f = (i * mult) + COLOR_A_MIN; + int value = fast_floorf(value_f); + + avg += value_f * ptr->ABins[i]; + stdev += value_f * value_f * ptr->ABins[i]; + + if ((median_count < 0.25f) && (0.25f <= (median_count + ptr->ABins[i]))) { + out->ALQ = value; + } + + if ((median_count < 0.5f) && (0.5f <= (median_count + ptr->ABins[i]))) { + out->AMedian = value; + } + + if ((median_count < 0.75f) && (0.75f <= (median_count + ptr->ABins[i]))) { + out->AUQ = value; + } + + if (ptr->ABins[i] > mode_count) { + mode_count = ptr->ABins[i]; + out->AMode = value; + } + + if ((ptr->ABins[i] > 0.0f) && (!min_flag)) { + min_flag = true; + out->AMin = value; + } + + if (ptr->ABins[i] > 0.0f) { + out->AMax = value; + } + + median_count += ptr->ABins[i]; + } + + out->AMean = fast_floorf(avg); + out->ASTDev = fast_floorf(fast_sqrtf(stdev - (avg * avg))); + } + { + float mult = (COLOR_B_MAX - COLOR_B_MIN) / ((float) (ptr->BBinCount - 1)); + + float avg = 0; + float stdev = 0; + float median_count = 0; + float mode_count = 0; + bool min_flag = false; + + for (int i = 0, j = ptr->BBinCount; i < j; i++) { + float value_f = (i * mult) + COLOR_B_MIN; + int value = fast_floorf(value_f); + + avg += value_f * ptr->BBins[i]; + stdev += value_f * value_f * ptr->BBins[i]; + + if ((median_count < 0.25f) && (0.25f <= (median_count + ptr->BBins[i]))) { + out->BLQ = value; + } + + if ((median_count < 0.5f) && (0.5f <= (median_count + ptr->BBins[i]))) { + out->BMedian = value; + } + + if ((median_count < 0.75f) && (0.75f <= (median_count + ptr->BBins[i]))) { + out->BUQ = value; + } + + if (ptr->BBins[i] > mode_count) { + mode_count = ptr->BBins[i]; + out->BMode = value; + } + + if ((ptr->BBins[i] > 0.0f) && (!min_flag)) { + min_flag = true; + out->BMin = value; + } + + if (ptr->BBins[i] > 0.0f) { + out->BMax = value; + } + + median_count += ptr->BBins[i]; + } + + out->BMean = fast_floorf(avg); + out->BSTDev = fast_floorf(fast_sqrtf(stdev - (avg * avg))); + } + break; + } + case PIXFORMAT_RGB888: { + { + float mult = (COLOR_L_MAX - COLOR_L_MIN) / ((float) (ptr->LBinCount - 1)); + + float avg = 0; + float stdev = 0; + float median_count = 0; + float mode_count = 0; + bool min_flag = false; + + for (int i = 0, j = ptr->LBinCount; i < j; i++) { + float value_f = (i * mult) + COLOR_L_MIN; + int value = fast_floorf(value_f); + + avg += value_f * ptr->LBins[i]; + stdev += value_f * value_f * ptr->LBins[i]; + + if ((median_count < 0.25f) && (0.25f <= (median_count + ptr->LBins[i]))) { + out->LLQ = value; + } + + if ((median_count < 0.5f) && (0.5f <= (median_count + ptr->LBins[i]))) { + out->LMedian = value; + } + + if ((median_count < 0.75f) && (0.75f <= (median_count + ptr->LBins[i]))) { + out->LUQ = value; + } + + if (ptr->LBins[i] > mode_count) { + mode_count = ptr->LBins[i]; + out->LMode = value; + } + + if ((ptr->LBins[i] > 0.0f) && (!min_flag)) { + min_flag = true; + out->LMin = value; + } + + if (ptr->LBins[i] > 0.0f) { + out->LMax = value; + } + + median_count += ptr->LBins[i]; + } + + out->LMean = fast_floorf(avg); + out->LSTDev = fast_floorf(fast_sqrtf(stdev - (avg * avg))); + } + { + float mult = (COLOR_A_MAX - COLOR_A_MIN) / ((float) (ptr->ABinCount - 1)); + + float avg = 0; + float stdev = 0; + float median_count = 0; + float mode_count = 0; + bool min_flag = false; + + for (int i = 0, j = ptr->ABinCount; i < j; i++) { + float value_f = (i * mult) + COLOR_A_MIN; + int value = fast_floorf(value_f); + + avg += value_f * ptr->ABins[i]; + stdev += value_f * value_f * ptr->ABins[i]; + + if ((median_count < 0.25f) && (0.25f <= (median_count + ptr->ABins[i]))) { + out->ALQ = value; + } + + if ((median_count < 0.5f) && (0.5f <= (median_count + ptr->ABins[i]))) { + out->AMedian = value; + } + + if ((median_count < 0.75f) && (0.75f <= (median_count + ptr->ABins[i]))) { + out->AUQ = value; + } + + if (ptr->ABins[i] > mode_count) { + mode_count = ptr->ABins[i]; + out->AMode = value; + } + + if ((ptr->ABins[i] > 0.0f) && (!min_flag)) { + min_flag = true; + out->AMin = value; + } + + if (ptr->ABins[i] > 0.0f) { + out->AMax = value; + } + + median_count += ptr->ABins[i]; + } + + out->AMean = fast_floorf(avg); + out->ASTDev = fast_floorf(fast_sqrtf(stdev - (avg * avg))); + } + { + float mult = (COLOR_B_MAX - COLOR_B_MIN) / ((float) (ptr->BBinCount - 1)); + + float avg = 0; + float stdev = 0; + float median_count = 0; + float mode_count = 0; + bool min_flag = false; + + for (int i = 0, j = ptr->BBinCount; i < j; i++) { + float value_f = (i * mult) + COLOR_B_MIN; + int value = fast_floorf(value_f); + + avg += value_f * ptr->BBins[i]; + stdev += value_f * value_f * ptr->BBins[i]; + + if ((median_count < 0.25f) && (0.25f <= (median_count + ptr->BBins[i]))) { + out->BLQ = value; + } + + if ((median_count < 0.5f) && (0.5f <= (median_count + ptr->BBins[i]))) { + out->BMedian = value; + } + + if ((median_count < 0.75f) && (0.75f <= (median_count + ptr->BBins[i]))) { + out->BUQ = value; + } + + if (ptr->BBins[i] > mode_count) { + mode_count = ptr->BBins[i]; + out->BMode = value; + } + + if ((ptr->BBins[i] > 0.0f) && (!min_flag)) { + min_flag = true; + out->BMin = value; + } + + if (ptr->BBins[i] > 0.0f) { + out->BMax = value; + } + + median_count += ptr->BBins[i]; + } + + out->BMean = fast_floorf(avg); + out->BSTDev = fast_floorf(fast_sqrtf(stdev - (avg * avg))); + } + break; + } + default: { + break; + } + } +} + +static int get_median(int *array, int array_sum, int array_len) +{ + const int median_threshold = (array_sum + 1) / 2; + int median_count = 0; + + for (int i = 0; i < array_len; i++) { + if ((median_count < median_threshold) && (median_threshold <= (median_count + array[i]))) return i; + median_count += array[i]; + } + + return array_len - 1; +} + +static int get_median_l(long long *array, long long array_sum, int array_len) +{ + const long long median_threshold = (array_sum + 1) / 2; + long long median_count = 0; + + for (int i = 0; i < array_len; i++) { + if ((median_count < median_threshold) && (median_threshold <= (median_count + array[i]))) return i; + median_count += array[i]; + } + + return array_len - 1; +} + +bool imlib_get_regression(find_lines_list_lnk_data_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, + list_t *thresholds, bool invert, unsigned int area_threshold, unsigned int pixels_threshold, bool robust) +{ + bool result = false; + memset(out, 0, sizeof(find_lines_list_lnk_data_t)); + + if (!robust) { // Least Squares + int blob_x1 = roi->x + roi->w - 1; + int blob_y1 = roi->y + roi->h - 1; + int blob_x2 = roi->x; + int blob_y2 = roi->y; + int blob_pixels = 0; + int blob_cx = 0; + int blob_cy = 0; + long long blob_a = 0; + long long blob_b = 0; + long long blob_c = 0; + + for (list_lnk_t *it = iterator_start_from_head(thresholds); it; it = iterator_next(it)) { + color_thresholds_list_lnk_data_t lnk_data; + iterator_get(thresholds, it, &lnk_data); + + switch (ptr->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y += y_stride) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w; x < xx; x += x_stride) { + if (COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { + blob_x1 = IM_MIN(blob_x1, x); + blob_y1 = IM_MIN(blob_y1, y); + blob_x2 = IM_MAX(blob_x2, x); + blob_y2 = IM_MAX(blob_y2, y); + blob_pixels += 1; + blob_cx += x; + blob_cy += y; + blob_a += x*x; + blob_b += x*y; + blob_c += y*y; + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y += y_stride) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w; x < xx; x += x_stride) { + if (COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { + blob_x1 = IM_MIN(blob_x1, x); + blob_y1 = IM_MIN(blob_y1, y); + blob_x2 = IM_MAX(blob_x2, x); + blob_y2 = IM_MAX(blob_y2, y); + blob_pixels += 1; + blob_cx += x; + blob_cy += y; + blob_a += x*x; + blob_b += x*y; + blob_c += y*y; + } + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y += y_stride) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w; x < xx; x += x_stride) { + if (COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { + blob_x1 = IM_MIN(blob_x1, x); + blob_y1 = IM_MIN(blob_y1, y); + blob_x2 = IM_MAX(blob_x2, x); + blob_y2 = IM_MAX(blob_y2, y); + blob_pixels += 1; + blob_cx += x; + blob_cy += y; + blob_a += x*x; + blob_b += x*y; + blob_c += y*y; + } + } + } + break; + } + case PIXFORMAT_RGB888: + { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y += y_stride) + { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w; x < xx; x += x_stride) + { + if (COLOR_THRESHOLD_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) + { + blob_x1 = IM_MIN(blob_x1, x); + blob_y1 = IM_MIN(blob_y1, y); + blob_x2 = IM_MAX(blob_x2, x); + blob_y2 = IM_MAX(blob_y2, y); + blob_pixels += 1; + blob_cx += x; + blob_cy += y; + blob_a += x * x; + blob_b += x * y; + blob_c += y * y; + } + } + } + break; + } + default: { + break; + } + } + } + + int w = blob_x2 - blob_x1; + int h = blob_y2 - blob_y1; + if (blob_pixels && ((w * h) >= area_threshold) && (blob_pixels >= pixels_threshold)) { + // http://www.cse.usf.edu/~r1k/MachineVisionBook/MachineVision.files/MachineVision_Chapter2.pdf + // https://www.strchr.com/standard_deviation_in_one_pass + // + // a = sigma(x*x) + (mx*sigma(x)) + (mx*sigma(x)) + (sigma()*mx*mx) + // b = sigma(x*y) + (mx*sigma(y)) + (my*sigma(x)) + (sigma()*mx*my) + // c = sigma(y*y) + (my*sigma(y)) + (my*sigma(y)) + (sigma()*my*my) + // + // blob_a = sigma(x*x) + // blob_b = sigma(x*y) + // blob_c = sigma(y*y) + // blob_cx = sigma(x) + // blob_cy = sigma(y) + // blob_pixels = sigma() + + int mx = blob_cx / blob_pixels; // x centroid + int my = blob_cy / blob_pixels; // y centroid + int small_blob_a = blob_a - ((mx * blob_cx) + (mx * blob_cx)) + (blob_pixels * mx * mx); + int small_blob_b = blob_b - ((mx * blob_cy) + (my * blob_cx)) + (blob_pixels * mx * my); + int small_blob_c = blob_c - ((my * blob_cy) + (my * blob_cy)) + (blob_pixels * my * my); + + float rotation = ((small_blob_a != small_blob_c) ? (fast_atan2f(2 * small_blob_b, small_blob_a - small_blob_c) / 2.0f) : 1.570796f) + 1.570796f; // PI/2 + + out->theta = fast_roundf(rotation * 57.295780) % 180; // * (180 / PI) + if (out->theta < 0) out->theta += 180; + out->rho = fast_roundf(((mx - roi->x) * cos_table[out->theta]) + ((my - roi->y) * sin_table[out->theta])); + + float part0 = (small_blob_a + small_blob_c) / 2.0f; + float f_b = (float) small_blob_b; + float f_a_c = (float) (small_blob_a - small_blob_c); + float part1 = fast_sqrtf((4 * f_b * f_b) + (f_a_c * f_a_c)) / 2.0f; + float p_add = fast_sqrtf(part0 + part1); + float p_sub = fast_sqrtf(part0 - part1); + float e_min = IM_MIN(p_add, p_sub); + float e_max = IM_MAX(p_add, p_sub); + out->magnitude = fast_roundf(e_max / e_min) - 1; // Circle -> [0, INF) -> Line + + if ((45 <= out->theta) && (out->theta < 135)) { + // y = (r - x cos(t)) / sin(t) + out->line.x1 = 0; + out->line.y1 = fast_roundf((out->rho - (out->line.x1 * cos_table[out->theta])) / sin_table[out->theta]); + out->line.x2 = roi->w - 1; + out->line.y2 = fast_roundf((out->rho - (out->line.x2 * cos_table[out->theta])) / sin_table[out->theta]); + } else { + // x = (r - y sin(t)) / cos(t); + out->line.y1 = 0; + out->line.x1 = fast_roundf((out->rho - (out->line.y1 * sin_table[out->theta])) / cos_table[out->theta]); + out->line.y2 = roi->h - 1; + out->line.x2 = fast_roundf((out->rho - (out->line.y2 * sin_table[out->theta])) / cos_table[out->theta]); + } + + if(lb_clip_line(&out->line, 0, 0, roi->w, roi->h)) { + out->line.x1 += roi->x; + out->line.y1 += roi->y; + out->line.x2 += roi->x; + out->line.y2 += roi->y; + // Move rho too. + out->rho += fast_roundf((roi->x * cos_table[out->theta]) + (roi->y * sin_table[out->theta])); + result = true; + } else { + memset(out, 0, sizeof(find_lines_list_lnk_data_t)); + } + } + } else { // Theil-Sen Estimator + int *x_histogram = fb_alloc0(ptr->w * sizeof(int), FB_ALLOC_NO_HINT); // Not roi so we don't have to adjust, we can burn the RAM. + int *y_histogram = fb_alloc0(ptr->h * sizeof(int), FB_ALLOC_NO_HINT); // Not roi so we don't have to adjust, we can burn the RAM. + + long long *x_delta_histogram = fb_alloc0((2 * ptr->w) * sizeof(long long), FB_ALLOC_NO_HINT); // Not roi so we don't have to adjust, we can burn the RAM. + long long *y_delta_histogram = fb_alloc0((2 * ptr->h) * sizeof(long long), FB_ALLOC_NO_HINT); // Not roi so we don't have to adjust, we can burn the RAM. + + uint32_t size; + point_t *points = (point_t *) fb_alloc_all(&size, FB_ALLOC_NO_HINT); + size_t points_max = size / sizeof(point_t); + size_t points_count = 0; + + if(points_max) { + int blob_x1 = roi->x + roi->w - 1; + int blob_y1 = roi->y + roi->h - 1; + int blob_x2 = roi->x; + int blob_y2 = roi->y; + int blob_pixels = 0; + + for (list_lnk_t *it = iterator_start_from_head(thresholds); it; it = iterator_next(it)) { + color_thresholds_list_lnk_data_t lnk_data; + iterator_get(thresholds, it, &lnk_data); + + switch (ptr->pixfmt) { + case PIXFORMAT_BINARY: { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y += y_stride) { + uint32_t *row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w; x < xx; x += x_stride) { + if (COLOR_THRESHOLD_BINARY(IMAGE_GET_BINARY_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { + blob_x1 = IM_MIN(blob_x1, x); + blob_y1 = IM_MIN(blob_y1, y); + blob_x2 = IM_MAX(blob_x2, x); + blob_y2 = IM_MAX(blob_y2, y); + blob_pixels += 1; + x_histogram[x]++; + y_histogram[y]++; + + if(points_count < points_max) { + point_init(&points[points_count], x, y); + points_count += 1; + } + } + } + } + break; + } + case PIXFORMAT_GRAYSCALE: { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y += y_stride) { + uint8_t *row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w; x < xx; x += x_stride) { + if (COLOR_THRESHOLD_GRAYSCALE(IMAGE_GET_GRAYSCALE_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { + blob_x1 = IM_MIN(blob_x1, x); + blob_y1 = IM_MIN(blob_y1, y); + blob_x2 = IM_MAX(blob_x2, x); + blob_y2 = IM_MAX(blob_y2, y); + blob_pixels += 1; + x_histogram[x]++; + y_histogram[y]++; + + if(points_count < points_max) { + point_init(&points[points_count], x, y); + points_count += 1; + } + } + } + } + break; + } + case PIXFORMAT_RGB565: { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y += y_stride) { + uint16_t *row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w; x < xx; x += x_stride) { + if (COLOR_THRESHOLD_RGB565(IMAGE_GET_RGB565_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) { + blob_x1 = IM_MIN(blob_x1, x); + blob_y1 = IM_MIN(blob_y1, y); + blob_x2 = IM_MAX(blob_x2, x); + blob_y2 = IM_MAX(blob_y2, y); + blob_pixels += 1; + x_histogram[x]++; + y_histogram[y]++; + + if(points_count < points_max) { + point_init(&points[points_count], x, y); + points_count += 1; + } + } + } + } + break; + } + case PIXFORMAT_RGB888: + { + for (int y = roi->y, yy = roi->y + roi->h; y < yy; y += y_stride) + { + pixel24_t *row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(ptr, y); + for (int x = roi->x + (y % x_stride), xx = roi->x + roi->w; x < xx; x += x_stride) + { + if (COLOR_THRESHOLD_RGB888(IMAGE_GET_RGB888_PIXEL_FAST(row_ptr, x), &lnk_data, invert)) + { + blob_x1 = IM_MIN(blob_x1, x); + blob_y1 = IM_MIN(blob_y1, y); + blob_x2 = IM_MAX(blob_x2, x); + blob_y2 = IM_MAX(blob_y2, y); + blob_pixels += 1; + x_histogram[x]++; + y_histogram[y]++; + + if (points_count < points_max) + { + point_init(&points[points_count], x, y); + points_count += 1; + } + } + } + } + break; + } + default: { + break; + } + } + } + + int w = blob_x2 - blob_x1; + int h = blob_y2 - blob_y1; + if (blob_pixels && ((w * h) >= area_threshold) && (blob_pixels >= pixels_threshold)) { + long long delta_sum = (points_count * (points_count - 1)) / 2; + + if (delta_sum) { + // The code below computes the average slope between all pairs of points. + // This is a N^2 operation that can easily blow up if the image is not threshold carefully... + + for(int i = 0; i < points_count; i++) { + point_t *p0 = &points[i]; + for(int j = i + 1; j < points_count; j++) { + point_t *p1 = &points[j]; + x_delta_histogram[p0->x - p1->x + ptr->w]++; // Note we allocated 1 extra above so we can do ptr->w instead of (ptr->w-1). + y_delta_histogram[p0->y - p1->y + ptr->h]++; // Note we allocated 1 extra above so we can do ptr->h instead of (ptr->h-1). + } + } + + int mx = get_median(x_histogram, blob_pixels, ptr->w); // Output doesn't need adjustment. + int my = get_median(y_histogram, blob_pixels, ptr->h); // Output doesn't need adjustment. + int mdx = get_median_l(x_delta_histogram, delta_sum, 2 * ptr->w) - ptr->w; // Fix offset. + int mdy = get_median_l(y_delta_histogram, delta_sum, 2 * ptr->h) - ptr->h; // Fix offset. + + float rotation = (mdx ? fast_atan2f(mdy, mdx) : 1.570796f) + 1.570796f; // PI/2 + + out->theta = fast_roundf(rotation * 57.295780) % 180; // * (180 / PI) + if (out->theta < 0) out->theta += 180; + out->rho = fast_roundf(((mx - roi->x) * cos_table[out->theta]) + ((my - roi->y) * sin_table[out->theta])); + + out->magnitude = fast_roundf(fast_sqrtf((mdx * mdx) + (mdy * mdy))); + + if ((45 <= out->theta) && (out->theta < 135)) { + // y = (r - x cos(t)) / sin(t) + out->line.x1 = 0; + out->line.y1 = fast_roundf((out->rho - (out->line.x1 * cos_table[out->theta])) / sin_table[out->theta]); + out->line.x2 = roi->w - 1; + out->line.y2 = fast_roundf((out->rho - (out->line.x2 * cos_table[out->theta])) / sin_table[out->theta]); + } else { + // x = (r - y sin(t)) / cos(t); + out->line.y1 = 0; + out->line.x1 = fast_roundf((out->rho - (out->line.y1 * sin_table[out->theta])) / cos_table[out->theta]); + out->line.y2 = roi->h - 1; + out->line.x2 = fast_roundf((out->rho - (out->line.y2 * sin_table[out->theta])) / cos_table[out->theta]); + } + + if(lb_clip_line(&out->line, 0, 0, roi->w, roi->h)) { + out->line.x1 += roi->x; + out->line.y1 += roi->y; + out->line.x2 += roi->x; + out->line.y2 += roi->y; + // Move rho too. + out->rho += fast_roundf((roi->x * cos_table[out->theta]) + (roi->y * sin_table[out->theta])); + result = true; + } else { + memset(out, 0, sizeof(find_lines_list_lnk_data_t)); + } + } + } + } + + fb_free(points); // points + fb_free(y_delta_histogram); // y_delta_histogram + fb_free(x_delta_histogram); // x_delta_histogram + fb_free(y_histogram); // y_histogram + fb_free(x_histogram); // x_histogram + } + + return result; +} diff --git a/github_source/minicv2/src/template.c b/github_source/minicv2/src/template.c new file mode 100644 index 0000000..2bc1fcc --- /dev/null +++ b/github_source/minicv2/src/template.c @@ -0,0 +1,278 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Template matching with NCC (Normalized Cross Correlation) using exhaustive and diamond search. + * + * References: + * Briechle, Kai, and Uwe D. Hanebeck. "Template matching using fast normalized cross correlation." Aerospace + * Lewis, J. P. "Fast normalized cross-correlation." + * Zhu, Shan, and Kai-Kuang Ma. "A new diamond search algorithm for fast block-matching motion estimation." + */ +#include +#include +#include + +#include "imlib.h" +#include "xalloc.h" + +static void set_dsp(int cx, int cy, point_t *pts, bool sdsp, int step) +{ + if (sdsp) { + // Small DSP + // 4 + // 3 0 1 + // 2 + pts[0].x = cx; + pts[0].y = cy; + + pts[1].x = cx + step/2; + pts[1].y = cy; + + pts[2].x = cx; + pts[2].y = cy + step/2; + + pts[3].x = cx - step/2; + pts[3].y = cy; + + pts[4].x = cx; + pts[4].y = cy - step/2; + } else { + // Large DSP + // 7 + // 6 8 + // 5 0 1 + // 4 2 + // 3 + pts[0].x = cx; + pts[0].y = cy; + + pts[1].x = cx + step; + pts[1].y = cy; + + pts[2].x = cx + step/2; + pts[2].y = cy + step/2; + + pts[3].x = cx; + pts[3].y = cy + step; + + pts[4].x = cx - step/2; + pts[4].y = cy + step/2; + + pts[5].x = cx - step; + pts[5].y = cy; + + pts[6].x = cx - step/2; + pts[6].y = cy - step/2; + + pts[7].x = cx; + pts[7].y = cy - step; + + pts[8].x = cx + step/2; + pts[8].y = cy - step/2; + + } +} + +static float find_block_ncc(image_t *f, image_t *t, i_image_t *sum, int t_mean, uint32_t t_sumsq, int u, int v) +{ + int w = t->w; + int h = t->h; + + int num = 0; + uint32_t f_sumsq=0; + + if (u < 0) { + u = 0; + } + + if (v < 0) { + v = 0; + } + + if (u+w >= f->w) { + w = f->w - u; + } + + if (v+h >= f->h) { + h = f->h - v; + } + + // Find the mean of the current patch + uint32_t f_sum = imlib_integral_lookup(sum, u, v, w, h); + uint32_t f_mean = f_sum / (w*h); + + // Find the normalized sum of squares of the image + for (int y=v; ydata[y*f->w+x]-f_mean; + int b = (int)t->data[(y-v)*t->w+(x-u)]-t_mean; + num += a*b; + f_sumsq += a*a; + } + } + + // Find the normalized cross-correlation + return (num/(fast_sqrtf(f_sumsq) * fast_sqrtf(t_sumsq))); +} + +float imlib_template_match_ds(image_t *f, image_t *t, rectangle_t *r) +{ + point_t pts[9]; + + // Integral images + i_image_t sum; + imlib_integral_image_alloc(&sum, f->w, f->h); + imlib_integral_image(f, &sum); + + // Normalized sum of squares of the template + int t_mean = 0; + uint32_t t_sumsq=0; + imlib_image_mean(t, &t_mean, &t_mean, &t_mean); + for (int i=0; i < (t->w*t->h); i++) { + int c = (int)t->data[i]-t_mean; + t_sumsq += c*c; + } + + int px = 0; + int py = 0; + + // Initial center point + int cx = f->w/2 - t->w/2; + int cy = f->h/2 - t->h/2; + + // Max cross-correlation + float max_xc=-FLT_MAX; + + // Start with the Large Diamond Search Pattern (LDSP) 9 points. + bool sdsp = false; + + // Step size == template width + int step = t->w; + + while (step > 0) { + // Set the Diamond Search Pattern (DSP). + set_dsp(cx, cy, pts, sdsp, step); + + // Set the number of search blocks (5 or 9 for SDSP and LDSP respectively). + int num_pts = (sdsp == true)? 5: 9; + + // Find the block with the highest NCC + for (int i=0; i= f->w || pts[i].y >= f->h) { + continue; + } + float blk_xc = find_block_ncc(f, t, &sum, t_mean, t_sumsq, pts[i].x, pts[i].y); + if (blk_xc > max_xc) { + px = pts[i].x; + py = pts[i].y; + max_xc = blk_xc; + } + } + + // If the highest correlation is found at the center block and search is using + // LDSP then the highest correlation is found, if not then switch search to SDSP. + if (px == cx && py == cy) { + // Note instead of switching to the smaller pattern, the step size can be reduced + // each time the highest correlation is found at the center, and break on step == 0. + // This makes DS much more accurate, but slower. + step --; + } + + // Set the new search center to the block with highest correlation + cx = px; + cy = py; + } + + r->x = cx; + r->y = cy; + r->w = t->w; + r->h = t->h; + + if (cx < 0) r->x = 0; + if (cy < 0) r->y = 0; + + if (cx+t->w > f->w) { + r->w = f->w - cx; + } + + if (cy+t->h > f->h) { + r->h = f->h - cy; + } + + imlib_integral_image_free(&sum); + + //printf("max xc: %f\n", (double) max_xc); + return max_xc; +} + +/* The NCC can be optimized using integral images and rectangular basis functions. + * See Kai Briechle's paper "Template Matching using Fast Normalized Cross Correlation". + * + * NOTE: only the denominator is optimized. + * + */ +float imlib_template_match_ex(image_t *f, image_t *t, rectangle_t *roi, int step, rectangle_t *r) +{ + int den_b=0; + float corr=0.0f; + + // Integral images + i_image_t sum; + i_image_t sumsq; + + imlib_integral_image_alloc(&sum, f->w, f->h); + imlib_integral_image_alloc(&sumsq, f->w, f->h); + + imlib_integral_image(f, &sum); + imlib_integral_image_sq(f, &sumsq); + + // Normalized sum of squares of the template + int t_mean = 0; + imlib_image_mean(t, &t_mean, &t_mean, &t_mean); + + for (int i=0; i < (t->w*t->h); i++) { + int c = (int)t->data[i]-t_mean; + den_b += c*c; + } + + for (int v=roi->y; v<=(roi->y+roi->h-t->h); v+=step) { + for (int u=roi->x; u<=(roi->x+roi->w-t->w); u+=step) { + int num = 0; + // The mean of the current patch + uint32_t f_sum = imlib_integral_lookup(&sum, u, v, t->w, t->h); + uint32_t f_sumsq = imlib_integral_lookup(&sumsq, u, v, t->w, t->h); + uint32_t f_mean = f_sum / (float) (t->w*t->h); + + // Normalized sum of squares of the image + for (int y=v; y<(v+t->h); y++) { + for (int x=u; x<(u+t->w); x++) { + int a = (int)f->data[y*f->w+x]-f_mean; + int b = (int)t->data[(y-v)*t->w+(x-u)]-t_mean; + num += a*b; + } + } + + uint32_t den_a = f_sumsq - f_sum * (f_sum / (float) (t->w * t->h)); + + // Find normalized cross-correlation + float c = num/(fast_sqrtf(den_a) * fast_sqrtf(den_b)); + + if (c > corr) { + corr = c; + r->x = u; + r->y = v; + r->w = t->w; + r->h = t->h; + } + } + } + + imlib_integral_image_free(&sum); + imlib_integral_image_free(&sumsq); + return corr; +} diff --git a/github_source/minicv2/src/umm_malloc.c b/github_source/minicv2/src/umm_malloc.c new file mode 100644 index 0000000..a30de95 --- /dev/null +++ b/github_source/minicv2/src/umm_malloc.c @@ -0,0 +1,684 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * UMM memory allocator. + */ +#include +// #include "py/runtime.h" +// #include "py/mphal.h" +#include "fb_alloc.h" +#include "umm_malloc.h" +#include "omv_boardconfig.h" +#include "imlib_config.h" +#include "imlib_io.h" +void umm_alloc_fail() +{ + imlib_printf(0, "MemoryError: Out of temporary Frame Buffer Heap Memory! Please reduce the resolution of the image you are running this algorithm on to bypass this issue!"); + // mp_raise_msg(&mp_type_MemoryError, + // MP_ERROR_TEXT("Out of temporary Frame Buffer Heap Memory!" + // " Please reduce the resolution of the image you are running this algorithm on to bypass this issue!")); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "umm_malloc.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//The MIT License (MIT) + +//Copyright (c) 2015 Ralph Hempel + +//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. + +/* ---------------------------------------------------------------------------- + * umm_malloc.c - a memory allocator for embedded systems (microcontrollers) + * + * See LICENSE for copyright notice + * See README.md for acknowledgements and description of internals + * ---------------------------------------------------------------------------- + * + * R.Hempel 2007-09-22 - Original + * R.Hempel 2008-12-11 - Added MIT License biolerplate + * - realloc() now looks to see if previous block is free + * - made common operations functions + * R.Hempel 2009-03-02 - Added macros to disable tasking + * - Added function to dump heap and check for valid free + * pointer + * R.Hempel 2009-03-09 - Changed name to umm_malloc to avoid conflicts with + * the mm_malloc() library functions + * - Added some test code to assimilate a free block + * with the very block if possible. Complicated and + * not worth the grief. + * D.Frank 2014-04-02 - Fixed heap configuration when UMM_TEST_MAIN is NOT set, + * added user-dependent configuration file umm_malloc_cfg.h + * R.Hempel 2016-12-04 - Add support for Unity test framework + * - Reorganize source files to avoid redundant content + * - Move integrity and poison checking to separate file + * R.Hempel 2017-12-29 - Fix bug in realloc when requesting a new block that + * results in OOM error - see Issue 11 + * ---------------------------------------------------------------------------- + */ + +/* A couple of macros to make packing structures less compiler dependent */ + +#define UMM_H_ATTPACKPRE +#define UMM_H_ATTPACKSUF __attribute__((__packed__)) + +#define UMM_BEST_FIT +#undef UMM_FIRST_FIT + +/* + * A couple of macros to make it easier to protect the memory allocator + * in a multitasking system. You should set these macros up to use whatever + * your system uses for this purpose. You can disable interrupts entirely, or + * just disable task switching - it's up to you + * + * NOTE WELL that these macros MUST be allowed to nest, because umm_free() is + * called from within umm_malloc() + */ + +#define UMM_CRITICAL_ENTRY() +#define UMM_CRITICAL_EXIT() + +#define DBGLOG_TRACE(format, ...) + +#define DBGLOG_DEBUG(format, ...) + +/* ------------------------------------------------------------------------- */ + +UMM_H_ATTPACKPRE typedef struct umm_ptr_t { + unsigned short int next; + unsigned short int prev; +} UMM_H_ATTPACKSUF umm_ptr; + + +UMM_H_ATTPACKPRE typedef struct umm_block_t { + union { + umm_ptr used; + } header; + union { + umm_ptr free; + unsigned char data[OMV_UMM_BLOCK_SIZE]; + } body; +} UMM_H_ATTPACKSUF umm_block; + +#define UMM_FREELIST_MASK (0x8000) +#define UMM_BLOCKNO_MASK (0x7FFF) + +/* ------------------------------------------------------------------------- */ + +umm_block *umm_heap = NULL; +unsigned short int umm_numblocks = 0; + +#define UMM_NUMBLOCKS (umm_numblocks) + +/* ------------------------------------------------------------------------ */ + +#define UMM_BLOCK(b) (umm_heap[b]) + +#define UMM_NBLOCK(b) (UMM_BLOCK(b).header.used.next) +#define UMM_PBLOCK(b) (UMM_BLOCK(b).header.used.prev) +#define UMM_NFREE(b) (UMM_BLOCK(b).body.free.next) +#define UMM_PFREE(b) (UMM_BLOCK(b).body.free.prev) +#define UMM_DATA(b) (UMM_BLOCK(b).body.data) + +/* ------------------------------------------------------------------------ */ + +static unsigned short int umm_blocks( size_t size ) { + + /* + * The calculation of the block size is not too difficult, but there are + * a few little things that we need to be mindful of. + * + * When a block removed from the free list, the space used by the free + * pointers is available for data. That's what the first calculation + * of size is doing. + */ + + if( size <= (sizeof(((umm_block *)0)->body)) ) + return( 1 ); + + /* + * If it's for more than that, then we need to figure out the number of + * additional whole blocks the size of an umm_block are required. + */ + + size -= ( 1 + (sizeof(((umm_block *)0)->body)) ); + + return( 2 + size/(sizeof(umm_block)) ); +} + +/* ------------------------------------------------------------------------ */ +/* + * Split the block `c` into two blocks: `c` and `c + blocks`. + * + * - `new_freemask` should be `0` if `c + blocks` used, or `UMM_FREELIST_MASK` + * otherwise. + * + * Note that free pointers are NOT modified by this function. + */ +static void umm_split_block( unsigned short int c, + unsigned short int blocks, + unsigned short int new_freemask ) { + + UMM_NBLOCK(c+blocks) = (UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) | new_freemask; + UMM_PBLOCK(c+blocks) = c; + + UMM_PBLOCK(UMM_NBLOCK(c) & UMM_BLOCKNO_MASK) = (c+blocks); + UMM_NBLOCK(c) = (c+blocks); +} + +/* ------------------------------------------------------------------------ */ + +static void umm_disconnect_from_free_list( unsigned short int c ) { + /* Disconnect this block from the FREE list */ + + UMM_NFREE(UMM_PFREE(c)) = UMM_NFREE(c); + UMM_PFREE(UMM_NFREE(c)) = UMM_PFREE(c); + + /* And clear the free block indicator */ + + UMM_NBLOCK(c) &= (~UMM_FREELIST_MASK); +} + +/* ------------------------------------------------------------------------ + * The umm_assimilate_up() function assumes that UMM_NBLOCK(c) does NOT + * have the UMM_FREELIST_MASK bit set! + */ + +static void umm_assimilate_up( unsigned short int c ) { + + if( UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK ) { + /* + * The next block is a free block, so assimilate up and remove it from + * the free list + */ + + DBGLOG_DEBUG( "Assimilate up to next block, which is FREE\n" ); + + /* Disconnect the next block from the FREE list */ + + umm_disconnect_from_free_list( UMM_NBLOCK(c) ); + + /* Assimilate the next block with this one */ + + UMM_PBLOCK(UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK) = c; + UMM_NBLOCK(c) = UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK; + } +} + +/* ------------------------------------------------------------------------ + * The umm_assimilate_down() function assumes that UMM_NBLOCK(c) does NOT + * have the UMM_FREELIST_MASK bit set! + */ + +static unsigned short int umm_assimilate_down( unsigned short int c, unsigned short int freemask ) { + + UMM_NBLOCK(UMM_PBLOCK(c)) = UMM_NBLOCK(c) | freemask; + UMM_PBLOCK(UMM_NBLOCK(c)) = UMM_PBLOCK(c); + + return( UMM_PBLOCK(c) ); +} + +/* ------------------------------------------------------------------------- */ + +void umm_init_x( size_t size ) { + uint32_t UMM_MALLOC_CFG_HEAP_SIZE = (size / sizeof(size_t)) * sizeof(size_t); + if (UMM_MALLOC_CFG_HEAP_SIZE < (sizeof(umm_block) * 128)) fb_alloc_fail(); + if (UMM_MALLOC_CFG_HEAP_SIZE > (sizeof(umm_block) * 32768)) UMM_MALLOC_CFG_HEAP_SIZE = sizeof(umm_block) * 32768; + void *UMM_MALLOC_CFG_HEAP_ADDR = fb_alloc(UMM_MALLOC_CFG_HEAP_SIZE, FB_ALLOC_NO_HINT); + /* init heap pointer and size, and memset it to 0 */ + umm_heap = (umm_block *)UMM_MALLOC_CFG_HEAP_ADDR; + umm_numblocks = (UMM_MALLOC_CFG_HEAP_SIZE / sizeof(umm_block)); + memset(umm_heap, 0x00, UMM_MALLOC_CFG_HEAP_SIZE); + + /* setup initial blank heap structure */ + { + /* index of the 0th `umm_block` */ + const unsigned short int block_0th = 0; + /* index of the 1st `umm_block` */ + const unsigned short int block_1th = 1; + /* index of the latest `umm_block` */ + const unsigned short int block_last = UMM_NUMBLOCKS - 1; + + /* setup the 0th `umm_block`, which just points to the 1st */ + UMM_NBLOCK(block_0th) = block_1th; + UMM_NFREE(block_0th) = block_1th; + UMM_PFREE(block_0th) = block_1th; + + /* + * Now, we need to set the whole heap space as a huge free block. We should + * not touch the 0th `umm_block`, since it's special: the 0th `umm_block` + * is the head of the free block list. It's a part of the heap invariant. + * + * See the detailed explanation at the beginning of the file. + */ + + /* + * 1th `umm_block` has pointers: + * + * - next `umm_block`: the latest one + * - prev `umm_block`: the 0th + * + * Plus, it's a free `umm_block`, so we need to apply `UMM_FREELIST_MASK` + * + * And it's the last free block, so the next free block is 0. + */ + UMM_NBLOCK(block_1th) = block_last | UMM_FREELIST_MASK; + UMM_NFREE(block_1th) = 0; + UMM_PBLOCK(block_1th) = block_0th; + UMM_PFREE(block_1th) = block_0th; + + /* + * latest `umm_block` has pointers: + * + * - next `umm_block`: 0 (meaning, there are no more `umm_blocks`) + * - prev `umm_block`: the 1st + * + * It's not a free block, so we don't touch NFREE / PFREE at all. + */ + UMM_NBLOCK(block_last) = 0; + UMM_PBLOCK(block_last) = block_1th; + } +} + +void umm_init( void ) { + umm_init_x(0); +} + +/* ------------------------------------------------------------------------ */ + +void umm_free( void *ptr ) { + + unsigned short int c; + + /* If we're being asked to free a NULL pointer, well that's just silly! */ + + if( (void *)0 == ptr ) { + DBGLOG_DEBUG( "free a null pointer -> do nothing\n" ); + + return; + } + + /* + * FIXME: At some point it might be a good idea to add a check to make sure + * that the pointer we're being asked to free up is actually within + * the umm_heap! + * + * NOTE: See the new umm_info() function that you can use to see if a ptr is + * on the free list! + */ + + /* Protect the critical section... */ + UMM_CRITICAL_ENTRY(); + + /* Figure out which block we're in. Note the use of truncated division... */ + + c = (((char *)ptr)-(char *)(&(umm_heap[0])))/sizeof(umm_block); + + DBGLOG_DEBUG( "Freeing block %6i\n", c ); + + /* Now let's assimilate this block with the next one if possible. */ + + umm_assimilate_up( c ); + + /* Then assimilate with the previous block if possible */ + + if( UMM_NBLOCK(UMM_PBLOCK(c)) & UMM_FREELIST_MASK ) { + + DBGLOG_DEBUG( "Assimilate down to next block, which is FREE\n" ); + + c = umm_assimilate_down(c, UMM_FREELIST_MASK); + } else { + /* + * The previous block is not a free block, so add this one to the head + * of the free list + */ + + DBGLOG_DEBUG( "Just add to head of free list\n" ); + + UMM_PFREE(UMM_NFREE(0)) = c; + UMM_NFREE(c) = UMM_NFREE(0); + UMM_PFREE(c) = 0; + UMM_NFREE(0) = c; + + UMM_NBLOCK(c) |= UMM_FREELIST_MASK; + } + + /* Release the critical section... */ + UMM_CRITICAL_EXIT(); +} + +/* ------------------------------------------------------------------------ */ + +void *umm_malloc( size_t size ) { + unsigned short int blocks; + unsigned short int blockSize = 0; + + unsigned short int bestSize; + unsigned short int bestBlock; + + unsigned short int cf; + + if (umm_heap == NULL) { + umm_init(); + } + + /* + * the very first thing we do is figure out if we're being asked to allocate + * a size of 0 - and if we are we'll simply return a null pointer. if not + * then reduce the size by 1 byte so that the subsequent calculations on + * the number of blocks to allocate are easier... + */ + + if( 0 == size ) { + DBGLOG_DEBUG( "malloc a block of 0 bytes -> do nothing\n" ); + + return( (void *)NULL ); + } + + /* Protect the critical section... */ + UMM_CRITICAL_ENTRY(); + + blocks = umm_blocks( size ); + + /* + * Now we can scan through the free list until we find a space that's big + * enough to hold the number of blocks we need. + * + * This part may be customized to be a best-fit, worst-fit, or first-fit + * algorithm + */ + + cf = UMM_NFREE(0); + + bestBlock = UMM_NFREE(0); + bestSize = 0x7FFF; + + while( cf ) { + blockSize = (UMM_NBLOCK(cf) & UMM_BLOCKNO_MASK) - cf; + + DBGLOG_TRACE( "Looking at block %6i size %6i\n", cf, blockSize ); + +#if defined UMM_BEST_FIT + if( (blockSize >= blocks) && (blockSize < bestSize) ) { + bestBlock = cf; + bestSize = blockSize; + } +#elif defined UMM_FIRST_FIT + /* This is the first block that fits! */ + if( (blockSize >= blocks) ) + break; +#else +# error "No UMM_*_FIT is defined - check umm_malloc_cfg.h" +#endif + + cf = UMM_NFREE(cf); + } + + if( 0x7FFF != bestSize ) { + cf = bestBlock; + blockSize = bestSize; + } + + if( UMM_NBLOCK(cf) & UMM_BLOCKNO_MASK && blockSize >= blocks ) { + /* + * This is an existing block in the memory heap, we just need to split off + * what we need, unlink it from the free list and mark it as in use, and + * link the rest of the block back into the freelist as if it was a new + * block on the free list... + */ + + if( blockSize == blocks ) { + /* It's an exact fit and we don't neet to split off a block. */ + DBGLOG_DEBUG( "Allocating %6i blocks starting at %6i - exact\n", blocks, cf ); + + /* Disconnect this block from the FREE list */ + + umm_disconnect_from_free_list( cf ); + + } else { + /* It's not an exact fit and we need to split off a block. */ + DBGLOG_DEBUG( "Allocating %6i blocks starting at %6i - existing\n", blocks, cf ); + + /* + * split current free block `cf` into two blocks. The first one will be + * returned to user, so it's not free, and the second one will be free. + */ + umm_split_block( cf, blocks, UMM_FREELIST_MASK /*new block is free*/ ); + + /* + * `umm_split_block()` does not update the free pointers (it affects + * only free flags), but effectively we've just moved beginning of the + * free block from `cf` to `cf + blocks`. So we have to adjust pointers + * to and from adjacent free blocks. + */ + + /* previous free block */ + UMM_NFREE( UMM_PFREE(cf) ) = cf + blocks; + UMM_PFREE( cf + blocks ) = UMM_PFREE(cf); + + /* next free block */ + UMM_PFREE( UMM_NFREE(cf) ) = cf + blocks; + UMM_NFREE( cf + blocks ) = UMM_NFREE(cf); + } + } else { + /* Out of memory */ + + DBGLOG_DEBUG( "Can't allocate %5i blocks\n", blocks ); + + /* Release the critical section... */ + UMM_CRITICAL_EXIT(); + + return( (void *)NULL ); + } + + /* Release the critical section... */ + UMM_CRITICAL_EXIT(); + + return( (void *)&UMM_DATA(cf) ); +} + +/* ------------------------------------------------------------------------ */ + +void *umm_realloc( void *ptr, size_t size ) { + + unsigned short int blocks; + unsigned short int blockSize; + unsigned short int prevBlockSize = 0; + unsigned short int nextBlockSize = 0; + + unsigned short int c; + + size_t curSize; + + if (umm_heap == NULL) { + umm_init(); + } + + /* + * This code looks after the case of a NULL value for ptr. The ANSI C + * standard says that if ptr is NULL and size is non-zero, then we've + * got to work the same a malloc(). If size is also 0, then our version + * of malloc() returns a NULL pointer, which is OK as far as the ANSI C + * standard is concerned. + */ + + if( ((void *)NULL == ptr) ) { + DBGLOG_DEBUG( "realloc the NULL pointer - call malloc()\n" ); + + return( umm_malloc(size) ); + } + + /* + * Now we're sure that we have a non_NULL ptr, but we're not sure what + * we should do with it. If the size is 0, then the ANSI C standard says that + * we should operate the same as free. + */ + + if( 0 == size ) { + DBGLOG_DEBUG( "realloc to 0 size, just free the block\n" ); + + umm_free( ptr ); + + return( (void *)NULL ); + } + + /* + * Otherwise we need to actually do a reallocation. A naiive approach + * would be to malloc() a new block of the correct size, copy the old data + * to the new block, and then free the old block. + * + * While this will work, we end up doing a lot of possibly unnecessary + * copying. So first, let's figure out how many blocks we'll need. + */ + + blocks = umm_blocks( size ); + + /* Figure out which block we're in. Note the use of truncated division... */ + + c = (((char *)ptr)-(char *)(&(umm_heap[0])))/sizeof(umm_block); + + /* Figure out how big this block is ... the free bit is not set :-) */ + + blockSize = (UMM_NBLOCK(c) - c); + + /* Figure out how many bytes are in this block */ + + curSize = (blockSize*sizeof(umm_block))-(sizeof(((umm_block *)0)->header)); + + /* Protect the critical section... */ + UMM_CRITICAL_ENTRY(); + + /* Now figure out if the previous and/or next blocks are free as well as + * their sizes - this will help us to minimize special code later when we + * decide if it's possible to use the adjacent blocks. + * + * We set prevBlockSize and nextBlockSize to non-zero values ONLY if they + * are free! + */ + + if ((UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_FREELIST_MASK)) { + nextBlockSize = (UMM_NBLOCK(UMM_NBLOCK(c)) & UMM_BLOCKNO_MASK) - UMM_NBLOCK(c); + } + + if ((UMM_NBLOCK(UMM_PBLOCK(c)) & UMM_FREELIST_MASK)) { + prevBlockSize = (c - UMM_PBLOCK(c)); + } + + DBGLOG_DEBUG( "realloc blocks %i blockSize %i nextBlockSize %i prevBlockSize %i\n", blocks, blockSize, nextBlockSize, prevBlockSize ); + + /* + * Ok, now that we're here we know how many blocks we want and the current + * blockSize. The prevBlockSize and nextBlockSize are set and we can figure + * out the best strategy for the new allocation as follows: + * + * 1. If the new block is the same size or smaller than the current block do + * nothing. + * 2. If the next block is free and adding it to the current block gives us + * enough memory, assimilate the next block. + * 3. If the prev block is free and adding it to the current block gives us + * enough memory, remove the previous block from the free list, assimilate + * it, copy to the new block. + * 4. If the prev and next blocks are free and adding them to the current + * block gives us enough memory, assimilate the next block, remove the + * previous block from the free list, assimilate it, copy to the new block. + * 5. Otherwise try to allocate an entirely new block of memory. If the + * allocation works free the old block and return the new pointer. If + * the allocation fails, return NULL and leave the old block intact. + * + * All that's left to do is decide if the fit was exact or not. If the fit + * was not exact, then split the memory block so that we use only the requested + * number of blocks and add what's left to the free list. + */ + + if (blockSize >= blocks) { + DBGLOG_DEBUG( "realloc the same or smaller size block - %i, do nothing\n", blocks ); + /* This space intentionally left blank */ + } else if ((blockSize + nextBlockSize) >= blocks) { + DBGLOG_DEBUG( "realloc using next block - %i\n", blocks ); + umm_assimilate_up( c ); + blockSize += nextBlockSize; + } else if ((prevBlockSize + blockSize) >= blocks) { + DBGLOG_DEBUG( "realloc using prev block - %i\n", blocks ); + umm_disconnect_from_free_list( UMM_PBLOCK(c) ); + c = umm_assimilate_down(c, 0); + memmove( (void *)&UMM_DATA(c), ptr, curSize ); + ptr = (void *)&UMM_DATA(c); + blockSize += prevBlockSize; + } else if ((prevBlockSize + blockSize + nextBlockSize) >= blocks) { + DBGLOG_DEBUG( "realloc using prev and next block - %i\n", blocks ); + umm_assimilate_up( c ); + umm_disconnect_from_free_list( UMM_PBLOCK(c) ); + c = umm_assimilate_down(c, 0); + memmove( (void *)&UMM_DATA(c), ptr, curSize ); + ptr = (void *)&UMM_DATA(c); + blockSize += (prevBlockSize + nextBlockSize); + } else { + DBGLOG_DEBUG( "realloc a completely new block %i\n", blocks ); + void *oldptr = ptr; + if( (ptr = umm_malloc( size )) ) { + DBGLOG_DEBUG( "realloc %i to a bigger block %i, copy, and free the old\n", blockSize, blocks ); + memcpy( ptr, oldptr, curSize ); + umm_free( oldptr ); + } else { + DBGLOG_DEBUG( "realloc %i to a bigger block %i failed - return NULL and leave the old block!\n", blockSize, blocks ); + /* This space intentionally left blnk */ + } + blockSize = blocks; + } + + /* Now all we need to do is figure out if the block fit exactly or if we + * need to split and free ... + */ + + if (blockSize > blocks ) { + DBGLOG_DEBUG( "split and free %i blocks from %i\n", blocks, blockSize ); + umm_split_block( c, blocks, 0 ); + umm_free( (void *)&UMM_DATA(c+blocks) ); + } + + /* Release the critical section... */ + UMM_CRITICAL_EXIT(); + + return( ptr ); +} + +/* ------------------------------------------------------------------------ */ + +void *umm_calloc( size_t num, size_t item_size ) { + void *ret; + + ret = umm_malloc((size_t)(item_size * num)); + + if (ret) + memset(ret, 0x00, (size_t)(item_size * num)); + + return ret; +} + +/* ------------------------------------------------------------------------ */ + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/github_source/minicv2/src/unaligned_memcpy.c b/github_source/minicv2/src/unaligned_memcpy.c new file mode 100644 index 0000000..535bfd8 --- /dev/null +++ b/github_source/minicv2/src/unaligned_memcpy.c @@ -0,0 +1,83 @@ +#include +#include +// #include "cmsis_gcc.h" +#include "arm_compat.h" +#include "unaligned_memcpy.h" +#define __REV16(_x) __builtin_bswap16(_x) + + +#ifdef __ARM_ARCH +#undef __ARM_ARCH +#endif + +// ARM Cortex-M4/M7 Processors can access memory using unaligned 32-bit reads/writes. +void *unaligned_memcpy(void *dest, void *src, size_t n) +{ + #if (__ARM_ARCH > 6) + // TODO: Make this faster using only 32-bit aligned reads/writes with data shifting. + uint32_t *dest32 = (uint32_t *) dest; + uint32_t *src32 = (uint32_t *) src; + + for (; n > 4; n -= 4) { + *dest32++ = *src32++; + } + + uint8_t *dest8 = (uint8_t *) dest32; + uint8_t *src8 = (uint8_t *) src32; + + for (; n > 0; n -= 1) { + *dest8++ = *src8++; + } + + return dest; + #else + return memcpy(dest, src, n); + #endif +} + +// ARM Cortex-M4/M7 Processors can access memory using unaligned 32-bit reads/writes. +void *unaligned_memcpy_rev16(void *dest, void *src, size_t n) +{ + uint32_t *dest32 = (uint32_t *) dest; + uint32_t *src32 = (uint32_t *) src; + + #if (__ARM_ARCH > 6) + // TODO: Make this faster using only 32-bit aligned reads/writes with data shifting. + for (; n > 2; n -= 2) { + *dest32++ = __REV16(*src32++); + } + #endif + + uint16_t *dest16 = (uint16_t *) dest32; + uint16_t *src16 = (uint16_t *) src32; + + for (; n > 0; n -= 1) { + *dest16++ = __REV16(*src16++); + } + + return dest; +} + +void *unaligned_2_to_1_memcpy(void *dest, void *src, size_t n) +{ + uint32_t *dest32 = (uint32_t *) dest; + uint32_t *src32 = (uint32_t *) src; + + #if (__ARM_ARCH > 6) + // TODO: Make this faster using only 32-bit aligned reads/writes with data shifting. + for (; n > 4; n -= 4) { + uint32_t tmp1 = *src32++; + uint32_t tmp2 = *src32++; + *dest32++ = (tmp1 & 0xff) | ((tmp1 >> 8) & 0xff00) | ((tmp2 & 0xff) << 16) | ((tmp2 & 0xff0000) << 8); + } + #endif + + uint8_t *dest8 = (uint8_t *) dest32; + uint16_t *src16 = (uint16_t *) src32; + + for (; n > 0; n -= 1) { + *dest8++ = *src16++; + } + + return dest; +} \ No newline at end of file diff --git a/github_source/minicv2/src/x_vfs.c b/github_source/minicv2/src/x_vfs.c new file mode 100644 index 0000000..e69de29 diff --git a/github_source/minicv2/src/xalloc.c b/github_source/minicv2/src/xalloc.c new file mode 100644 index 0000000..296039b --- /dev/null +++ b/github_source/minicv2/src/xalloc.c @@ -0,0 +1,95 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Memory allocation functions. + */ +#include +// #include "py/runtime.h" +// #include "py/gc.h" +// #include "py/mphal.h" +#include "xalloc.h" +#include +#include +#include "imlib_io.h" + +int Debug_s = 0; + +#define LOG_PRINT(fmt, ...) do{\ + if(Debug_s)\ + {\ + printf(fmt" [info:%s:%d] [%s]\n", ##__VA_ARGS__, __FILE__, __LINE__, __FUNCTION__);\ + }\ +}while(0); + + + +static void xalloc_fail(size_t size) +{ + imlib_printf(0, "MemoryError :memory allocation failed, allocating %d bytes", size); + // mp_raise_msg_varg(&mp_type_MemoryError, + // MP_ERROR_TEXT("memory allocation failed, allocating %u bytes"), (uint)size); +} + +// returns null pointer without error if size==0 +void *xalloc(size_t size) +{ + void *mem = malloc(size); + if (size && (mem == NULL)) { + xalloc_fail(size); + } + LOG_PRINT("xalloc:%p", mem); + return mem; +} + +// returns null pointer without error if size==0 +void *xalloc_try_alloc(size_t size) +{ + return malloc(size); +} + +// returns null pointer without error if size==0 +void *xalloc0(size_t size) +{ + void *mem = malloc(size); + if (size && (mem == NULL)) { + xalloc_fail(size); + } + memset(mem, 0, size); + LOG_PRINT("xalloc0:%p", mem); + return mem; +} + +// returns without error if mem==null +void xfree(void *mem) +{ + free(mem); + LOG_PRINT("xfree:%p", mem); +} + +// returns null pointer without error if size==0 +// allocs if mem==null and size!=0 +// frees if mem!=null and size==0 +void *xrealloc(void *mem, size_t size) +{ + mem = realloc(mem, size); + if (size && (mem == NULL)) { + xalloc_fail(size); + } + LOG_PRINT("xrealloc:%p", mem); + return mem; +} +void *xcalloc(size_t nitems, size_t size) +{ + void *mem = xcalloc(nitems, size); + if (size && (mem == NULL)) { + xalloc_fail(size); + } + LOG_PRINT("xalloc_fail:%p", mem); + return mem; +} + diff --git a/github_source/minicv2/src/xyz_tab.c b/github_source/minicv2/src/xyz_tab.c new file mode 100644 index 0000000..763d2b4 --- /dev/null +++ b/github_source/minicv2/src/xyz_tab.c @@ -0,0 +1,34 @@ +const float xyz_table[256] = { + 0.000000f, 0.030353f, 0.060705f, 0.091058f, 0.121411f, 0.151763f, 0.182116f, 0.212469f, + 0.242822f, 0.273174f, 0.303527f, 0.334654f, 0.367651f, 0.402472f, 0.439144f, 0.477695f, + 0.518152f, 0.560539f, 0.604883f, 0.651209f, 0.699541f, 0.749903f, 0.802319f, 0.856813f, + 0.913406f, 0.972122f, 1.032982f, 1.096009f, 1.161225f, 1.228649f, 1.298303f, 1.370208f, + 1.444384f, 1.520851f, 1.599629f, 1.680738f, 1.764195f, 1.850022f, 1.938236f, 2.028856f, + 2.121901f, 2.217388f, 2.315337f, 2.415763f, 2.518686f, 2.624122f, 2.732089f, 2.842604f, + 2.955683f, 3.071344f, 3.189603f, 3.310477f, 3.433981f, 3.560131f, 3.688945f, 3.820437f, + 3.954624f, 4.091520f, 4.231141f, 4.373503f, 4.518620f, 4.666509f, 4.817182f, 4.970657f, + 5.126946f, 5.286065f, 5.448028f, 5.612849f, 5.780543f, 5.951124f, 6.124605f, 6.301002f, + 6.480327f, 6.662594f, 6.847817f, 7.036010f, 7.227185f, 7.421357f, 7.618538f, 7.818742f, + 8.021982f, 8.228271f, 8.437621f, 8.650046f, 8.865559f, 9.084171f, 9.305896f, 9.530747f, + 9.758735f, 9.989873f, 10.224173f, 10.461648f, 10.702310f, 10.946171f, 11.193243f, 11.443537f, + 11.697067f, 11.953843f, 12.213877f, 12.477182f, 12.743768f, 13.013648f, 13.286832f, 13.563333f, + 13.843162f, 14.126329f, 14.412847f, 14.702727f, 14.995979f, 15.292615f, 15.592646f, 15.896084f, + 16.202938f, 16.513219f, 16.826940f, 17.144110f, 17.464740f, 17.788842f, 18.116424f, 18.447499f, + 18.782077f, 19.120168f, 19.461783f, 19.806932f, 20.155625f, 20.507874f, 20.863687f, 21.223076f, + 21.586050f, 21.952620f, 22.322796f, 22.696587f, 23.074005f, 23.455058f, 23.839757f, 24.228112f, + 24.620133f, 25.015828f, 25.415209f, 25.818285f, 26.225066f, 26.635560f, 27.049779f, 27.467731f, + 27.889426f, 28.314874f, 28.744084f, 29.177065f, 29.613827f, 30.054379f, 30.498731f, 30.946892f, + 31.398871f, 31.854678f, 32.314321f, 32.777810f, 33.245154f, 33.716362f, 34.191442f, 34.670406f, + 35.153260f, 35.640014f, 36.130678f, 36.625260f, 37.123768f, 37.626212f, 38.132601f, 38.642943f, + 39.157248f, 39.675523f, 40.197778f, 40.724021f, 41.254261f, 41.788507f, 42.326767f, 42.869050f, + 43.415364f, 43.965717f, 44.520119f, 45.078578f, 45.641102f, 46.207700f, 46.778380f, 47.353150f, + 47.932018f, 48.514994f, 49.102085f, 49.693300f, 50.288646f, 50.888132f, 51.491767f, 52.099557f, + 52.711513f, 53.327640f, 53.947949f, 54.572446f, 55.201140f, 55.834039f, 56.471151f, 57.112483f, + 57.758044f, 58.407842f, 59.061884f, 59.720179f, 60.382734f, 61.049557f, 61.720656f, 62.396039f, + 63.075714f, 63.759687f, 64.447968f, 65.140564f, 65.837482f, 66.538730f, 67.244316f, 67.954247f, + 68.668531f, 69.387176f, 70.110189f, 70.837578f, 71.569350f, 72.305513f, 73.046074f, 73.791041f, + 74.540421f, 75.294222f, 76.052450f, 76.815115f, 77.582222f, 78.353779f, 79.129794f, 79.910274f, + 80.695226f, 81.484657f, 82.278575f, 83.076988f, 83.879901f, 84.687323f, 85.499261f, 86.315721f, + 87.136712f, 87.962240f, 88.792312f, 89.626935f, 90.466117f, 91.309865f, 92.158186f, 93.011086f, + 93.868573f, 94.730654f, 95.597335f, 96.468625f, 97.344529f, 98.225055f, 99.110210f, 100.000000f +}; diff --git a/github_source/minicv2/src/yuv.c b/github_source/minicv2/src/yuv.c new file mode 100644 index 0000000..aeab918 --- /dev/null +++ b/github_source/minicv2/src/yuv.c @@ -0,0 +1,169 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * Deyuv Functions + */ +#include "imlib.h" + +void imlib_deyuv_line(int x_start, int x_end, int y_row, void *dst_row_ptr, pixformat_t pixfmt, image_t *src) +{ + int shift = (src->pixfmt == PIXFORMAT_YUV422) ? 16 : 0; + int src_w = src->w, w_limit = src_w - 1; + + uint16_t *rowptr_yuv = ((uint16_t *) src->data) + (y_row * src_w); + + // If the image is an odd width this will go for the last loop and we drop the last column. + for (int x = x_start; x < x_end; x += 2) { + int32_t row_yuv; // signed + + // keep pixels in bounds + if (x >= w_limit) { + if (src_w >= 2) { + uint32_t temp = *((uint32_t *) (rowptr_yuv + x - 1)); + row_yuv = ((temp & 0xff00) << 16) | (temp & 0xff0000) | (temp >> 16); + } else { + row_yuv = *((uint16_t *) (rowptr_yuv + x)); + row_yuv = ((row_yuv & 0xff) << 16) | 0x80000000; + } + } else { + row_yuv = *((uint32_t *) (rowptr_yuv + x)); + } + + int y0 = row_yuv & 0xff, y1 = (row_yuv >> 16) & 0xff; + + switch (pixfmt) { + case PIXFORMAT_BINARY: { + uint32_t *row_ptr_32 = (uint32_t *) dst_row_ptr; + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr_32, x, (y0 >> 7)); + + if (x != w_limit) { + IMAGE_PUT_BINARY_PIXEL_FAST(row_ptr_32, x + 1, (y1 >> 7)); + } + + break; + } + case PIXFORMAT_GRAYSCALE: { + uint8_t *row_ptr_8 = (uint8_t *) dst_row_ptr; + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr_8, x, y0); + + if (x != w_limit) { + IMAGE_PUT_GRAYSCALE_PIXEL_FAST(row_ptr_8, x + 1, y1); + } + + break; + } + case PIXFORMAT_RGB565: { + uint16_t *row_ptr_16 = (uint16_t *) dst_row_ptr; + + // R = Y + (1.40200 * U) + // G = Y - (0.34414 * V) - (0.71414 * U) + // B = Y + (1.77200 * V) + + // R = Y + ((179 * U) >> 7) + // G = Y - (((44 * V) - (91 * U)) >> 7) + // B = Y + ((227 * V) >> 7) + + row_yuv ^= 0x80008000; + + int u = (row_yuv << shift) >> 24; // signed bit extraction + int v = (row_yuv << (16 - shift)) >> 24; // signed bit extraction + + int ry = (179 * u) >> 7; + int gy = ((44 * v) + (91 * u)) >> 7; + int by = (227 * v) >> 7; + + int r0 = y0 + ry, g0 = y0 - gy, b0 = y0 + by; + r0 = IM_MIN(IM_MAX(r0, COLOR_R8_MIN), COLOR_R8_MAX); + g0 = IM_MIN(IM_MAX(g0, COLOR_G8_MIN), COLOR_G8_MAX); + b0 = IM_MIN(IM_MAX(b0, COLOR_B8_MIN), COLOR_B8_MAX); + int rgb565_0 = COLOR_R8_G8_B8_TO_RGB565(r0, g0, b0); + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr_16, x, rgb565_0); + + if (x != w_limit) { + int r1 = y1 + ry, g1 = y1 - gy, b1 = y1 + by; + r1 = IM_MIN(IM_MAX(r1, COLOR_R8_MIN), COLOR_R8_MAX); + g1 = IM_MIN(IM_MAX(g1, COLOR_G8_MIN), COLOR_G8_MAX); + b1 = IM_MIN(IM_MAX(b1, COLOR_B8_MIN), COLOR_B8_MAX); + int rgb565_1 = COLOR_R8_G8_B8_TO_RGB565(r1, g1, b1); + IMAGE_PUT_RGB565_PIXEL_FAST(row_ptr_16, x + 1, rgb565_1); + } + + break; + } + case PIXFORMAT_RGB888: { + pixel24_t *row_ptr_24 = (pixel24_t *) dst_row_ptr; + + // R = Y + (1.40200 * U) + // G = Y - (0.34414 * V) - (0.71414 * U) + // B = Y + (1.77200 * V) + + // R = Y + ((179 * U) >> 7) + // G = Y - (((44 * V) - (91 * U)) >> 7) + // B = Y + ((227 * V) >> 7) + + row_yuv ^= 0x80008000; + + int u = (row_yuv << shift) >> 24; // signed bit extraction + int v = (row_yuv << (16 - shift)) >> 24; // signed bit extraction + + int ry = (179 * u) >> 7; + int gy = ((44 * v) + (91 * u)) >> 7; + int by = (227 * v) >> 7; + + int r0 = y0 + ry, g0 = y0 - gy, b0 = y0 + by; + r0 = IM_MIN(IM_MAX(r0, COLOR_R8_MIN), COLOR_R8_MAX); + g0 = IM_MIN(IM_MAX(g0, COLOR_G8_MIN), COLOR_G8_MAX); + b0 = IM_MIN(IM_MAX(b0, COLOR_B8_MIN), COLOR_B8_MAX); + int rgb888_0 = COLOR_R8_G8_B8_TO_RGB888(r0, g0, b0); + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr_24, x, rgb888_0); + + if (x != w_limit) { + int r1 = y1 + ry, g1 = y1 - gy, b1 = y1 + by; + r1 = IM_MIN(IM_MAX(r1, COLOR_R8_MIN), COLOR_R8_MAX); + g1 = IM_MIN(IM_MAX(g1, COLOR_G8_MIN), COLOR_G8_MAX); + b1 = IM_MIN(IM_MAX(b1, COLOR_B8_MIN), COLOR_B8_MAX); + int rgb888_1 = COLOR_R8_G8_B8_TO_RGB888(r1, g1, b1); + IMAGE_PUT_RGB888_PIXEL_FAST(row_ptr_24, x + 1, rgb888_1); + } + + break; + } + default: { + break; + } + } + } +} + +void imlib_deyuv_image(image_t *dst, image_t *src) +{ + for (int y = 0, src_w = src->w, src_h = src->h; y < src_h; y++) { + void *row_ptr = NULL; + + switch (dst->pixfmt) { + case PIXFORMAT_BINARY: { + row_ptr = IMAGE_COMPUTE_BINARY_PIXEL_ROW_PTR(dst, y); + break; + } + case PIXFORMAT_GRAYSCALE: { + row_ptr = IMAGE_COMPUTE_GRAYSCALE_PIXEL_ROW_PTR(dst, y); + break; + } + case PIXFORMAT_RGB565: { + row_ptr = IMAGE_COMPUTE_RGB565_PIXEL_ROW_PTR(dst, y); + break; + } + case PIXFORMAT_RGB888: { + row_ptr = IMAGE_COMPUTE_RGB888_PIXEL_ROW_PTR(dst, y); + break; + } + } + + imlib_deyuv_line(0, src_w, y, row_ptr, dst->pixfmt, src); + } +} diff --git a/github_source/minicv2/src/zbar.c b/github_source/minicv2/src/zbar.c new file mode 100644 index 0000000..eaaeb71 --- /dev/null +++ b/github_source/minicv2/src/zbar.c @@ -0,0 +1,8869 @@ +/* + * This file is part of the OpenMV project. + * + * Copyright (c) 2013-2021 Ibrahim Abdelkader + * Copyright (c) 2013-2021 Kwabena W. Agyeman + * + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * This file is part of the ZBar Bar Code Reader library. + */ +#include +#include "imlib.h" +#ifdef IMLIB_ENABLE_BARCODES +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-variable" +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define free(ptr) ({ umm_free(ptr); }) +#define malloc(size) ({ void *_r = umm_malloc(size); if(!_r) fb_alloc_fail(); _r; }) +#define realloc(ptr, size) ({ void *_r = umm_realloc((ptr), (size)); if(!_r) fb_alloc_fail(); _r; }) +#define calloc(num, item_size) ({ void *_r = umm_calloc((num), (item_size)); if(!_r) fb_alloc_fail(); _r; }) +#define assert(expression) +#define zprintf(...) +#define dbprintf(...) while(0) +#define zassert(condition, retval, format, ...) do { if(!(condition)) return(retval); } while(0) + +#define zbar_image_write_png(...) +#define svg_open(...) +#define svg_image(...) +#define svg_group_start(...) +#define svg_path_start(...) +#define svg_path_end(...) +#define svg_group_end(...) +#define svg_close(...) + +#define NO_STATS +#define ENABLE_EAN +#define FIXME_ADDON_SYNC +#define ENABLE_I25 +#define ENABLE_DATABAR +#define ENABLE_CODABAR +#define ENABLE_CODE39 +#define ENABLE_CODE93 +#define ENABLE_CODE128 +#define ENABLE_PDF417 + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "zbar.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +/** @file + * ZBar Barcode Reader C API definition + */ + +/** @mainpage + * + * interface to the barcode reader is available at several levels. + * most applications will want to use the high-level interfaces: + * + * @section high-level High-Level Interfaces + * + * these interfaces wrap all library functionality into an easy-to-use + * package for a specific toolkit: + * - the "GTK+ 2.x widget" may be used with GTK GUI applications. a + * Python wrapper is included for PyGtk + * - the @ref zbar::QZBar "Qt4 widget" may be used with Qt GUI + * applications + * - the Processor interface (in @ref c-processor "C" or @ref + * zbar::Processor "C++") adds a scanning window to an application + * with no GUI. + * + * @section mid-level Intermediate Interfaces + * + * building blocks used to construct high-level interfaces: + * - the ImageScanner (in @ref c-imagescanner "C" or @ref + * zbar::ImageScanner "C++") looks for barcodes in a library defined + * image object + * - the Window abstraction (in @ref c-window "C" or @ref + * zbar::Window "C++") sinks library images, displaying them on the + * platform display + * - the Video abstraction (in @ref c-video "C" or @ref zbar::Video + * "C++") sources library images from a video device + * + * @section low-level Low-Level Interfaces + * + * direct interaction with barcode scanning and decoding: + * - the Scanner (in @ref c-scanner "C" or @ref zbar::Scanner "C++") + * looks for barcodes in a linear intensity sample stream + * - the Decoder (in @ref c-decoder "C" or @ref zbar::Decoder "C++") + * extracts barcodes from a stream of bar and space widths + */ + +/** @name Global library interfaces */ +/*@{*/ + +/** "color" of element: bar or space. */ +typedef enum zbar_color_e { + ZBAR_SPACE = 0, /**< light area or space between bars */ + ZBAR_BAR = 1, /**< dark area or colored bar segment */ +} zbar_color_t; + +/** decoded symbol type. */ +typedef enum zbar_symbol_type_e { + ZBAR_NONE = 0, /**< no symbol decoded */ + ZBAR_PARTIAL = 1, /**< intermediate status */ + ZBAR_EAN2 = 2, /**< GS1 2-digit add-on */ + ZBAR_EAN5 = 5, /**< GS1 5-digit add-on */ + ZBAR_EAN8 = 8, /**< EAN-8 */ + ZBAR_UPCE = 9, /**< UPC-E */ + ZBAR_ISBN10 = 10, /**< ISBN-10 (from EAN-13). @since 0.4 */ + ZBAR_UPCA = 12, /**< UPC-A */ + ZBAR_EAN13 = 13, /**< EAN-13 */ + ZBAR_ISBN13 = 14, /**< ISBN-13 (from EAN-13). @since 0.4 */ + ZBAR_COMPOSITE = 15, /**< EAN/UPC composite */ + ZBAR_I25 = 25, /**< Interleaved 2 of 5. @since 0.4 */ + ZBAR_DATABAR = 34, /**< GS1 DataBar (RSS). @since 0.11 */ + ZBAR_DATABAR_EXP = 35, /**< GS1 DataBar Expanded. @since 0.11 */ + ZBAR_CODABAR = 38, /**< Codabar. @since 0.11 */ + ZBAR_CODE39 = 39, /**< Code 39. @since 0.4 */ + ZBAR_PDF417 = 57, /**< PDF417. @since 0.6 */ + ZBAR_QRCODE = 64, /**< QR Code. @since 0.10 */ + ZBAR_CODE93 = 93, /**< Code 93. @since 0.11 */ + ZBAR_CODE128 = 128, /**< Code 128 */ + + /** mask for base symbol type. + * @deprecated in 0.11, remove this from existing code + */ + ZBAR_SYMBOL = 0x00ff, + /** 2-digit add-on flag. + * @deprecated in 0.11, a ::ZBAR_EAN2 component is used for + * 2-digit GS1 add-ons + */ + ZBAR_ADDON2 = 0x0200, + /** 5-digit add-on flag. + * @deprecated in 0.11, a ::ZBAR_EAN5 component is used for + * 5-digit GS1 add-ons + */ + ZBAR_ADDON5 = 0x0500, + /** add-on flag mask. + * @deprecated in 0.11, GS1 add-ons are represented using composite + * symbols of type ::ZBAR_COMPOSITE; add-on components use ::ZBAR_EAN2 + * or ::ZBAR_EAN5 + */ + ZBAR_ADDON = 0x0700, +} zbar_symbol_type_t; + +/** decoded symbol coarse orientation. + * @since 0.11 + */ +typedef enum zbar_orientation_e { + ZBAR_ORIENT_UNKNOWN = -1, /**< unable to determine orientation */ + ZBAR_ORIENT_UP, /**< upright, read left to right */ + ZBAR_ORIENT_RIGHT, /**< sideways, read top to bottom */ + ZBAR_ORIENT_DOWN, /**< upside-down, read right to left */ + ZBAR_ORIENT_LEFT, /**< sideways, read bottom to top */ +} zbar_orientation_t; + +/** decoder configuration options. + * @since 0.4 + */ +typedef enum zbar_config_e { + ZBAR_CFG_ENABLE = 0, /**< enable symbology/feature */ + ZBAR_CFG_ADD_CHECK, /**< enable check digit when optional */ + ZBAR_CFG_EMIT_CHECK, /**< return check digit when present */ + ZBAR_CFG_ASCII, /**< enable full ASCII character set */ + ZBAR_CFG_NUM, /**< number of boolean decoder configs */ + + ZBAR_CFG_MIN_LEN = 0x20, /**< minimum data length for valid decode */ + ZBAR_CFG_MAX_LEN, /**< maximum data length for valid decode */ + + ZBAR_CFG_UNCERTAINTY = 0x40,/**< required video consistency frames */ + + ZBAR_CFG_POSITION = 0x80, /**< enable scanner to collect position data */ + + ZBAR_CFG_X_DENSITY = 0x100, /**< image scanner vertical scan density */ + ZBAR_CFG_Y_DENSITY, /**< image scanner horizontal scan density */ +} zbar_config_t; + +/** decoder symbology modifier flags. + * @since 0.11 + */ +typedef enum zbar_modifier_e { + /** barcode tagged as GS1 (EAN.UCC) reserved + * (eg, FNC1 before first data character). + * data may be parsed as a sequence of GS1 AIs + */ + ZBAR_MOD_GS1 = 0, + + /** barcode tagged as AIM reserved + * (eg, FNC1 after first character or digit pair) + */ + ZBAR_MOD_AIM, + + /** number of modifiers */ + ZBAR_MOD_NUM, +} zbar_modifier_t; + +/** retrieve string name for symbol encoding. + * @param sym symbol type encoding + * @returns the static string name for the specified symbol type, + * or "UNKNOWN" if the encoding is not recognized + */ +extern const char *zbar_get_symbol_name(zbar_symbol_type_t sym); + +/** retrieve string name for addon encoding. + * @param sym symbol type encoding + * @returns static string name for any addon, or the empty string + * if no addons were decoded + * @deprecated in 0.11 + */ +extern const char *zbar_get_addon_name(zbar_symbol_type_t sym); + +/** retrieve string name for configuration setting. + * @param config setting to name + * @returns static string name for config, + * or the empty string if value is not a known config + */ +extern const char *zbar_get_config_name(zbar_config_t config); + +/** retrieve string name for modifier. + * @param modifier flag to name + * @returns static string name for modifier, + * or the empty string if the value is not a known flag + */ +extern const char *zbar_get_modifier_name(zbar_modifier_t modifier); + +/** retrieve string name for orientation. + * @param orientation orientation encoding + * @returns the static string name for the specified orientation, + * or "UNKNOWN" if the orientation is not recognized + * @since 0.11 + */ +extern const char *zbar_get_orientation_name(zbar_orientation_t orientation); + +/** parse a configuration string of the form "[symbology.]config[=value]". + * the config must match one of the recognized names. + * the symbology, if present, must match one of the recognized names. + * if symbology is unspecified, it will be set to 0. + * if value is unspecified it will be set to 1. + * @returns 0 if the config is parsed successfully, 1 otherwise + * @since 0.4 + */ +extern int zbar_parse_config(const char *config_string, + zbar_symbol_type_t *symbology, + zbar_config_t *config, + int *value); + +/** consistently compute fourcc values across architectures + * (adapted from v4l2 specification) + * @since 0.11 + */ +#define zbar_fourcc(a, b, c, d) \ + ((unsigned long)(a) | \ + ((unsigned long)(b) << 8) | \ + ((unsigned long)(c) << 16) | \ + ((unsigned long)(d) << 24)) + +/** parse a fourcc string into its encoded integer value. + * @since 0.11 + */ +static inline unsigned long zbar_fourcc_parse (const char *format) +{ + unsigned long fourcc = 0; + if(format) { + int i; + for(i = 0; i < 4 && format[i]; i++) + fourcc |= ((unsigned long)format[i]) << (i * 8); + } + return(fourcc); +} + +/*@}*/ + +struct zbar_symbol_s; +typedef struct zbar_symbol_s zbar_symbol_t; + +struct zbar_symbol_set_s; +typedef struct zbar_symbol_set_s zbar_symbol_set_t; + + +/*------------------------------------------------------------*/ +/** @name Symbol interface + * decoded barcode symbol result object. stores type, data, and image + * location of decoded symbol. all memory is owned by the library + */ +/*@{*/ + +/** @typedef zbar_symbol_t + * opaque decoded symbol object. + */ + +/** symbol reference count manipulation. + * increment the reference count when you store a new reference to the + * symbol. decrement when the reference is no longer used. do not + * refer to the symbol once the count is decremented and the + * containing image has been recycled or destroyed. + * @note the containing image holds a reference to the symbol, so you + * only need to use this if you keep a symbol after the image has been + * destroyed or reused. + * @since 0.9 + */ +extern void zbar_symbol_ref(const zbar_symbol_t *symbol, + int refs); + +/** retrieve type of decoded symbol. + * @returns the symbol type + */ +extern zbar_symbol_type_t zbar_symbol_get_type(const zbar_symbol_t *symbol); + +/** retrieve symbology boolean config settings. + * @returns a bitmask indicating which configs were set for the detected + * symbology during decoding. + * @since 0.11 + */ +extern unsigned int zbar_symbol_get_configs(const zbar_symbol_t *symbol); + +/** retrieve symbology modifier flag settings. + * @returns a bitmask indicating which characteristics were detected + * during decoding. + * @since 0.11 + */ +extern unsigned int zbar_symbol_get_modifiers(const zbar_symbol_t *symbol); + +/** retrieve data decoded from symbol. + * @returns the data string + */ +extern const char *zbar_symbol_get_data(const zbar_symbol_t *symbol); + +/** retrieve length of binary data. + * @returns the length of the decoded data + */ +extern unsigned int zbar_symbol_get_data_length(const zbar_symbol_t *symbol); + +/** retrieve a symbol confidence metric. + * @returns an unscaled, relative quantity: larger values are better + * than smaller values, where "large" and "small" are application + * dependent. + * @note expect the exact definition of this quantity to change as the + * metric is refined. currently, only the ordered relationship + * between two values is defined and will remain stable in the future + * @since 0.9 + */ +extern int zbar_symbol_get_quality(const zbar_symbol_t *symbol); + +/** retrieve current cache count. when the cache is enabled for the + * image_scanner this provides inter-frame reliability and redundancy + * information for video streams. + * @returns < 0 if symbol is still uncertain. + * @returns 0 if symbol is newly verified. + * @returns > 0 for duplicate symbols + */ +extern int zbar_symbol_get_count(const zbar_symbol_t *symbol); + +/** retrieve the number of points in the location polygon. the + * location polygon defines the image area that the symbol was + * extracted from. + * @returns the number of points in the location polygon + * @note this is currently not a polygon, but the scan locations + * where the symbol was decoded + */ +extern unsigned zbar_symbol_get_loc_size(const zbar_symbol_t *symbol); + +/** retrieve location polygon x-coordinates. + * points are specified by 0-based index. + * @returns the x-coordinate for a point in the location polygon. + * @returns -1 if index is out of range + */ +extern int zbar_symbol_get_loc_x(const zbar_symbol_t *symbol, + unsigned index); + +/** retrieve location polygon y-coordinates. + * points are specified by 0-based index. + * @returns the y-coordinate for a point in the location polygon. + * @returns -1 if index is out of range + */ +extern int zbar_symbol_get_loc_y(const zbar_symbol_t *symbol, + unsigned index); + +/** retrieve general orientation of decoded symbol. + * @returns a coarse, axis-aligned indication of symbol orientation or + * ::ZBAR_ORIENT_UNKNOWN if unknown + * @since 0.11 + */ +extern zbar_orientation_t +zbar_symbol_get_orientation(const zbar_symbol_t *symbol); + +/** iterate the set to which this symbol belongs (there can be only one). + * @returns the next symbol in the set, or + * @returns NULL when no more results are available + */ +extern const zbar_symbol_t *zbar_symbol_next(const zbar_symbol_t *symbol); + +/** retrieve components of a composite result. + * @returns the symbol set containing the components + * @returns NULL if the symbol is already a physical symbol + * @since 0.10 + */ +extern const zbar_symbol_set_t* +zbar_symbol_get_components(const zbar_symbol_t *symbol); + +/** iterate components of a composite result. + * @returns the first physical component symbol of a composite result + * @returns NULL if the symbol is already a physical symbol + * @since 0.10 + */ +extern const zbar_symbol_t* +zbar_symbol_first_component(const zbar_symbol_t *symbol); + +/** print XML symbol element representation to user result buffer. + * @see http://zbar.sourceforge.net/2008/barcode.xsd for the schema. + * @param symbol is the symbol to print + * @param buffer is the inout result pointer, it will be reallocated + * with a larger size if necessary. + * @param buflen is inout length of the result buffer. + * @returns the buffer pointer + * @since 0.6 + */ +extern char *zbar_symbol_xml(const zbar_symbol_t *symbol, + char **buffer, + unsigned *buflen); + +/*@}*/ + +/*------------------------------------------------------------*/ +/** @name Symbol Set interface + * container for decoded result symbols associated with an image + * or a composite symbol. + * @since 0.10 + */ +/*@{*/ + +/** @typedef zbar_symbol_set_t + * opaque symbol iterator object. + * @since 0.10 + */ + +/** reference count manipulation. + * increment the reference count when you store a new reference. + * decrement when the reference is no longer used. do not refer to + * the object any longer once references have been released. + * @since 0.10 + */ +extern void zbar_symbol_set_ref(const zbar_symbol_set_t *symbols, + int refs); + +/** retrieve set size. + * @returns the number of symbols in the set. + * @since 0.10 + */ +extern int zbar_symbol_set_get_size(const zbar_symbol_set_t *symbols); + +/** set iterator. + * @returns the first decoded symbol result in a set + * @returns NULL if the set is empty + * @since 0.10 + */ +extern const zbar_symbol_t* +zbar_symbol_set_first_symbol(const zbar_symbol_set_t *symbols); + +/** raw result iterator. + * @returns the first decoded symbol result in a set, *before* filtering + * @returns NULL if the set is empty + * @since 0.11 + */ +extern const zbar_symbol_t* +zbar_symbol_set_first_unfiltered(const zbar_symbol_set_t *symbols); + +/*@}*/ + +/*------------------------------------------------------------*/ +/** @name Image interface + * stores image data samples along with associated format and size + * metadata + */ +/*@{*/ + +struct zbar_image_s; +/** opaque image object. */ +typedef struct zbar_image_s zbar_image_t; + +/** cleanup handler callback function. + * called to free sample data when an image is destroyed. + */ +typedef void (zbar_image_cleanup_handler_t)(zbar_image_t *image); + +/** data handler callback function. + * called when decoded symbol results are available for an image + */ +typedef void (zbar_image_data_handler_t)(zbar_image_t *image, + const void *userdata); + +/*@}*/ + +/*------------------------------------------------------------*/ +/** @name Image Scanner interface + * @anchor c-imagescanner + * mid-level image scanner interface. + * reads barcodes from 2-D images + */ +/*@{*/ + +struct zbar_image_scanner_s; +/** opaque image scanner object. */ +typedef struct zbar_image_scanner_s zbar_image_scanner_t; + +/** constructor. */ +extern zbar_image_scanner_t *zbar_image_scanner_create(void); + +/** destructor. */ +extern void zbar_image_scanner_destroy(zbar_image_scanner_t *scanner); + +/** setup result handler callback. + * the specified function will be called by the scanner whenever + * new results are available from a decoded image. + * pass a NULL value to disable callbacks. + * @returns the previously registered handler + */ +extern zbar_image_data_handler_t* +zbar_image_scanner_set_data_handler(zbar_image_scanner_t *scanner, + zbar_image_data_handler_t *handler, + const void *userdata); + + +/** set config for indicated symbology (0 for all) to specified value. + * @returns 0 for success, non-0 for failure (config does not apply to + * specified symbology, or value out of range) + * @see zbar_decoder_set_config() + * @since 0.4 + */ +extern int zbar_image_scanner_set_config(zbar_image_scanner_t *scanner, + zbar_symbol_type_t symbology, + zbar_config_t config, + int value); + +/** parse configuration string using zbar_parse_config() + * and apply to image scanner using zbar_image_scanner_set_config(). + * @returns 0 for success, non-0 for failure + * @see zbar_parse_config() + * @see zbar_image_scanner_set_config() + * @since 0.4 + */ +static inline int +zbar_image_scanner_parse_config (zbar_image_scanner_t *scanner, + const char *config_string) +{ + zbar_symbol_type_t sym; + zbar_config_t cfg; + int val; + return(zbar_parse_config(config_string, &sym, &cfg, &val) || + zbar_image_scanner_set_config(scanner, sym, cfg, val)); +} + +/** enable or disable the inter-image result cache (default disabled). + * mostly useful for scanning video frames, the cache filters + * duplicate results from consecutive images, while adding some + * consistency checking and hysteresis to the results. + * this interface also clears the cache + */ +extern void zbar_image_scanner_enable_cache(zbar_image_scanner_t *scanner, + int enable); + +/** remove any previously decoded results from the image scanner and the + * specified image. somewhat more efficient version of + * zbar_image_set_symbols(image, NULL) which may retain memory for + * subsequent decodes + * @since 0.10 + */ +extern void zbar_image_scanner_recycle_image(zbar_image_scanner_t *scanner, + zbar_image_t *image); + +/** retrieve decode results for last scanned image. + * @returns the symbol set result container or NULL if no results are + * available + * @note the symbol set does not have its reference count adjusted; + * ensure that the count is incremented if the results may be kept + * after the next image is scanned + * @since 0.10 + */ +extern const zbar_symbol_set_t* +zbar_image_scanner_get_results(const zbar_image_scanner_t *scanner); + +/** scan for symbols in provided image. The image format must be + * "Y800" or "GRAY". + * @returns >0 if symbols were successfully decoded from the image, + * 0 if no symbols were found or -1 if an error occurs + * @see zbar_image_convert() + * @since 0.9 - changed to only accept grayscale images + */ +extern int zbar_scan_image(zbar_image_scanner_t *scanner, + zbar_image_t *image); + +/*@}*/ + +/*------------------------------------------------------------*/ +/** @name Decoder interface + * @anchor c-decoder + * low-level bar width stream decoder interface. + * identifies symbols and extracts encoded data + */ +/*@{*/ + +struct zbar_decoder_s; +/** opaque decoder object. */ +typedef struct zbar_decoder_s zbar_decoder_t; + +/** decoder data handler callback function. + * called by decoder when new data has just been decoded + */ +typedef void (zbar_decoder_handler_t)(zbar_decoder_t *decoder); + +/** constructor. */ +extern zbar_decoder_t *zbar_decoder_create(void); + +/** destructor. */ +extern void zbar_decoder_destroy(zbar_decoder_t *decoder); + +/** set config for indicated symbology (0 for all) to specified value. + * @returns 0 for success, non-0 for failure (config does not apply to + * specified symbology, or value out of range) + * @since 0.4 + */ +extern int zbar_decoder_set_config(zbar_decoder_t *decoder, + zbar_symbol_type_t symbology, + zbar_config_t config, + int value); + +/** parse configuration string using zbar_parse_config() + * and apply to decoder using zbar_decoder_set_config(). + * @returns 0 for success, non-0 for failure + * @see zbar_parse_config() + * @see zbar_decoder_set_config() + * @since 0.4 + */ +static inline int zbar_decoder_parse_config (zbar_decoder_t *decoder, + const char *config_string) +{ + zbar_symbol_type_t sym; + zbar_config_t cfg; + int val; + return(zbar_parse_config(config_string, &sym, &cfg, &val) || + zbar_decoder_set_config(decoder, sym, cfg, val)); +} + +/** retrieve symbology boolean config settings. + * @returns a bitmask indicating which configs are currently set for the + * specified symbology. + * @since 0.11 + */ +extern unsigned int zbar_decoder_get_configs(const zbar_decoder_t *decoder, + zbar_symbol_type_t symbology); + +/** clear all decoder state. + * any partial symbols are flushed + */ +extern void zbar_decoder_reset(zbar_decoder_t *decoder); + +/** mark start of a new scan pass. + * clears any intra-symbol state and resets color to ::ZBAR_SPACE. + * any partially decoded symbol state is retained + */ +extern void zbar_decoder_new_scan(zbar_decoder_t *decoder); + +/** process next bar/space width from input stream. + * the width is in arbitrary relative units. first value of a scan + * is ::ZBAR_SPACE width, alternating from there. + * @returns appropriate symbol type if width completes + * decode of a symbol (data is available for retrieval) + * @returns ::ZBAR_PARTIAL as a hint if part of a symbol was decoded + * @returns ::ZBAR_NONE (0) if no new symbol data is available + */ +extern zbar_symbol_type_t zbar_decode_width(zbar_decoder_t *decoder, + unsigned width); + +/** retrieve color of @em next element passed to + * zbar_decode_width(). */ +extern zbar_color_t zbar_decoder_get_color(const zbar_decoder_t *decoder); + +/** retrieve last decoded data. + * @returns the data string or NULL if no new data available. + * the returned data buffer is owned by library, contents are only + * valid between non-0 return from zbar_decode_width and next library + * call + */ +extern const char *zbar_decoder_get_data(const zbar_decoder_t *decoder); + +/** retrieve length of binary data. + * @returns the length of the decoded data or 0 if no new data + * available. + */ +extern unsigned int +zbar_decoder_get_data_length(const zbar_decoder_t *decoder); + +/** retrieve last decoded symbol type. + * @returns the type or ::ZBAR_NONE if no new data available + */ +extern zbar_symbol_type_t +zbar_decoder_get_type(const zbar_decoder_t *decoder); + +/** retrieve modifier flags for the last decoded symbol. + * @returns a bitmask indicating which characteristics were detected + * during decoding. + * @since 0.11 + */ +extern unsigned int zbar_decoder_get_modifiers(const zbar_decoder_t *decoder); + +/** retrieve last decode direction. + * @returns 1 for forward and -1 for reverse + * @returns 0 if the decode direction is unknown or does not apply + * @since 0.11 + */ +extern int zbar_decoder_get_direction(const zbar_decoder_t *decoder); + +/** setup data handler callback. + * the registered function will be called by the decoder + * just before zbar_decode_width() returns a non-zero value. + * pass a NULL value to disable callbacks. + * @returns the previously registered handler + */ +extern zbar_decoder_handler_t* +zbar_decoder_set_handler(zbar_decoder_t *decoder, + zbar_decoder_handler_t *handler); + +/** associate user specified data value with the decoder. */ +extern void zbar_decoder_set_userdata(zbar_decoder_t *decoder, + void *userdata); + +/** return user specified data value associated with the decoder. */ +extern void *zbar_decoder_get_userdata(const zbar_decoder_t *decoder); + +/*@}*/ + +/*------------------------------------------------------------*/ +/** @name Scanner interface + * @anchor c-scanner + * low-level linear intensity sample stream scanner interface. + * identifies "bar" edges and measures width between them. + * optionally passes to bar width decoder + */ +/*@{*/ + +struct zbar_scanner_s; +/** opaque scanner object. */ +typedef struct zbar_scanner_s zbar_scanner_t; + +/** constructor. + * if decoder is non-NULL it will be attached to scanner + * and called automatically at each new edge + * current color is initialized to ::ZBAR_SPACE + * (so an initial BAR->SPACE transition may be discarded) + */ +extern zbar_scanner_t *zbar_scanner_create(zbar_decoder_t *decoder); + +/** destructor. */ +extern void zbar_scanner_destroy(zbar_scanner_t *scanner); + +/** clear all scanner state. + * also resets an associated decoder + */ +extern zbar_symbol_type_t zbar_scanner_reset(zbar_scanner_t *scanner); + +/** mark start of a new scan pass. resets color to ::ZBAR_SPACE. + * also updates an associated decoder. + * @returns any decode results flushed from the pipeline + * @note when not using callback handlers, the return value should + * be checked the same as zbar_scan_y() + * @note call zbar_scanner_flush() at least twice before calling this + * method to ensure no decode results are lost + */ +extern zbar_symbol_type_t zbar_scanner_new_scan(zbar_scanner_t *scanner); + +/** flush scanner processing pipeline. + * forces current scanner position to be a scan boundary. + * call multiple times (max 3) to completely flush decoder. + * @returns any decode/scan results flushed from the pipeline + * @note when not using callback handlers, the return value should + * be checked the same as zbar_scan_y() + * @since 0.9 + */ +extern zbar_symbol_type_t zbar_scanner_flush(zbar_scanner_t *scanner); + +/** process next sample intensity value. + * intensity (y) is in arbitrary relative units. + * @returns result of zbar_decode_width() if a decoder is attached, + * otherwise @returns (::ZBAR_PARTIAL) when new edge is detected + * or 0 (::ZBAR_NONE) if no new edge is detected + */ +extern zbar_symbol_type_t zbar_scan_y(zbar_scanner_t *scanner, + int y); + +/** process next sample from RGB (or BGR) triple. */ +static inline zbar_symbol_type_t zbar_scan_rgb24 (zbar_scanner_t *scanner, + unsigned char *rgb) +{ + return(zbar_scan_y(scanner, rgb[0] + rgb[1] + rgb[2])); +} + +/** retrieve last scanned width. */ +extern unsigned zbar_scanner_get_width(const zbar_scanner_t *scanner); + +/** retrieve sample position of last edge. + * @since 0.10 + */ +extern unsigned zbar_scanner_get_edge(const zbar_scanner_t *scn, + unsigned offset, + int prec); + +/** retrieve last scanned color. */ +extern zbar_color_t zbar_scanner_get_color(const zbar_scanner_t *scanner); + +/*@}*/ + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "config.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2008-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +int zbar_parse_config (const char *cfgstr, + zbar_symbol_type_t *sym, + zbar_config_t *cfg, + int *val) +{ + const char *dot, *eq; + int len; + char negate; + if(!cfgstr) + return(1); + + dot = strchr(cfgstr, '.'); + if(dot) { + int len = dot - cfgstr; + if(!len || (len == 1 && !strncmp(cfgstr, "*", len))) + *sym = 0; + else if(len < 2) + return(1); + else if(!strncmp(cfgstr, "qrcode", len)) + *sym = ZBAR_QRCODE; + else if(!strncmp(cfgstr, "db", len)) + *sym = ZBAR_DATABAR; + else if(len < 3) + return(1); + else if(!strncmp(cfgstr, "upca", len)) + *sym = ZBAR_UPCA; + else if(!strncmp(cfgstr, "upce", len)) + *sym = ZBAR_UPCE; + else if(!strncmp(cfgstr, "ean13", len)) + *sym = ZBAR_EAN13; + else if(!strncmp(cfgstr, "ean8", len)) + *sym = ZBAR_EAN8; + else if(!strncmp(cfgstr, "ean5", len)) + *sym = ZBAR_EAN5; + else if(!strncmp(cfgstr, "ean2", len)) + *sym = ZBAR_EAN2; + else if(!strncmp(cfgstr, "composite", len)) + *sym = ZBAR_COMPOSITE; + else if(!strncmp(cfgstr, "i25", len)) + *sym = ZBAR_I25; + else if(len < 4) + return(1); + else if(!strncmp(cfgstr, "scanner", len)) + *sym = ZBAR_PARTIAL; /* FIXME lame */ + else if(!strncmp(cfgstr, "isbn13", len)) + *sym = ZBAR_ISBN13; + else if(!strncmp(cfgstr, "isbn10", len)) + *sym = ZBAR_ISBN10; + else if(!strncmp(cfgstr, "db-exp", len)) + *sym = ZBAR_DATABAR_EXP; + else if(!strncmp(cfgstr, "codabar", len)) + *sym = ZBAR_CODABAR; + else if(len < 6) + return(1); + else if(!strncmp(cfgstr, "code93", len)) + *sym = ZBAR_CODE93; + else if(!strncmp(cfgstr, "code39", len)) + *sym = ZBAR_CODE39; + else if(!strncmp(cfgstr, "pdf417", len)) + *sym = ZBAR_PDF417; + else if(len < 7) + return(1); + else if(!strncmp(cfgstr, "code128", len)) + *sym = ZBAR_CODE128; + else if(!strncmp(cfgstr, "databar", len)) + *sym = ZBAR_DATABAR; + else if(!strncmp(cfgstr, "databar-exp", len)) + *sym = ZBAR_DATABAR_EXP; + else + return(1); + cfgstr = dot + 1; + } + else + *sym = 0; + + len = strlen(cfgstr); + eq = strchr(cfgstr, '='); + if(eq) + len = eq - cfgstr; + else + *val = 1; /* handle this here so we can override later */ + negate = 0; + + if(len > 3 && !strncmp(cfgstr, "no-", 3)) { + negate = 1; + cfgstr += 3; + len -= 3; + } + + if(len < 1) + return(1); + else if(!strncmp(cfgstr, "y-density", len)) + *cfg = ZBAR_CFG_Y_DENSITY; + else if(!strncmp(cfgstr, "x-density", len)) + *cfg = ZBAR_CFG_X_DENSITY; + else if(len < 2) + return(1); + else if(!strncmp(cfgstr, "enable", len)) + *cfg = ZBAR_CFG_ENABLE; + else if(len < 3) + return(1); + else if(!strncmp(cfgstr, "disable", len)) { + *cfg = ZBAR_CFG_ENABLE; + negate = !negate; /* no-disable ?!? */ + } + else if(!strncmp(cfgstr, "min-length", len)) + *cfg = ZBAR_CFG_MIN_LEN; + else if(!strncmp(cfgstr, "max-length", len)) + *cfg = ZBAR_CFG_MAX_LEN; + else if(!strncmp(cfgstr, "ascii", len)) + *cfg = ZBAR_CFG_ASCII; + else if(!strncmp(cfgstr, "add-check", len)) + *cfg = ZBAR_CFG_ADD_CHECK; + else if(!strncmp(cfgstr, "emit-check", len)) + *cfg = ZBAR_CFG_EMIT_CHECK; + else if(!strncmp(cfgstr, "uncertainty", len)) + *cfg = ZBAR_CFG_UNCERTAINTY; + else if(!strncmp(cfgstr, "position", len)) + *cfg = ZBAR_CFG_POSITION; + else + return(1); + + if(eq) { +#ifdef HAVE_ERRNO_H + errno = 0; +#endif + *val = strtol(eq + 1, NULL, 0); +#ifdef HAVE_ERRNO_H + if(errno) + return(1); +#endif + } + if(negate) + *val = !*val; + + return(0); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "image.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +#define fourcc zbar_fourcc + +struct zbar_image_s { + uint32_t format; /* fourcc image format code */ + unsigned width, height; /* image size */ + const void *data; /* image sample data */ + unsigned long datalen; /* allocated/mapped size of data */ + unsigned crop_x, crop_y; /* crop rectangle */ + unsigned crop_w, crop_h; + void *userdata; /* user specified data associated w/image */ + + unsigned seq; /* page/frame sequence number */ + zbar_symbol_set_t *syms; /* decoded result set */ +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "refcnt.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +typedef int refcnt_t; + +static inline int _zbar_refcnt (refcnt_t *cnt, + int delta) +{ + int rc = (*cnt += delta); + assert(rc >= 0); + return(rc); +} + +void _zbar_refcnt_init(void); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "refcnt.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2009 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +void _zbar_refcnt_init () +{ +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "symbol.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +#define NUM_SYMS 20 + +typedef struct zbar_point_s { + int x, y; +} zbar_point_t; + +struct zbar_symbol_set_s { + refcnt_t refcnt; + int nsyms; /* number of filtered symbols */ + zbar_symbol_t *head; /* first of decoded symbol results */ + zbar_symbol_t *tail; /* last of unfiltered symbol results */ +}; + +struct zbar_symbol_s { + zbar_symbol_type_t type; /* symbol type */ + unsigned int configs; /* symbology boolean config bitmask */ + unsigned int modifiers; /* symbology modifier bitmask */ + unsigned int data_alloc; /* allocation size of data */ + unsigned int datalen; /* length of binary symbol data */ + char *data; /* symbol data */ + + unsigned pts_alloc; /* allocation size of pts */ + unsigned npts; /* number of points in location polygon */ + zbar_point_t *pts; /* list of points in location polygon */ + zbar_orientation_t orient; /* coarse orientation */ + + refcnt_t refcnt; /* reference count */ + zbar_symbol_t *next; /* linked list of results (or siblings) */ + zbar_symbol_set_t *syms; /* components of composite result */ + unsigned long time; /* relative symbol capture time */ + int cache_count; /* cache state */ + int quality; /* relative symbol reliability metric */ +}; + +extern int _zbar_get_symbol_hash(zbar_symbol_type_t); + +extern void _zbar_symbol_free(zbar_symbol_t*); + +extern zbar_symbol_set_t *_zbar_symbol_set_create(void); +extern void _zbar_symbol_set_free(zbar_symbol_set_t*); + +static inline void sym_add_point (zbar_symbol_t *sym, + int x, + int y) +{ + int i = sym->npts; + if(++sym->npts >= sym->pts_alloc) + sym->pts = realloc(sym->pts, ++sym->pts_alloc * sizeof(zbar_point_t)); + sym->pts[i].x = x; + sym->pts[i].y = y; +} + +static inline void _zbar_symbol_refcnt (zbar_symbol_t *sym, + int delta) +{ + if(!_zbar_refcnt(&sym->refcnt, delta) && delta <= 0) + _zbar_symbol_free(sym); +} + +static inline void _zbar_symbol_set_add (zbar_symbol_set_t *syms, + zbar_symbol_t *sym) +{ + sym->next = syms->head; + syms->head = sym; + syms->nsyms++; + + _zbar_symbol_refcnt(sym, 1); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "symbol.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +const char *zbar_get_symbol_name (zbar_symbol_type_t sym) +{ + switch(sym & ZBAR_SYMBOL) { + case ZBAR_EAN2: return("EAN-2"); + case ZBAR_EAN5: return("EAN-5"); + case ZBAR_EAN8: return("EAN-8"); + case ZBAR_UPCE: return("UPC-E"); + case ZBAR_ISBN10: return("ISBN-10"); + case ZBAR_UPCA: return("UPC-A"); + case ZBAR_EAN13: return("EAN-13"); + case ZBAR_ISBN13: return("ISBN-13"); + case ZBAR_COMPOSITE: return("COMPOSITE"); + case ZBAR_I25: return("I2/5"); + case ZBAR_DATABAR: return("DataBar"); + case ZBAR_DATABAR_EXP: return("DataBar-Exp"); + case ZBAR_CODABAR: return("Codabar"); + case ZBAR_CODE39: return("CODE-39"); + case ZBAR_CODE93: return("CODE-93"); + case ZBAR_CODE128: return("CODE-128"); + case ZBAR_PDF417: return("PDF417"); + case ZBAR_QRCODE: return("QR-Code"); + default: return("UNKNOWN"); + } +} + +const char *zbar_get_addon_name (zbar_symbol_type_t sym) +{ + return(""); +} + +const char *zbar_get_config_name (zbar_config_t cfg) +{ + switch(cfg) { + case ZBAR_CFG_ENABLE: return("ENABLE"); + case ZBAR_CFG_ADD_CHECK: return("ADD_CHECK"); + case ZBAR_CFG_EMIT_CHECK: return("EMIT_CHECK"); + case ZBAR_CFG_ASCII: return("ASCII"); + case ZBAR_CFG_MIN_LEN: return("MIN_LEN"); + case ZBAR_CFG_MAX_LEN: return("MAX_LEN"); + case ZBAR_CFG_UNCERTAINTY: return("UNCERTAINTY"); + case ZBAR_CFG_POSITION: return("POSITION"); + case ZBAR_CFG_X_DENSITY: return("X_DENSITY"); + case ZBAR_CFG_Y_DENSITY: return("Y_DENSITY"); + default: return(""); + } +} + +const char *zbar_get_modifier_name (zbar_modifier_t mod) +{ + switch(mod) { + case ZBAR_MOD_GS1: return("GS1"); + case ZBAR_MOD_AIM: return("AIM"); + default: return(""); + } +} + +const char *zbar_get_orientation_name (zbar_orientation_t orient) +{ + switch(orient) { + case ZBAR_ORIENT_UP: return("UP"); + case ZBAR_ORIENT_RIGHT: return("RIGHT"); + case ZBAR_ORIENT_DOWN: return("DOWN"); + case ZBAR_ORIENT_LEFT: return("LEFT"); + default: return("UNKNOWN"); + } +} + +int _zbar_get_symbol_hash (zbar_symbol_type_t sym) +{ + static const signed char hash[0x20] = { + 0x00, 0x01, 0x10, 0x11, -1, 0x11, 0x16, 0x0c, + 0x05, 0x06, 0x08, -1, 0x04, 0x03, 0x07, 0x12, + -1, -1, -1, -1, -1, -1, -1, 0x02, + -1, 0x00, 0x12, 0x0c, 0x0b, 0x1d, 0x0a, 0x00, + }; + int g0 = hash[sym & 0x1f]; + int g1 = hash[~(sym >> 4) & 0x1f]; + assert(g0 >= 0 && g1 >= 0); + if(g0 < 0 || g1 < 0) + return(0); + return((g0 + g1) & 0x1f); +} + +void _zbar_symbol_free (zbar_symbol_t *sym) +{ + if(sym->syms) { + zbar_symbol_set_ref(sym->syms, -1); + sym->syms = NULL; + } + if(sym->pts) + free(sym->pts); + if(sym->data_alloc && sym->data) + free(sym->data); + free(sym); +} + +void zbar_symbol_ref (const zbar_symbol_t *sym, + int refs) +{ + zbar_symbol_t *ncsym = (zbar_symbol_t*)sym; + _zbar_symbol_refcnt(ncsym, refs); +} + +zbar_symbol_type_t zbar_symbol_get_type (const zbar_symbol_t *sym) +{ + return(sym->type); +} + +unsigned int zbar_symbol_get_configs (const zbar_symbol_t *sym) +{ + return(sym->configs); +} + +unsigned int zbar_symbol_get_modifiers (const zbar_symbol_t *sym) +{ + return(sym->modifiers); +} + +const char *zbar_symbol_get_data (const zbar_symbol_t *sym) +{ + return(sym->data); +} + +unsigned int zbar_symbol_get_data_length (const zbar_symbol_t *sym) +{ + return(sym->datalen); +} + +int zbar_symbol_get_count (const zbar_symbol_t *sym) +{ + return(sym->cache_count); +} + +int zbar_symbol_get_quality (const zbar_symbol_t *sym) +{ + return(sym->quality); +} + +unsigned zbar_symbol_get_loc_size (const zbar_symbol_t *sym) +{ + return(sym->npts); +} + +int zbar_symbol_get_loc_x (const zbar_symbol_t *sym, + unsigned idx) +{ + if(idx < sym->npts) + return(sym->pts[idx].x); + else + return(-1); +} + +int zbar_symbol_get_loc_y (const zbar_symbol_t *sym, + unsigned idx) +{ + if(idx < sym->npts) + return(sym->pts[idx].y); + else + return(-1); +} + +zbar_orientation_t zbar_symbol_get_orientation (const zbar_symbol_t *sym) +{ + return(sym->orient); +} + +const zbar_symbol_t *zbar_symbol_next (const zbar_symbol_t *sym) +{ + return((sym) ? sym->next : NULL); +} + +const zbar_symbol_set_t* +zbar_symbol_get_components (const zbar_symbol_t *sym) +{ + return(sym->syms); +} + +const zbar_symbol_t *zbar_symbol_first_component (const zbar_symbol_t *sym) +{ + return((sym && sym->syms) ? sym->syms->head : NULL); +} + +zbar_symbol_set_t *_zbar_symbol_set_create () +{ + zbar_symbol_set_t *syms = calloc(1, sizeof(*syms)); + _zbar_refcnt(&syms->refcnt, 1); + return(syms); +} + +inline void _zbar_symbol_set_free (zbar_symbol_set_t *syms) +{ + zbar_symbol_t *sym, *next; + for(sym = syms->head; sym; sym = next) { + next = sym->next; + sym->next = NULL; + _zbar_symbol_refcnt(sym, -1); + } + syms->head = NULL; + free(syms); +} + +void zbar_symbol_set_ref (const zbar_symbol_set_t *syms, + int delta) +{ + zbar_symbol_set_t *ncsyms = (zbar_symbol_set_t*)syms; + if(!_zbar_refcnt(&ncsyms->refcnt, delta) && delta <= 0) + _zbar_symbol_set_free(ncsyms); +} + +int zbar_symbol_set_get_size (const zbar_symbol_set_t *syms) +{ + return(syms->nsyms); +} + +const zbar_symbol_t* +zbar_symbol_set_first_symbol (const zbar_symbol_set_t *syms) +{ + zbar_symbol_t *sym = syms->tail; + if(sym) + return(sym->next); + return(syms->head); +} + +const zbar_symbol_t* +zbar_symbol_set_first_unfiltered (const zbar_symbol_set_t *syms) +{ + return(syms->head); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "img_scanner.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2009 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +/* internal image scanner APIs for 2D readers */ + +extern zbar_symbol_t *_zbar_image_scanner_alloc_sym(zbar_image_scanner_t*, + zbar_symbol_type_t, + int); +extern void _zbar_image_scanner_add_sym(zbar_image_scanner_t*, + zbar_symbol_t*); +extern void _zbar_image_scanner_recycle_syms(zbar_image_scanner_t*, + zbar_symbol_t*); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "img_scanner.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +#if 0 +# define ASSERT_POS \ + assert(p == data + x + y * (intptr_t)w) +#else +# define ASSERT_POS +#endif + +/* FIXME cache setting configurability */ + +/* time interval for which two images are considered "nearby" + */ +#define CACHE_PROXIMITY 1000 /* ms */ + +/* time that a result must *not* be detected before + * it will be reported again + */ +#define CACHE_HYSTERESIS 2000 /* ms */ + +/* time after which cache entries are invalidated + */ +#define CACHE_TIMEOUT (CACHE_HYSTERESIS * 2) /* ms */ + +#define NUM_SCN_CFGS (ZBAR_CFG_Y_DENSITY - ZBAR_CFG_X_DENSITY + 1) + +#define CFG(iscn, cfg) ((iscn)->configs[(cfg) - ZBAR_CFG_X_DENSITY]) +#define TEST_CFG(iscn, cfg) (((iscn)->config >> ((cfg) - ZBAR_CFG_POSITION)) & 1) + +#ifndef NO_STATS +# define STAT(x) iscn->stat_##x++ +#else +# define STAT(...) +# define dump_stats(...) +#endif + +#define RECYCLE_BUCKETS 5 + +typedef struct recycle_bucket_s { + int nsyms; + zbar_symbol_t *head; +} recycle_bucket_t; + +/* image scanner state */ +struct zbar_image_scanner_s { + zbar_scanner_t *scn; /* associated linear intensity scanner */ + zbar_decoder_t *dcode; /* associated symbol decoder */ +#ifdef ENABLE_QRCODE + qr_reader *qr; /* QR Code 2D reader */ +#endif + + const void *userdata; /* application data */ + /* user result callback */ + zbar_image_data_handler_t *handler; + + unsigned long time; /* scan start time */ + zbar_image_t *img; /* currently scanning image *root* */ + int dx, dy, du, umin, v; /* current scan direction */ + zbar_symbol_set_t *syms; /* previous decode results */ + /* recycled symbols in 4^n size buckets */ + recycle_bucket_t recycle[RECYCLE_BUCKETS]; + + int enable_cache; /* current result cache state */ + zbar_symbol_t *cache; /* inter-image result cache entries */ + + /* configuration settings */ + unsigned config; /* config flags */ + unsigned ean_config; + int configs[NUM_SCN_CFGS]; /* int valued configurations */ + int sym_configs[1][NUM_SYMS]; /* per-symbology configurations */ + +#ifndef NO_STATS + int stat_syms_new; + int stat_iscn_syms_inuse, stat_iscn_syms_recycle; + int stat_img_syms_inuse, stat_img_syms_recycle; + int stat_sym_new; + int stat_sym_recycle[RECYCLE_BUCKETS]; +#endif +}; + +void _zbar_image_scanner_recycle_syms (zbar_image_scanner_t *iscn, + zbar_symbol_t *sym) +{ + zbar_symbol_t *next = NULL; + for(; sym; sym = next) { + next = sym->next; + if(sym->refcnt && _zbar_refcnt(&sym->refcnt, -1)) { + /* unlink referenced symbol */ + /* FIXME handle outstanding component refs (currently unsupported) + */ + assert(sym->data_alloc); + sym->next = NULL; + } + else { + int i; + recycle_bucket_t *bucket; + /* recycle unreferenced symbol */ + if(!sym->data_alloc) { + sym->data = NULL; + sym->datalen = 0; + } + if(sym->syms) { + if(_zbar_refcnt(&sym->syms->refcnt, -1)) + assert(0); + _zbar_image_scanner_recycle_syms(iscn, sym->syms->head); + sym->syms->head = NULL; + _zbar_symbol_set_free(sym->syms); + sym->syms = NULL; + } + for(i = 0; i < RECYCLE_BUCKETS; i++) + if(sym->data_alloc < 1 << (i * 2)) + break; + if(i == RECYCLE_BUCKETS) { + assert(sym->data); + free(sym->data); + sym->data = NULL; + sym->data_alloc = 0; + i = 0; + } + bucket = &iscn->recycle[i]; + /* FIXME cap bucket fill */ + bucket->nsyms++; + sym->next = bucket->head; + bucket->head = sym; + } + } +} + +static inline int recycle_syms (zbar_image_scanner_t *iscn, + zbar_symbol_set_t *syms) +{ + if(_zbar_refcnt(&syms->refcnt, -1)) + return(1); + + _zbar_image_scanner_recycle_syms(iscn, syms->head); + syms->head = syms->tail = NULL; + syms->nsyms = 0; + return(0); +} + +inline void zbar_image_scanner_recycle_image (zbar_image_scanner_t *iscn, + zbar_image_t *img) +{ + zbar_symbol_set_t *syms = iscn->syms; + if(syms && syms->refcnt) { + if(recycle_syms(iscn, syms)) { + STAT(iscn_syms_inuse); + iscn->syms = NULL; + } + else + STAT(iscn_syms_recycle); + } + + syms = img->syms; + img->syms = NULL; + if(syms && recycle_syms(iscn, syms)) + STAT(img_syms_inuse); + else if(syms) { + STAT(img_syms_recycle); + + /* select one set to resurrect, destroy the other */ + if(iscn->syms) + _zbar_symbol_set_free(syms); + else + iscn->syms = syms; + } +} + +inline zbar_symbol_t* +_zbar_image_scanner_alloc_sym (zbar_image_scanner_t *iscn, + zbar_symbol_type_t type, + int datalen) +{ + /* recycle old or alloc new symbol */ + zbar_symbol_t *sym = NULL; + int i; + for(i = 0; i < RECYCLE_BUCKETS - 1; i++) + if(datalen <= 1 << (i * 2)) + break; + + for(; i > 0; i--) + if((sym = iscn->recycle[i].head)) { + STAT(sym_recycle[i]); + break; + } + + if(sym) { + iscn->recycle[i].head = sym->next; + sym->next = NULL; + assert(iscn->recycle[i].nsyms); + iscn->recycle[i].nsyms--; + } + else { + sym = calloc(1, sizeof(zbar_symbol_t)); + STAT(sym_new); + } + + /* init new symbol */ + sym->type = type; + sym->quality = 1; + sym->npts = 0; + sym->orient = ZBAR_ORIENT_UNKNOWN; + sym->cache_count = 0; + sym->time = iscn->time; + assert(!sym->syms); + + if(datalen > 0) { + sym->datalen = datalen - 1; + if(sym->data_alloc < datalen) { + if(sym->data) + free(sym->data); + sym->data_alloc = datalen; + sym->data = malloc(datalen); + } + } + else { + if(sym->data) + free(sym->data); + sym->data = NULL; + sym->datalen = sym->data_alloc = 0; + } + return(sym); +} + +static inline zbar_symbol_t *cache_lookup (zbar_image_scanner_t *iscn, + zbar_symbol_t *sym) +{ + /* search for matching entry in cache */ + zbar_symbol_t **entry = &iscn->cache; + while(*entry) { + if((*entry)->type == sym->type && + (*entry)->datalen == sym->datalen && + !memcmp((*entry)->data, sym->data, sym->datalen)) + break; + if((sym->time - (*entry)->time) > CACHE_TIMEOUT) { + /* recycle stale cache entry */ + zbar_symbol_t *next = (*entry)->next; + (*entry)->next = NULL; + _zbar_image_scanner_recycle_syms(iscn, *entry); + *entry = next; + } + else + entry = &(*entry)->next; + } + return(*entry); +} + +static inline void cache_sym (zbar_image_scanner_t *iscn, + zbar_symbol_t *sym) +{ + if(iscn->enable_cache) { + uint32_t age, near_thresh, far_thresh, dup; + zbar_symbol_t *entry = cache_lookup(iscn, sym); + if(!entry) { + /* FIXME reuse sym */ + entry = _zbar_image_scanner_alloc_sym(iscn, sym->type, + sym->datalen + 1); + entry->configs = sym->configs; + entry->modifiers = sym->modifiers; + memcpy(entry->data, sym->data, sym->datalen); + entry->time = sym->time - CACHE_HYSTERESIS; + entry->cache_count = 0; + /* add to cache */ + entry->next = iscn->cache; + iscn->cache = entry; + } + + /* consistency check and hysteresis */ + age = sym->time - entry->time; + entry->time = sym->time; + near_thresh = (age < CACHE_PROXIMITY); + far_thresh = (age >= CACHE_HYSTERESIS); + dup = (entry->cache_count >= 0); + if((!dup && !near_thresh) || far_thresh) { + int type = sym->type; + int h = _zbar_get_symbol_hash(type); + entry->cache_count = -iscn->sym_configs[0][h]; + } + else if(dup || near_thresh) + entry->cache_count++; + + sym->cache_count = entry->cache_count; + } + else + sym->cache_count = 0; +} + +void _zbar_image_scanner_add_sym(zbar_image_scanner_t *iscn, + zbar_symbol_t *sym) +{ + zbar_symbol_set_t *syms; + cache_sym(iscn, sym); + + syms = iscn->syms; + if(sym->cache_count || !syms->tail) { + sym->next = syms->head; + syms->head = sym; + } + else { + sym->next = syms->tail->next; + syms->tail->next = sym; + } + + if(!sym->cache_count) + syms->nsyms++; + else if(!syms->tail) + syms->tail = sym; + + _zbar_symbol_refcnt(sym, 1); +} + +#ifdef ENABLE_QRCODE +extern qr_finder_line *_zbar_decoder_get_qr_finder_line(zbar_decoder_t*); + +# define QR_FIXED(v, rnd) ((((v) << 1) + (rnd)) << (QR_FINDER_SUBPREC - 1)) +# define PRINT_FIXED(val, prec) \ + ((val) >> (prec)), \ + (1000 * ((val) & ((1 << (prec)) - 1)) / (1 << (prec))) + +static inline void qr_handler (zbar_image_scanner_t *iscn) +{ + unsigned u; + int vert; + qr_finder_line *line = _zbar_decoder_get_qr_finder_line(iscn->dcode); + assert(line); + u = zbar_scanner_get_edge(iscn->scn, line->pos[0], + QR_FINDER_SUBPREC); + line->boffs = u - zbar_scanner_get_edge(iscn->scn, line->boffs, + QR_FINDER_SUBPREC); + line->len = zbar_scanner_get_edge(iscn->scn, line->len, + QR_FINDER_SUBPREC); + line->eoffs = zbar_scanner_get_edge(iscn->scn, line->eoffs, + QR_FINDER_SUBPREC) - line->len; + line->len -= u; + + u = QR_FIXED(iscn->umin, 0) + iscn->du * u; + if(iscn->du < 0) { + int tmp = line->boffs; + line->boffs = line->eoffs; + line->eoffs = tmp; + u -= line->len; + } + vert = !iscn->dx; + line->pos[vert] = u; + line->pos[!vert] = QR_FIXED(iscn->v, 1); + + _zbar_qr_found_line(iscn->qr, vert, line); +} +#endif + +static void symbol_handler (zbar_decoder_t *dcode) +{ + zbar_image_scanner_t *iscn = zbar_decoder_get_userdata(dcode); + zbar_symbol_type_t type = zbar_decoder_get_type(dcode); + int x = 0, y = 0, dir; + const char *data; + unsigned datalen; + zbar_symbol_t *sym; + +#ifdef ENABLE_QRCODE + if(type == ZBAR_QRCODE) { + qr_handler(iscn); + return; + } +#else + assert(type != ZBAR_QRCODE); +#endif + + if(TEST_CFG(iscn, ZBAR_CFG_POSITION)) { + /* tmp position fixup */ + int w = zbar_scanner_get_width(iscn->scn); + int u = iscn->umin + iscn->du * zbar_scanner_get_edge(iscn->scn, w, 0); + if(iscn->dx) { + x = u; + y = iscn->v; + } + else { + x = iscn->v; + y = u; + } + } + + /* FIXME debug flag to save/display all PARTIALs */ + if(type <= ZBAR_PARTIAL) { + zprintf(256, "partial symbol @(%d,%d)\n", x, y); + return; + } + + data = zbar_decoder_get_data(dcode); + datalen = zbar_decoder_get_data_length(dcode); + + /* FIXME need better symbol matching */ + for(sym = iscn->syms->head; sym; sym = sym->next) + if(sym->type == type && + sym->datalen == datalen && + !memcmp(sym->data, data, datalen)) { + sym->quality++; + zprintf(224, "dup symbol @(%d,%d): dup %s: %.20s\n", + x, y, zbar_get_symbol_name(type), data); + if(TEST_CFG(iscn, ZBAR_CFG_POSITION)) + /* add new point to existing set */ + /* FIXME should be polygon */ + sym_add_point(sym, x, y); + return; + } + + sym = _zbar_image_scanner_alloc_sym(iscn, type, datalen + 1); + sym->configs = zbar_decoder_get_configs(dcode, type); + sym->modifiers = zbar_decoder_get_modifiers(dcode); + /* FIXME grab decoder buffer */ + memcpy(sym->data, data, datalen + 1); + + /* initialize first point */ + if(TEST_CFG(iscn, ZBAR_CFG_POSITION)) { + zprintf(192, "new symbol @(%d,%d): %s: %.20s\n", + x, y, zbar_get_symbol_name(type), data); + sym_add_point(sym, x, y); + } + + dir = zbar_decoder_get_direction(dcode); + if(dir) + sym->orient = (iscn->dy != 0) + ((iscn->du ^ dir) & 2); + + _zbar_image_scanner_add_sym(iscn, sym); +} + +zbar_image_scanner_t *zbar_image_scanner_create () +{ + zbar_image_scanner_t *iscn = calloc(1, sizeof(zbar_image_scanner_t)); + if(!iscn) + return(NULL); + iscn->dcode = zbar_decoder_create(); + iscn->scn = zbar_scanner_create(iscn->dcode); + if(!iscn->dcode || !iscn->scn) { + zbar_image_scanner_destroy(iscn); + return(NULL); + } + zbar_decoder_set_userdata(iscn->dcode, iscn); + zbar_decoder_set_handler(iscn->dcode, symbol_handler); + +#ifdef ENABLE_QRCODE + iscn->qr = _zbar_qr_create(); +#endif + + /* apply default configuration */ + CFG(iscn, ZBAR_CFG_X_DENSITY) = 1; + CFG(iscn, ZBAR_CFG_Y_DENSITY) = 1; + zbar_image_scanner_set_config(iscn, 0, ZBAR_CFG_POSITION, 1); + zbar_image_scanner_set_config(iscn, 0, ZBAR_CFG_UNCERTAINTY, 2); + zbar_image_scanner_set_config(iscn, ZBAR_QRCODE, ZBAR_CFG_UNCERTAINTY, 0); + zbar_image_scanner_set_config(iscn, ZBAR_CODE128, ZBAR_CFG_UNCERTAINTY, 0); + zbar_image_scanner_set_config(iscn, ZBAR_CODE93, ZBAR_CFG_UNCERTAINTY, 0); + zbar_image_scanner_set_config(iscn, ZBAR_CODE39, ZBAR_CFG_UNCERTAINTY, 0); + zbar_image_scanner_set_config(iscn, ZBAR_CODABAR, ZBAR_CFG_UNCERTAINTY, 1); + zbar_image_scanner_set_config(iscn, ZBAR_COMPOSITE, ZBAR_CFG_UNCERTAINTY, 0); + return(iscn); +} + +#ifndef NO_STATS +static inline void dump_stats (const zbar_image_scanner_t *iscn) +{ + int i; + zprintf(1, "symbol sets allocated = %-4d\n", iscn->stat_syms_new); + zprintf(1, " scanner syms in use = %-4d\trecycled = %-4d\n", + iscn->stat_iscn_syms_inuse, iscn->stat_iscn_syms_recycle); + zprintf(1, " image syms in use = %-4d\trecycled = %-4d\n", + iscn->stat_img_syms_inuse, iscn->stat_img_syms_recycle); + zprintf(1, "symbols allocated = %-4d\n", iscn->stat_sym_new); + for(i = 0; i < RECYCLE_BUCKETS; i++) + zprintf(1, " recycled[%d] = %-4d\n", + i, iscn->stat_sym_recycle[i]); +} +#endif + +void zbar_image_scanner_destroy (zbar_image_scanner_t *iscn) +{ + int i; + dump_stats(iscn); + if(iscn->syms) { + if(iscn->syms->refcnt) + zbar_symbol_set_ref(iscn->syms, -1); + else + _zbar_symbol_set_free(iscn->syms); + iscn->syms = NULL; + } + if(iscn->scn) + zbar_scanner_destroy(iscn->scn); + iscn->scn = NULL; + if(iscn->dcode) + zbar_decoder_destroy(iscn->dcode); + iscn->dcode = NULL; + for(i = 0; i < RECYCLE_BUCKETS; i++) { + zbar_symbol_t *sym, *next; + for(sym = iscn->recycle[i].head; sym; sym = next) { + next = sym->next; + _zbar_symbol_free(sym); + } + } +#ifdef ENABLE_QRCODE + if(iscn->qr) { + _zbar_qr_destroy(iscn->qr); + iscn->qr = NULL; + } +#endif + free(iscn); +} + +zbar_image_data_handler_t* +zbar_image_scanner_set_data_handler (zbar_image_scanner_t *iscn, + zbar_image_data_handler_t *handler, + const void *userdata) +{ + zbar_image_data_handler_t *result = iscn->handler; + iscn->handler = handler; + iscn->userdata = userdata; + return(result); +} + +int zbar_image_scanner_set_config (zbar_image_scanner_t *iscn, + zbar_symbol_type_t sym, + zbar_config_t cfg, + int val) +{ + if((sym == 0 || sym == ZBAR_COMPOSITE) && cfg == ZBAR_CFG_ENABLE) { + iscn->ean_config = !!val; + if(sym) + return(0); + } + + if(cfg < ZBAR_CFG_UNCERTAINTY) + return(zbar_decoder_set_config(iscn->dcode, sym, cfg, val)); + + if(cfg < ZBAR_CFG_POSITION) { + int c, i; + if(cfg > ZBAR_CFG_UNCERTAINTY) + return(1); + c = cfg - ZBAR_CFG_UNCERTAINTY; + if(sym > ZBAR_PARTIAL) { + i = _zbar_get_symbol_hash(sym); + iscn->sym_configs[c][i] = val; + } + else + for(i = 0; i < NUM_SYMS; i++) + iscn->sym_configs[c][i] = val; + return(0); + } + + if(sym > ZBAR_PARTIAL) + return(1); + + if(cfg >= ZBAR_CFG_X_DENSITY && cfg <= ZBAR_CFG_Y_DENSITY) { + CFG(iscn, cfg) = val; + return(0); + } + + if(cfg > ZBAR_CFG_POSITION) + return(1); + cfg -= ZBAR_CFG_POSITION; + + if(!val) + iscn->config &= ~(1 << cfg); + else if(val == 1) + iscn->config |= (1 << cfg); + else + return(1); + + return(0); +} + +void zbar_image_scanner_enable_cache (zbar_image_scanner_t *iscn, + int enable) +{ + if(iscn->cache) { + /* recycle all cached syms */ + _zbar_image_scanner_recycle_syms(iscn, iscn->cache); + iscn->cache = NULL; + } + iscn->enable_cache = (enable) ? 1 : 0; +} + +const zbar_symbol_set_t * +zbar_image_scanner_get_results (const zbar_image_scanner_t *iscn) +{ + return(iscn->syms); +} + +static inline void quiet_border (zbar_image_scanner_t *iscn) +{ + /* flush scanner pipeline */ + zbar_scanner_t *scn = iscn->scn; + zbar_scanner_flush(scn); + zbar_scanner_flush(scn); + zbar_scanner_new_scan(scn); +} + +#define movedelta(dx, dy) do { \ + x += (dx); \ + y += (dy); \ + p += (dx) + ((uintptr_t)(dy) * w); \ + } while(0); + +int zbar_scan_image (zbar_image_scanner_t *iscn, + zbar_image_t *img) +{ + zbar_symbol_set_t *syms; + const uint8_t *data; + zbar_scanner_t *scn = iscn->scn; + unsigned w, h, cx1, cy1; + int density; + + /* timestamp image + * FIXME prefer video timestamp + */ + iscn->time = 0;//_zbar_timer_now(); + +#ifdef ENABLE_QRCODE + _zbar_qr_reset(iscn->qr); +#endif + + /* image must be in grayscale format */ + if(img->format != fourcc('Y','8','0','0') && + img->format != fourcc('G','R','E','Y')) + return(-1); + iscn->img = img; + + /* recycle previous scanner and image results */ + zbar_image_scanner_recycle_image(iscn, img); + syms = iscn->syms; + if(!syms) { + syms = iscn->syms = _zbar_symbol_set_create(); + STAT(syms_new); + zbar_symbol_set_ref(syms, 1); + } + else + zbar_symbol_set_ref(syms, 2); + img->syms = syms; + + w = img->width; + h = img->height; + cx1 = img->crop_x + img->crop_w; + assert(cx1 <= w); + cy1 = img->crop_y + img->crop_h; + assert(cy1 <= h); + data = img->data; + + zbar_image_write_png(img, "debug.png"); + svg_open("debug.svg", 0, 0, w, h); + svg_image("debug.png", w, h); + + zbar_scanner_new_scan(scn); + + density = CFG(iscn, ZBAR_CFG_Y_DENSITY); + if(density > 0) { + const uint8_t *p = data; + int x = 0, y = 0; + + int border = (((img->crop_h - 1) % density) + 1) / 2; + if(border > img->crop_h / 2) + border = img->crop_h / 2; + border += img->crop_y; + assert(border <= h); + svg_group_start("scanner", 0, 1, 1, 0, 0); + iscn->dy = 0; + + movedelta(img->crop_x, border); + iscn->v = y; + + while(y < cy1) { + int cx0 = img->crop_x;; + zprintf(128, "img_x+: %04d,%04d @%p\n", x, y, p); + svg_path_start("vedge", 1. / 32, 0, y + 0.5); + iscn->dx = iscn->du = 1; + iscn->umin = cx0; + while(x < cx1) { + uint8_t d = *p; + movedelta(1, 0); + zbar_scan_y(scn, d); + } + ASSERT_POS; + quiet_border(iscn); + svg_path_end(); + + movedelta(-1, density); + iscn->v = y; + if(y >= cy1) + break; + + zprintf(128, "img_x-: %04d,%04d @%p\n", x, y, p); + svg_path_start("vedge", -1. / 32, w, y + 0.5); + iscn->dx = iscn->du = -1; + iscn->umin = cx1; + while(x >= cx0) { + uint8_t d = *p; + movedelta(-1, 0); + zbar_scan_y(scn, d); + } + ASSERT_POS; + quiet_border(iscn); + svg_path_end(); + + movedelta(1, density); + iscn->v = y; + } + svg_group_end(); + } + iscn->dx = 0; + + density = CFG(iscn, ZBAR_CFG_X_DENSITY); + if(density > 0) { + const uint8_t *p = data; + int x = 0, y = 0; + + int border = (((img->crop_w - 1) % density) + 1) / 2; + if(border > img->crop_w / 2) + border = img->crop_w / 2; + border += img->crop_x; + assert(border <= w); + svg_group_start("scanner", 90, 1, -1, 0, 0); + movedelta(border, img->crop_y); + iscn->v = x; + + while(x < cx1) { + int cy0 = img->crop_y; + zprintf(128, "img_y+: %04d,%04d @%p\n", x, y, p); + svg_path_start("vedge", 1. / 32, 0, x + 0.5); + iscn->dy = iscn->du = 1; + iscn->umin = cy0; + while(y < cy1) { + uint8_t d = *p; + movedelta(0, 1); + zbar_scan_y(scn, d); + } + ASSERT_POS; + quiet_border(iscn); + svg_path_end(); + + movedelta(density, -1); + iscn->v = x; + if(x >= cx1) + break; + + zprintf(128, "img_y-: %04d,%04d @%p\n", x, y, p); + svg_path_start("vedge", -1. / 32, h, x + 0.5); + iscn->dy = iscn->du = -1; + iscn->umin = cy1; + while(y >= cy0) { + uint8_t d = *p; + movedelta(0, -1); + zbar_scan_y(scn, d); + } + ASSERT_POS; + quiet_border(iscn); + svg_path_end(); + + movedelta(density, 1); + iscn->v = x; + } + svg_group_end(); + } + iscn->dy = 0; + iscn->img = NULL; + +#ifdef ENABLE_QRCODE + _zbar_qr_decode(iscn->qr, iscn, img); +#endif + + /* FIXME tmp hack to filter bad EAN results */ + /* FIXME tmp hack to merge simple case EAN add-ons */ + char filter = (!iscn->enable_cache && + (density == 1 || CFG(iscn, ZBAR_CFG_Y_DENSITY) == 1)); + int nean = 0, naddon = 0; + if(syms->nsyms) { + zbar_symbol_t **symp; + for(symp = &syms->head; *symp; ) { + zbar_symbol_t *sym = *symp; + if(sym->cache_count <= 0 && + ((sym->type < ZBAR_COMPOSITE && sym->type > ZBAR_PARTIAL) || + sym->type == ZBAR_DATABAR || + sym->type == ZBAR_DATABAR_EXP || + sym->type == ZBAR_CODABAR)) + { + if((sym->type == ZBAR_CODABAR || filter) && sym->quality < 4) { + if(iscn->enable_cache) { + /* revert cache update */ + zbar_symbol_t *entry = cache_lookup(iscn, sym); + if(entry) + entry->cache_count--; + else + assert(0); + } + + /* recycle */ + *symp = sym->next; + syms->nsyms--; + sym->next = NULL; + _zbar_image_scanner_recycle_syms(iscn, sym); + continue; + } + else if(sym->type < ZBAR_COMPOSITE && + sym->type != ZBAR_ISBN10) + { + if(sym->type > ZBAR_EAN5) + nean++; + else + naddon++; + } + } + symp = &sym->next; + } + + if(nean == 1 && naddon == 1 && iscn->ean_config) { + /* create container symbol for composite result */ + zbar_symbol_t *ean = NULL, *addon = NULL; + for(symp = &syms->head; *symp; ) { + zbar_symbol_t *sym = *symp; + if(sym->type < ZBAR_COMPOSITE && sym->type > ZBAR_PARTIAL) { + /* move to composite */ + *symp = sym->next; + syms->nsyms--; + sym->next = NULL; + if(sym->type <= ZBAR_EAN5) + addon = sym; + else + ean = sym; + } + else + symp = &sym->next; + } + assert(ean); + assert(addon); + + int datalen = ean->datalen + addon->datalen + 1; + zbar_symbol_t *ean_sym = + _zbar_image_scanner_alloc_sym(iscn, ZBAR_COMPOSITE, datalen); + ean_sym->orient = ean->orient; + ean_sym->syms = _zbar_symbol_set_create(); + memcpy(ean_sym->data, ean->data, ean->datalen); + memcpy(ean_sym->data + ean->datalen, + addon->data, addon->datalen + 1); + ean_sym->syms->head = ean; + ean->next = addon; + ean_sym->syms->nsyms = 2; + _zbar_image_scanner_add_sym(iscn, ean_sym); + } + } + + if(syms->nsyms && iscn->handler) + iscn->handler(img, iscn->userdata); + + svg_close(); + return(syms->nsyms); +} + +#undef TEST_CFG +#undef CFG + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +#define NUM_CFGS (ZBAR_CFG_MAX_LEN - ZBAR_CFG_MIN_LEN + 1) + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "ean.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +/* state of each parallel decode attempt */ +typedef struct ean_pass_s { + signed char state; /* module position of w[idx] in symbol */ +#define STATE_REV 0x80 /* scan direction reversed */ +#define STATE_ADDON 0x40 /* scanning add-on */ +#define STATE_IDX 0x3f /* element offset into symbol */ + unsigned width; /* width of last character */ + unsigned char raw[7]; /* decode in process */ +} ean_pass_t; + +/* EAN/UPC specific decode state */ +typedef struct ean_decoder_s { + ean_pass_t pass[4]; /* state of each parallel decode attempt */ + zbar_symbol_type_t left; /* current holding buffer contents */ + zbar_symbol_type_t right; + int direction; /* scan direction */ + unsigned s4, width; /* character width */ + signed char buf[18]; /* holding buffer */ + + signed char enable; + unsigned ean13_config; + unsigned ean8_config; + unsigned upca_config; + unsigned upce_config; + unsigned isbn10_config; + unsigned isbn13_config; + unsigned ean5_config; + unsigned ean2_config; +} ean_decoder_t; + +/* reset EAN/UPC pass specific state */ +static inline void ean_new_scan (ean_decoder_t *ean) +{ + ean->pass[0].state = ean->pass[1].state = -1; + ean->pass[2].state = ean->pass[3].state = -1; + ean->s4 = 0; +} + +/* reset all EAN/UPC state */ +static inline void ean_reset (ean_decoder_t *ean) +{ + ean_new_scan(ean); + ean->left = ean->right = ZBAR_NONE; +} + +static inline unsigned ean_get_config (ean_decoder_t *ean, + zbar_symbol_type_t sym) +{ + switch(sym) { + case ZBAR_EAN2: return(ean->ean2_config); + case ZBAR_EAN5: return(ean->ean5_config); + case ZBAR_EAN8: return(ean->ean8_config); + case ZBAR_UPCE: return(ean->upce_config); + case ZBAR_ISBN10: return(ean->isbn10_config); + case ZBAR_UPCA: return(ean->upca_config); + case ZBAR_EAN13: return(ean->ean13_config); + case ZBAR_ISBN13: return(ean->isbn13_config); + default: return(0); + } +} + +/* decode EAN/UPC symbols */ +zbar_symbol_type_t _zbar_decode_ean(zbar_decoder_t *dcode); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "i25.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2008-2009 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +/* interleaved 2 of 5 specific decode state */ +typedef struct i25_decoder_s { + unsigned direction : 1; /* scan direction: 0=fwd/space, 1=rev/bar */ + unsigned element : 4; /* element offset 0-8 */ + int character : 12; /* character position in symbol */ + unsigned s10; /* current character width */ + unsigned width; /* last character width */ + unsigned char buf[4]; /* initial scan buffer */ + + unsigned config; + int configs[NUM_CFGS]; /* int valued configurations */ +} i25_decoder_t; + +/* reset interleaved 2 of 5 specific state */ +static inline void i25_reset (i25_decoder_t *i25) +{ + i25->direction = 0; + i25->element = 0; + i25->character = -1; + i25->s10 = 0; +} + +/* decode interleaved 2 of 5 symbols */ +zbar_symbol_type_t _zbar_decode_i25(zbar_decoder_t *dcode); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "databar.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +#define DATABAR_MAX_SEGMENTS 32 + +/* active DataBar (partial) segment entry */ +typedef struct databar_segment_s { + signed finder : 5; /* finder pattern */ + unsigned exp : 1; /* DataBar expanded finder */ + unsigned color : 1; /* finder coloring */ + unsigned side : 1; /* data character side of finder */ + + unsigned partial : 1; /* unpaired partial segment */ + unsigned count : 7; /* times encountered */ + unsigned epoch : 8; /* age, in characters scanned */ + unsigned check : 8; /* bar checksum */ + signed short data; /* decoded character data */ + unsigned short width; /* measured width of finder (14 modules) */ +} databar_segment_t; + +/* DataBar specific decode state */ +typedef struct databar_decoder_s { + unsigned config; /* decoder configuration flags */ + unsigned config_exp; + + unsigned csegs : 8; /* allocated segments */ + unsigned epoch : 8; /* current scan */ + + databar_segment_t *segs; /* active segment list */ + signed char chars[16]; /* outstanding character indices */ +} databar_decoder_t; + +/* reset DataBar segment decode state */ +static inline void databar_new_scan (databar_decoder_t *db) +{ + int i; + for(i = 0; i < 16; i++) + if(db->chars[i] >= 0) { + databar_segment_t *seg = db->segs + db->chars[i]; + if(seg->partial) + seg->finder = -1; + db->chars[i] = -1; + } +} + +/* reset DataBar accumulated segments */ +static inline void databar_reset (databar_decoder_t *db) +{ + int i, n = db->csegs; + databar_new_scan(db); + for(i = 0; i < n; i++) + db->segs[i].finder = -1; +} + +/* decode DataBar symbols */ +zbar_symbol_type_t _zbar_decode_databar(zbar_decoder_t *dcode); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "codabar.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2011 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +/* Codabar specific decode state */ +typedef struct codabar_decoder_s { + unsigned direction : 1; /* scan direction: 0=fwd, 1=rev */ + unsigned element : 4; /* element offset 0-7 */ + int character : 12; /* character position in symbol */ + unsigned s7; /* current character width */ + unsigned width; /* last character width */ + unsigned char buf[6]; /* initial scan buffer */ + + unsigned config; + int configs[NUM_CFGS]; /* int valued configurations */ +} codabar_decoder_t; + +/* reset Codabar specific state */ +static inline void codabar_reset (codabar_decoder_t *codabar) +{ + codabar->direction = 0; + codabar->element = 0; + codabar->character = -1; + codabar->s7 = 0; +} + +/* decode Codabar symbols */ +zbar_symbol_type_t _zbar_decode_codabar(zbar_decoder_t *dcode); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "code39.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2008-2009 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +/* Code 39 specific decode state */ +typedef struct code39_decoder_s { + unsigned direction : 1; /* scan direction: 0=fwd, 1=rev */ + unsigned element : 4; /* element offset 0-8 */ + int character : 12; /* character position in symbol */ + unsigned s9; /* current character width */ + unsigned width; /* last character width */ + + unsigned config; + int configs[NUM_CFGS]; /* int valued configurations */ +} code39_decoder_t; + +/* reset Code 39 specific state */ +static inline void code39_reset (code39_decoder_t *dcode39) +{ + dcode39->direction = 0; + dcode39->element = 0; + dcode39->character = -1; + dcode39->s9 = 0; +} + +/* decode Code 39 symbols */ +zbar_symbol_type_t _zbar_decode_code39(zbar_decoder_t *dcode); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "code93.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +/* Code 93 specific decode state */ +typedef struct code93_decoder_s { + unsigned direction : 1; /* scan direction: 0=fwd/space, 1=rev/bar */ + unsigned element : 3; /* element offset 0-5 */ + int character : 12; /* character position in symbol */ + unsigned width; /* last character width */ + unsigned char buf; /* first character */ + + unsigned config; + int configs[NUM_CFGS]; /* int valued configurations */ +} code93_decoder_t; + +/* reset Code 93 specific state */ +static inline void code93_reset (code93_decoder_t *dcode93) +{ + dcode93->direction = 0; + dcode93->element = 0; + dcode93->character = -1; +} + +/* decode Code 93 symbols */ +zbar_symbol_type_t _zbar_decode_code93(zbar_decoder_t *dcode); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "code128.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2009 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +/* Code 128 specific decode state */ +typedef struct code128_decoder_s { + unsigned direction : 1; /* scan direction: 0=fwd/space, 1=rev/bar */ + unsigned element : 3; /* element offset 0-5 */ + int character : 12; /* character position in symbol */ + unsigned char start; /* start character */ + unsigned s6; /* character width */ + unsigned width; /* last character width */ + + unsigned config; + int configs[NUM_CFGS]; /* int valued configurations */ +} code128_decoder_t; + +/* reset Code 128 specific state */ +static inline void code128_reset (code128_decoder_t *dcode128) +{ + dcode128->direction = 0; + dcode128->element = 0; + dcode128->character = -1; + dcode128->s6 = 0; +} + +/* decode Code 128 symbols */ +zbar_symbol_type_t _zbar_decode_code128(zbar_decoder_t *dcode); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "pdf417.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2008-2009 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +/* PDF417 specific decode state */ +typedef struct pdf417_decoder_s { + unsigned direction : 1; /* scan direction: 0=fwd/space, 1=rev/bar */ + unsigned element : 3; /* element offset 0-7 */ + int character : 12; /* character position in symbol */ + unsigned s8; /* character width */ + + unsigned config; + int configs[NUM_CFGS]; /* int valued configurations */ +} pdf417_decoder_t; + +/* reset PDF417 specific state */ +static inline void pdf417_reset (pdf417_decoder_t *pdf417) +{ + pdf417->direction = 0; + pdf417->element = 0; + pdf417->character = -1; + pdf417->s8 = 0; +} + +/* decode PDF417 symbols */ +zbar_symbol_type_t _zbar_decode_pdf417(zbar_decoder_t *dcode); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "decoder.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +/* size of bar width history (implementation assumes power of two) */ +#ifndef DECODE_WINDOW +# define DECODE_WINDOW 16 +#endif + +/* initial data buffer allocation */ +#ifndef BUFFER_MIN +# define BUFFER_MIN 0x20 +#endif + +/* maximum data buffer allocation + * (longer symbols are rejected) + */ +#ifndef BUFFER_MAX +# define BUFFER_MAX 0x100 +#endif + +/* buffer allocation increment */ +#ifndef BUFFER_INCR +# define BUFFER_INCR 0x10 +#endif + +#define CFG(dcode, cfg) ((dcode).configs[(cfg) - ZBAR_CFG_MIN_LEN]) +#define TEST_CFG(config, cfg) (((config) >> (cfg)) & 1) +#define MOD(mod) (1 << (mod)) + +/* symbology independent decoder state */ +struct zbar_decoder_s { + unsigned char idx; /* current width index */ + unsigned w[DECODE_WINDOW]; /* window of last N bar widths */ + zbar_symbol_type_t type; /* type of last decoded data */ + zbar_symbol_type_t lock; /* buffer lock */ + unsigned modifiers; /* symbology modifier */ + int direction; /* direction of last decoded data */ + unsigned s6; /* 6-element character width */ + + /* everything above here is automatically reset */ + unsigned buf_alloc; /* dynamic buffer allocation */ + unsigned buflen; /* binary data length */ + unsigned char *buf; /* decoded characters */ + void *userdata; /* application data */ + zbar_decoder_handler_t *handler; /* application callback */ + + /* symbology specific state */ +#ifdef ENABLE_EAN + ean_decoder_t ean; /* EAN/UPC parallel decode attempts */ +#endif +#ifdef ENABLE_I25 + i25_decoder_t i25; /* Interleaved 2 of 5 decode state */ +#endif +#ifdef ENABLE_DATABAR + databar_decoder_t databar; /* DataBar decode state */ +#endif +#ifdef ENABLE_CODABAR + codabar_decoder_t codabar; /* Codabar decode state */ +#endif +#ifdef ENABLE_CODE39 + code39_decoder_t code39; /* Code 39 decode state */ +#endif +#ifdef ENABLE_CODE93 + code93_decoder_t code93; /* Code 93 decode state */ +#endif +#ifdef ENABLE_CODE128 + code128_decoder_t code128; /* Code 128 decode state */ +#endif +#ifdef ENABLE_PDF417 + pdf417_decoder_t pdf417; /* PDF417 decode state */ +#endif +#ifdef ENABLE_QRCODE + qr_finder_t qrf; /* QR Code finder state */ +#endif +}; + +/* return current element color */ +static inline char get_color (const zbar_decoder_t *dcode) +{ + return(dcode->idx & 1); +} + +/* retrieve i-th previous element width */ +static inline unsigned get_width (const zbar_decoder_t *dcode, + unsigned char offset) +{ + return(dcode->w[(dcode->idx - offset) & (DECODE_WINDOW - 1)]); +} + +/* retrieve bar+space pair width starting at offset i */ +static inline unsigned pair_width (const zbar_decoder_t *dcode, + unsigned char offset) +{ + return(get_width(dcode, offset) + get_width(dcode, offset + 1)); +} + +/* calculate total character width "s" + * - start of character identified by context sensitive offset + * (<= DECODE_WINDOW - n) + * - size of character is n elements + */ +static inline unsigned calc_s (const zbar_decoder_t *dcode, + unsigned char offset, + unsigned char n) +{ + /* FIXME check that this gets unrolled for constant n */ + unsigned s = 0; + while(n--) + s += get_width(dcode, offset++); + return(s); +} + +/* fixed character width decode assist + * bar+space width are compared as a fraction of the reference dimension "x" + * - +/- 1/2 x tolerance + * - measured total character width (s) compared to symbology baseline (n) + * (n = 7 for EAN/UPC, 11 for Code 128) + * - bar+space *pair width* "e" is used to factor out bad "exposures" + * ("blooming" or "swelling" of dark or light areas) + * => using like-edge measurements avoids these issues + * - n should be > 3 + */ +static inline int decode_e (unsigned e, + unsigned s, + unsigned n) +{ + /* result is encoded number of units - 2 + * (for use as zero based index) + * or -1 if invalid + */ + unsigned char E = ((e * n * 2 + 1) / s - 3) / 2; + return((E >= n - 3) ? -1 : E); +} + +/* sort three like-colored elements and return ordering + */ +static inline unsigned decode_sort3 (zbar_decoder_t *dcode, + int i0) +{ + unsigned w0 = get_width(dcode, i0); + unsigned w2 = get_width(dcode, i0 + 2); + unsigned w4 = get_width(dcode, i0 + 4); + if(w0 < w2) { + if(w2 < w4) + return((i0 << 8) | ((i0 + 2) << 4) | (i0 + 4)); + if(w0 < w4) + return((i0 << 8) | ((i0 + 4) << 4) | (i0 + 2)); + return(((i0 + 4) << 8) | (i0 << 4) | (i0 + 2)); + } + if(w4 < w2) + return(((i0 + 4) << 8) | ((i0 + 2) << 4) | i0); + if(w0 < w4) + return(((i0 + 2) << 8) | (i0 << 4) | (i0 + 4)); + return(((i0 + 2) << 8) | ((i0 + 4) << 4) | i0); +} + +/* sort N like-colored elements and return ordering + */ +static inline unsigned decode_sortn (zbar_decoder_t *dcode, + int n, + int i0) +{ + unsigned mask = 0, sort = 0; + int i; + for(i = n - 1; i >= 0; i--) { + unsigned wmin = UINT_MAX; + int jmin = -1, j; + for(j = n - 1; j >= 0; j--) { + if((mask >> j) & 1) + continue; + unsigned w = get_width(dcode, i0 + j * 2); + if(wmin >= w) { + wmin = w; + jmin = j; + } + } + zassert(jmin >= 0, 0, "sortn(%d,%d) jmin=%d", + n, i0, jmin); + sort <<= 4; + mask |= 1 << jmin; + sort |= i0 + jmin * 2; + } + return(sort); +} + +/* acquire shared state lock */ +static inline char acquire_lock (zbar_decoder_t *dcode, + zbar_symbol_type_t req) +{ + if(dcode->lock) { + dbprintf(2, " [locked %d]\n", dcode->lock); + return(1); + } + dcode->lock = req; + return(0); +} + +/* check and release shared state lock */ +static inline char release_lock (zbar_decoder_t *dcode, + zbar_symbol_type_t req) +{ + zassert(dcode->lock == req, 1, "lock=%d req=%d\n", + dcode->lock, req); + dcode->lock = 0; + return(0); +} + +/* ensure output buffer has sufficient allocation for request */ +static inline char size_buf (zbar_decoder_t *dcode, + unsigned len) +{ + unsigned char *buf; + if(len <= BUFFER_MIN) + return(0); + if(len < dcode->buf_alloc) + /* FIXME size reduction heuristic? */ + return(0); + if(len > BUFFER_MAX) + return(1); + if(len < dcode->buf_alloc + BUFFER_INCR) { + len = dcode->buf_alloc + BUFFER_INCR; + if(len > BUFFER_MAX) + len = BUFFER_MAX; + } + buf = realloc(dcode->buf, len); + if(!buf) + return(1); + dcode->buf = buf; + dcode->buf_alloc = len; + return(0); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "ean.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +/* partial decode symbol location */ +typedef enum symbol_partial_e { + EAN_LEFT = 0x0000, + EAN_RIGHT = 0x1000, +} symbol_partial_t; + +/* convert compact encoded D2E1E2 to character (bit4 is parity) */ +static const unsigned char digits[] = { /* E1 E2 */ + 0x06, 0x10, 0x04, 0x13, /* 2 2-5 */ + 0x19, 0x08, 0x11, 0x05, /* 3 2-5 (d2 <= thr) */ + 0x09, 0x12, 0x07, 0x15, /* 4 2-5 (d2 <= thr) */ + 0x16, 0x00, 0x14, 0x03, /* 5 2-5 */ + 0x18, 0x01, 0x02, 0x17, /* E1E2=43,44,33,34 (d2 > thr) */ +}; + +static const unsigned char parity_decode[] = { + 0xf0, /* [xx] BBBBBB = RIGHT half EAN-13 */ + + /* UPC-E check digit encoding */ + 0xff, + 0xff, + 0x0f, /* [07] BBBAAA = 0 */ + 0xff, + 0x1f, /* [0b] BBABAA = 1 */ + 0x2f, /* [0d] BBAABA = 2 */ + 0xf3, /* [0e] BBAAAB = 3 */ + 0xff, + 0x4f, /* [13] BABBAA = 4 */ + 0x7f, /* [15] BABABA = 7 */ + 0xf8, /* [16] BABAAB = 8 */ + 0x5f, /* [19] BAABBA = 5 */ + 0xf9, /* [1a] BAABAB = 9 */ + 0xf6, /* [1c] BAAABB = 6 */ + 0xff, + + /* LEFT half EAN-13 leading digit */ + 0xff, + 0x6f, /* [23] ABBBAA = 6 */ + 0x9f, /* [25] ABBABA = 9 */ + 0xf5, /* [26] ABBAAB = 5 */ + 0x8f, /* [29] ABABBA = 8 */ + 0xf7, /* [2a] ABABAB = 7 */ + 0xf4, /* [2c] ABAABB = 4 */ + 0xff, + 0x3f, /* [31] AABBBA = 3 */ + 0xf2, /* [32] AABBAB = 2 */ + 0xf1, /* [34] AABABB = 1 */ + 0xff, + 0xff, + 0xff, + 0xff, + 0x0f, /* [3f] AAAAAA = 0 */ +}; + +static inline int check_width (unsigned w0, + unsigned w1) +{ + unsigned dw0 = w0; + w0 *= 8; + w1 *= 8; + return(w0 - dw0 <= w1 && w1 <= w0 + dw0); +} + +/* evaluate previous N (>= 2) widths as auxiliary pattern, + * using preceding 4 as character width + */ +static inline signed char aux_end (zbar_decoder_t *dcode, + unsigned char fwd) +{ + signed char code, i; + + /* reference width from previous character */ + unsigned s = calc_s(dcode, 4 + fwd, 4); + + /* check quiet zone */ + unsigned qz = get_width(dcode, 0); + if(!fwd && qz && qz <= s * 3 / 4) { + dbprintf(2, " [invalid quiet]"); + return(-1); + } + + dbprintf(2, " ("); + code = 0; + for(i = 1 - fwd; i < 3 + fwd; i++) { + unsigned e = get_width(dcode, i) + get_width(dcode, i + 1); + dbprintf(2, " %d", e); + code = (code << 2) | decode_e(e, s, 7); + if(code < 0) { + dbprintf(2, " [invalid end guard]"); + return(-1); + } + } + dbprintf(2, ") s=%d aux=%x", s, code); + return(code); +} + +/* determine possible auxiliary pattern + * using current 4 as possible character + */ +static inline signed char aux_start (zbar_decoder_t *dcode) +{ + /* FIXME NB add-on has no guard in reverse */ + unsigned e1, e2 = get_width(dcode, 5) + get_width(dcode, 6); + unsigned char E1; + if(dcode->ean.s4 < 6) + return(-1); + + if(decode_e(e2, dcode->ean.s4, 7)) { + dbprintf(2, " [invalid any]"); + return(-1); + } + + e1 = get_width(dcode, 4) + get_width(dcode, 5); + E1 = decode_e(e1, dcode->ean.s4, 7); + + if(get_color(dcode) == ZBAR_BAR) { + /* check for quiet-zone */ + unsigned qz = get_width(dcode, 7); + if(!qz || qz > dcode->ean.s4 * 3 / 4) { + if(!E1) { + dbprintf(2, " [valid normal]"); + return(0); /* normal symbol start */ + } + else if(E1 == 1) { + dbprintf(2, " [valid add-on]"); + return(STATE_ADDON); /* add-on symbol start */ + } + } + dbprintf(2, " [invalid start]"); + return(-1); + } + + if(!E1) { + /* attempting decode from SPACE => validate center guard */ + unsigned e3 = get_width(dcode, 6) + get_width(dcode, 7); + unsigned e4 = get_width(dcode, 7) + get_width(dcode, 8); + if(!decode_e(e3, dcode->ean.s4, 7) && + !decode_e(e4, dcode->ean.s4, 7)) { + dbprintf(2, " [valid center]"); + return(0); /* start after center guard */ + } + } + dbprintf(2, " [invalid center]"); + return(-1); +} + +/* check addon delimiter using current 4 as character + */ +static inline signed char aux_mid (zbar_decoder_t *dcode) +{ + unsigned e = get_width(dcode, 4) + get_width(dcode, 5); + return(decode_e(e, dcode->ean.s4, 7)); +} + +/* attempt to decode previous 4 widths (2 bars and 2 spaces) as a character */ +static inline signed char decode4 (zbar_decoder_t *dcode) +{ + signed char code; + + /* calculate similar edge measurements */ + unsigned e1 = ((get_color(dcode) == ZBAR_BAR) + ? get_width(dcode, 0) + get_width(dcode, 1) + : get_width(dcode, 2) + get_width(dcode, 3)); + unsigned e2 = get_width(dcode, 1) + get_width(dcode, 2); + dbprintf(2, "\n e1=%d e2=%d", e1, e2); + + if(dcode->ean.s4 < 6) + return(-1); + + /* create compacted encoding for direct lookup */ + code = ((decode_e(e1, dcode->ean.s4, 7) << 2) | + decode_e(e2, dcode->ean.s4, 7)); + if(code < 0) + return(-1); + dbprintf(2, " code=%x", code); + + /* 4 combinations require additional determinant (D2) + E1E2 == 34 (0110) + E1E2 == 43 (1001) + E1E2 == 33 (0101) + E1E2 == 44 (1010) + */ + if((1 << code) & 0x0660) { + unsigned char mid, alt; + /* use sum of bar widths */ + unsigned d2 = ((get_color(dcode) == ZBAR_BAR) + ? get_width(dcode, 0) + get_width(dcode, 2) + : get_width(dcode, 1) + get_width(dcode, 3)); + d2 *= 7; + mid = (((1 << code) & 0x0420) + ? 3 /* E1E2 in 33,44 */ + : 4); /* E1E2 in 34,43 */ + alt = d2 > (mid * dcode->ean.s4); + if(alt) + code = ((code >> 1) & 3) | 0x10; /* compress code space */ + dbprintf(2, " (d2=%d(%d) alt=%d)", d2, mid * dcode->ean.s4, alt); + } + dbprintf(2, " char=%02x", digits[(unsigned char)code]); + zassert(code < 0x14, -1, "code=%02x e1=%x e2=%x s4=%x color=%x\n", + code, e1, e2, dcode->ean.s4, get_color(dcode)); + return(code); +} + +static inline char ean_part_end2 (ean_decoder_t *ean, + ean_pass_t *pass) +{ + if(!TEST_CFG(ean->ean2_config, ZBAR_CFG_ENABLE)) + return(ZBAR_NONE); + + /* extract parity bits */ + unsigned char par = ((pass->raw[1] & 0x10) >> 3 | + (pass->raw[2] & 0x10) >> 4); + /* calculate "checksum" */ + unsigned char chk = ~((pass->raw[1] & 0xf) * 10 + + (pass->raw[2] & 0xf)) & 0x3; + dbprintf(2, " par=%x chk=%x", par, chk); + if(par != chk) + return(ZBAR_NONE); + + dbprintf(2, "\n"); + dbprintf(1, "decode2=%x%x\n", + pass->raw[1] & 0xf, pass->raw[2] & 0xf); + return(ZBAR_EAN2); +} + +static inline zbar_symbol_type_t ean_part_end4 (ean_pass_t *pass, + unsigned char fwd) +{ + /* extract parity bits */ + unsigned char par = ((pass->raw[1] & 0x10) >> 1 | + (pass->raw[2] & 0x10) >> 2 | + (pass->raw[3] & 0x10) >> 3 | + (pass->raw[4] & 0x10) >> 4); + + dbprintf(2, " par=%x", par); + if(par && par != 0xf) + /* invalid parity combination */ + return(ZBAR_NONE); + + if((!par) == fwd) { + /* reverse sampled digits */ + unsigned char tmp = pass->raw[1]; + pass->state |= STATE_REV; + pass->raw[1] = pass->raw[4]; + pass->raw[4] = tmp; + tmp = pass->raw[2]; + pass->raw[2] = pass->raw[3]; + pass->raw[3] = tmp; + } + + dbprintf(2, "\n"); + dbprintf(1, "decode4=%x%x%x%x\n", + pass->raw[1] & 0xf, pass->raw[2] & 0xf, + pass->raw[3] & 0xf, pass->raw[4] & 0xf); + if(!par) + return(ZBAR_EAN8 | EAN_RIGHT); + return(ZBAR_EAN8 | EAN_LEFT); +} + +static inline char ean_part_end5 (ean_decoder_t *ean, + ean_pass_t *pass) +{ + if(!TEST_CFG(ean->ean5_config, ZBAR_CFG_ENABLE)) + return(ZBAR_NONE); + + /* extract parity bits */ + unsigned char par = ((pass->raw[1] & 0x10) | + (pass->raw[2] & 0x10) >> 1 | + (pass->raw[3] & 0x10) >> 2 | + (pass->raw[4] & 0x10) >> 3 | + (pass->raw[5] & 0x10) >> 4); + /* calculate checksum */ + unsigned char chk = (((pass->raw[1] & 0x0f) + + (pass->raw[2] & 0x0f) * 3 + + (pass->raw[3] & 0x0f) + + (pass->raw[4] & 0x0f) * 3 + + (pass->raw[5] & 0x0f)) * 3) % 10; + + unsigned char parchk = parity_decode[par >> 1]; + if(par & 1) + parchk >>= 4; + parchk &= 0xf; + dbprintf(2, " par=%x(%d) chk=%d", par, parchk, chk); + if(parchk != chk) + return(ZBAR_NONE); + + dbprintf(2, "\n"); + dbprintf(1, "decode5=%x%x%x%x%x\n", + pass->raw[1] & 0xf, pass->raw[2] & 0xf, + pass->raw[3] & 0xf, pass->raw[4] & 0xf, + pass->raw[5] & 0xf); + + return(ZBAR_EAN5); +} + +static inline zbar_symbol_type_t ean_part_end7 (ean_decoder_t *ean, + ean_pass_t *pass, + unsigned char fwd) +{ + /* calculate parity index */ + unsigned char par = ((fwd) + ? ((pass->raw[1] & 0x10) << 1 | + (pass->raw[2] & 0x10) | + (pass->raw[3] & 0x10) >> 1 | + (pass->raw[4] & 0x10) >> 2 | + (pass->raw[5] & 0x10) >> 3 | + (pass->raw[6] & 0x10) >> 4) + : ((pass->raw[1] & 0x10) >> 4 | + (pass->raw[2] & 0x10) >> 3 | + (pass->raw[3] & 0x10) >> 2 | + (pass->raw[4] & 0x10) >> 1 | + (pass->raw[5] & 0x10) | + (pass->raw[6] & 0x10) << 1)); + + /* lookup parity combination */ + pass->raw[0] = parity_decode[par >> 1]; + if(par & 1) + pass->raw[0] >>= 4; + pass->raw[0] &= 0xf; + dbprintf(2, " par=%02x(%x)", par, pass->raw[0]); + + if(pass->raw[0] == 0xf) + /* invalid parity combination */ + return(ZBAR_NONE); + + if((!par) == fwd) { + unsigned char i; + pass->state |= STATE_REV; + /* reverse sampled digits */ + for(i = 1; i < 4; i++) { + unsigned char tmp = pass->raw[i]; + pass->raw[i] = pass->raw[7 - i]; + pass->raw[7 - i] = tmp; + } + } + + dbprintf(2, "\n"); + dbprintf(1, "decode=%x%x%x%x%x%x%x(%02x)\n", + pass->raw[0] & 0xf, pass->raw[1] & 0xf, + pass->raw[2] & 0xf, pass->raw[3] & 0xf, + pass->raw[4] & 0xf, pass->raw[5] & 0xf, + pass->raw[6] & 0xf, par); + + if(TEST_CFG(ean->ean13_config, ZBAR_CFG_ENABLE)) { + if(!par) + return(ZBAR_EAN13 | EAN_RIGHT); + if(par & 0x20) + return(ZBAR_EAN13 | EAN_LEFT); + } + if(par && !(par & 0x20)) + return(ZBAR_UPCE); + + return(ZBAR_NONE); +} + +/* update state for one of 4 parallel passes */ +static inline zbar_symbol_type_t decode_pass (zbar_decoder_t *dcode, + ean_pass_t *pass) +{ + unsigned char idx, fwd; + pass->state++; + idx = pass->state & STATE_IDX; + fwd = pass->state & 1; + + if(get_color(dcode) == ZBAR_SPACE) { + if(pass->state & STATE_ADDON) { + dbprintf(2, " i=%d", idx); + if(idx == 0x09 || idx == 0x21) { + unsigned qz = get_width(dcode, 0); + unsigned s = calc_s(dcode, 1, 4); + zbar_symbol_type_t part = !qz || (qz >= s * 3 / 4); + if(part && idx == 0x09) + part = ean_part_end2(&dcode->ean, pass); + else if(part) + part = ean_part_end5(&dcode->ean, pass); + + if(part || idx == 0x21) { + dcode->ean.direction = 0; + pass->state = -1; + return(part); + } + } + if((idx & 7) == 1) { + dbprintf(2, " +"); + pass->state += 2; + idx += 2; + } + } + else if((idx == 0x10 || idx == 0x11) && + TEST_CFG(dcode->ean.ean8_config, ZBAR_CFG_ENABLE) && + !aux_end(dcode, fwd)) { + dbprintf(2, " fwd=%x", fwd); + zbar_symbol_type_t part = ean_part_end4(pass, fwd); + if(part) + dcode->ean.direction = (pass->state & STATE_REV) != 0; + pass->state = -1; + return(part); + } + else if((idx == 0x18 || idx == 0x19)) { + zbar_symbol_type_t part = ZBAR_NONE; + dbprintf(2, " fwd=%x", fwd); + if(!aux_end(dcode, fwd) && pass->raw[5] != 0xff) + part = ean_part_end7(&dcode->ean, pass, fwd); + if(part) + dcode->ean.direction = (pass->state & STATE_REV) != 0; + pass->state = -1; + return(part); + } + } + + if(pass->state & STATE_ADDON) + idx >>= 1; + + if(!(idx & 0x03) && idx <= 0x14) { + signed char code = -1; + unsigned w = pass->width; + if(!dcode->ean.s4) + return(0); + /* validate guard bars before decoding first char of symbol */ + if(!pass->state) { + pass->state = aux_start(dcode); + pass->width = dcode->ean.s4; + if(pass->state < 0) + return(0); + idx = pass->state & STATE_IDX; + } + else { + w = check_width(w, dcode->ean.s4); + if(w) + pass->width = (pass->width + dcode->ean.s4 * 3) / 4; + } + + if(w) + code = decode4(dcode); + else + dbprintf(2, " [bad width]"); + + if((code < 0 && idx != 0x10) || + (idx > 0 && (pass->state & STATE_ADDON) && aux_mid(dcode))) + pass->state = -1; + else if(code < 0) + pass->raw[5] = 0xff; + else { + dbprintf(2, "\n raw[%x]=%02x =>", idx >> 2, + digits[(unsigned char)code]); + pass->raw[(idx >> 2) + 1] = digits[(unsigned char)code]; + dbprintf(2, " raw=%d%d%d%d%d%d%d", + pass->raw[0] & 0xf, pass->raw[1] & 0xf, + pass->raw[2] & 0xf, pass->raw[3] & 0xf, + pass->raw[4] & 0xf, pass->raw[5] & 0xf, + pass->raw[6] & 0xf); + } + } + return(0); +} + +static inline signed char ean_verify_checksum (ean_decoder_t *ean, + int n) +{ + unsigned char chk = 0; + unsigned char i, d; + for(i = 0; i < n; i++) { + unsigned char d = ean->buf[i]; + zassert(d < 10, -1, "i=%x d=%x chk=%x %s\n", i, d, chk, + _zbar_decoder_buf_dump((void*)ean->buf, 18)); + chk += d; + if((i ^ n) & 1) { + chk += d << 1; + if(chk >= 20) + chk -= 20; + } + if(chk >= 10) + chk -= 10; + } + zassert(chk < 10, -1, "chk=%x n=%x %s", chk, n, + _zbar_decoder_buf_dump((void*)ean->buf, 18)); + if(chk) + chk = 10 - chk; + d = ean->buf[n]; + zassert(d < 10, -1, "n=%x d=%x chk=%x %s\n", n, d, chk, + _zbar_decoder_buf_dump((void*)ean->buf, 18)); + if(chk != d) { + dbprintf(1, "\nchecksum mismatch %d != %d (%s)\n", + chk, d, dsprintbuf(ean)); + return(-1); + } + return(0); +} + +static inline unsigned char isbn10_calc_checksum (ean_decoder_t *ean) +{ + unsigned int chk = 0; + unsigned char w; + for(w = 10; w > 1; w--) { + unsigned char d = ean->buf[13 - w]; + zassert(d < 10, '?', "w=%x d=%x chk=%x %s\n", w, d, chk, + _zbar_decoder_buf_dump((void*)ean->buf, 18)); + chk += d * w; + } + chk = chk % 11; + if(!chk) + return('0'); + chk = 11 - chk; + if(chk < 10) + return(chk + '0'); + return('X'); +} + +static inline void ean_expand_upce (ean_decoder_t *ean, + ean_pass_t *pass) +{ + int i = 0; + unsigned char decode; + /* parity encoded digit is checksum */ + ean->buf[12] = pass->raw[i++]; + + decode = pass->raw[6] & 0xf; + ean->buf[0] = 0; + ean->buf[1] = 0; + ean->buf[2] = pass->raw[i++] & 0xf; + ean->buf[3] = pass->raw[i++] & 0xf; + ean->buf[4] = (decode < 3) ? decode : pass->raw[i++] & 0xf; + ean->buf[5] = (decode < 4) ? 0 : pass->raw[i++] & 0xf; + ean->buf[6] = (decode < 5) ? 0 : pass->raw[i++] & 0xf; + ean->buf[7] = 0; + ean->buf[8] = 0; + ean->buf[9] = (decode < 3) ? pass->raw[i++] & 0xf : 0; + ean->buf[10] = (decode < 4) ? pass->raw[i++] & 0xf : 0; + ean->buf[11] = (decode < 5) ? pass->raw[i] & 0xf : decode; +} + +static inline zbar_symbol_type_t integrate_partial (ean_decoder_t *ean, + ean_pass_t *pass, + zbar_symbol_type_t part) +{ + /* copy raw data into holding buffer */ + /* if same partial is not consistent, reset others */ + dbprintf(2, " integrate part=%x (%s)", part, dsprintbuf(ean)); + signed char i, j; + + if((ean->left && ((part & ZBAR_SYMBOL) != ean->left)) || + (ean->right && ((part & ZBAR_SYMBOL) != ean->right))) { + /* partial mismatch - reset collected parts */ + dbprintf(2, " rst(type %x %x)", ean->left, ean->right); + ean->left = ean->right = ZBAR_NONE; + } + + if((ean->left || ean->right) && + !check_width(ean->width, pass->width)) { + dbprintf(2, " rst(width %d)", pass->width); + ean->left = ean->right = ZBAR_NONE; + } + + + if(part & EAN_RIGHT) { + part &= ZBAR_SYMBOL; + j = part - 1; + for(i = part >> 1; i; i--, j--) { + unsigned char digit = pass->raw[i] & 0xf; + if(ean->right && ean->buf[j] != digit) { + /* partial mismatch - reset collected parts */ + dbprintf(2, " rst(right)"); + ean->left = ean->right = ZBAR_NONE; + } + ean->buf[j] = digit; + } + ean->right = part; + part &= ean->left; /* FIXME!? */ + } + else if(part == ZBAR_EAN13 || part == ZBAR_EAN8) /* EAN_LEFT */ { + j = (part - 1) >> 1; + for(i = part >> 1; j >= 0; i--, j--) { + unsigned char digit = pass->raw[i] & 0xf; + if(ean->left && ean->buf[j] != digit) { + /* partial mismatch - reset collected parts */ + dbprintf(2, " rst(left)"); + ean->left = ean->right = ZBAR_NONE; + } + ean->buf[j] = digit; + } + ean->left = part; + part &= ean->right; /* FIXME!? */ + } + else if(part != ZBAR_UPCE) /* add-ons */ { + for(i = part; i > 0; i--) + ean->buf[i - 1] = pass->raw[i] & 0xf; + ean->left = part; + } + else + ean_expand_upce(ean, pass); + + ean->width = pass->width; + + if(!part) + part = ZBAR_PARTIAL; + + if(((part == ZBAR_EAN13 || + part == ZBAR_UPCE) && ean_verify_checksum(ean, 12)) || + (part == ZBAR_EAN8 && ean_verify_checksum(ean, 7))) { + /* invalid checksum */ + if(ean->right) + ean->left = ZBAR_NONE; + else + ean->right = ZBAR_NONE; + part = ZBAR_NONE; + } + + if(part == ZBAR_EAN13) { + /* special case EAN-13 subsets */ + if(!ean->buf[0] && TEST_CFG(ean->upca_config, ZBAR_CFG_ENABLE)) + part = ZBAR_UPCA; + else if(ean->buf[0] == 9 && ean->buf[1] == 7) { + /* ISBN-10 has priority over ISBN-13(?) */ + if(ean->buf[2] == 8 && + TEST_CFG(ean->isbn10_config, ZBAR_CFG_ENABLE)) + part = ZBAR_ISBN10; + else if((ean->buf[2] == 8 || ean->buf[2] == 9) && + TEST_CFG(ean->isbn13_config, ZBAR_CFG_ENABLE)) + part = ZBAR_ISBN13; + } + } + else if(part == ZBAR_UPCE) { + if(TEST_CFG(ean->upce_config, ZBAR_CFG_ENABLE)) { + /* UPC-E was decompressed for checksum verification, + * but user requested compressed result + */ + ean->buf[0] = ean->buf[1] = 0; + for(i = 2; i < 8; i++) + ean->buf[i] = pass->raw[i - 1] & 0xf; + ean->buf[i] = pass->raw[0] & 0xf; + } + else if(TEST_CFG(ean->upca_config, ZBAR_CFG_ENABLE)) + /* UPC-E reported as UPC-A has priority over EAN-13 */ + part = ZBAR_UPCA; + else if(TEST_CFG(ean->ean13_config, ZBAR_CFG_ENABLE)) + part = ZBAR_EAN13; + else + part = ZBAR_NONE; + } + + dbprintf(2, " dir=%d %x/%x=%x", + ean->direction, ean->left, ean->right, part); + return(part); +} + +/* copy result to output buffer */ +static inline void ean_postprocess (zbar_decoder_t *dcode, + zbar_symbol_type_t sym) +{ + ean_decoder_t *ean = &dcode->ean; + zbar_symbol_type_t base = sym; + int i = 0, j = 0; + if(base > ZBAR_PARTIAL) { + if(base == ZBAR_UPCA) + i = 1; + else if(base == ZBAR_UPCE) { + i = 1; + base--; + } + else if(base == ZBAR_ISBN13) + base = ZBAR_EAN13; + else if(base == ZBAR_ISBN10) + i = 3; + + if(base == ZBAR_ISBN10 || + (base > ZBAR_EAN5 && + !TEST_CFG(ean_get_config(ean, sym), ZBAR_CFG_EMIT_CHECK))) + base--; + + for(; j < base && ean->buf[i] >= 0; i++, j++) + dcode->buf[j] = ean->buf[i] + '0'; + + if(sym == ZBAR_ISBN10 && j == 9 && + TEST_CFG(ean->isbn10_config, ZBAR_CFG_EMIT_CHECK)) + /* recalculate ISBN-10 check digit */ + dcode->buf[j++] = isbn10_calc_checksum(ean); + } + dcode->buflen = j; + dcode->buf[j] = '\0'; + dcode->direction = 1 - 2 * ean->direction; + dcode->modifiers = 0; + dbprintf(2, " base=%d j=%d (%s)", base, j, dcode->buf); +} + +zbar_symbol_type_t _zbar_decode_ean (zbar_decoder_t *dcode) +{ + /* process upto 4 separate passes */ + zbar_symbol_type_t sym = ZBAR_NONE; + unsigned char pass_idx = dcode->idx & 3; + unsigned char i; + + /* update latest character width */ + dcode->ean.s4 -= get_width(dcode, 4); + dcode->ean.s4 += get_width(dcode, 0); + + for(i = 0; i < 4; i++) { + ean_pass_t *pass = &dcode->ean.pass[i]; + if(pass->state >= 0 || + i == pass_idx) + { + zbar_symbol_type_t part; + dbprintf(2, " ean[%x/%x]: idx=%x st=%d s=%d", + i, pass_idx, dcode->idx, pass->state, dcode->ean.s4); + part = decode_pass(dcode, pass); + if(part) { + /* update accumulated data from new partial decode */ + sym = integrate_partial(&dcode->ean, pass, part); + if(sym) { + /* this pass valid => _reset_ all passes */ + dbprintf(2, " sym=%x", sym); + dcode->ean.pass[0].state = dcode->ean.pass[1].state = -1; + dcode->ean.pass[2].state = dcode->ean.pass[3].state = -1; + if(sym > ZBAR_PARTIAL) { + if(!acquire_lock(dcode, sym)) + ean_postprocess(dcode, sym); + else { + dbprintf(1, " [locked %d]", dcode->lock); + sym = ZBAR_PARTIAL; + } + } + } + } + dbprintf(2, "\n"); + } + } + return(sym); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "i25.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2008-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +static inline unsigned char i25_decode1 (unsigned char enc, + unsigned e, + unsigned s) +{ + unsigned char E = decode_e(e, s, 45); + if(E > 7) + return(0xff); + enc <<= 1; + if(E > 2) + enc |= 1; + return(enc); +} + +static inline unsigned char i25_decode10 (zbar_decoder_t *dcode, + unsigned char offset) +{ + i25_decoder_t *dcode25 = &dcode->i25; + dbprintf(2, " s=%d", dcode25->s10); + if(dcode25->s10 < 10) + return(0xff); + + /* threshold bar width ratios */ + unsigned char enc = 0, par = 0; + signed char i; + for(i = 8; i >= 0; i -= 2) { + unsigned char j = offset + ((dcode25->direction) ? i : 8 - i); + enc = i25_decode1(enc, get_width(dcode, j), dcode25->s10); + if(enc == 0xff) + return(0xff); + if(enc & 1) + par++; + } + + dbprintf(2, " enc=%02x par=%x", enc, par); + + /* parity check */ + if(par != 2) { + dbprintf(2, " [bad parity]"); + return(0xff); + } + + /* decode binary weights */ + enc &= 0xf; + if(enc & 8) { + if(enc == 12) + enc = 0; + else if(--enc > 9) { + dbprintf(2, " [invalid encoding]"); + return(0xff); + } + } + + dbprintf(2, " => %x", enc); + return(enc); +} + +static inline signed char i25_decode_start (zbar_decoder_t *dcode) +{ + i25_decoder_t *dcode25 = &dcode->i25; + if(dcode25->s10 < 10) + return(ZBAR_NONE); + + unsigned char enc = 0; + unsigned char i = 10; + enc = i25_decode1(enc, get_width(dcode, i++), dcode25->s10); + enc = i25_decode1(enc, get_width(dcode, i++), dcode25->s10); + enc = i25_decode1(enc, get_width(dcode, i++), dcode25->s10); + + if((get_color(dcode) == ZBAR_BAR) + ? enc != 4 + : (enc = i25_decode1(enc, get_width(dcode, i++), dcode25->s10))) { + dbprintf(4, " i25: s=%d enc=%x [invalid]\n", dcode25->s10, enc); + return(ZBAR_NONE); + } + + /* check leading quiet zone - spec is 10n(?) + * we require 5.25n for w=2n to 6.75n for w=3n + * (FIXME should really factor in w:n ratio) + */ + unsigned quiet = get_width(dcode, i); + if(quiet && quiet < dcode25->s10 * 3 / 8) { + dbprintf(3, " i25: s=%d enc=%x q=%d [invalid qz]\n", + dcode25->s10, enc, quiet); + return(ZBAR_NONE); + } + + dcode25->direction = get_color(dcode); + dcode25->element = 1; + dcode25->character = 0; + return(ZBAR_PARTIAL); +} + +static inline int i25_acquire_lock (zbar_decoder_t *dcode) +{ + int i; + /* lock shared resources */ + if(acquire_lock(dcode, ZBAR_I25)) { + dcode->i25.character = -1; + return(1); + } + + /* copy holding buffer */ + for(i = 4; --i >= 0; ) + dcode->buf[i] = dcode->i25.buf[i]; + return(0); +} + +static inline signed char i25_decode_end (zbar_decoder_t *dcode) +{ + i25_decoder_t *dcode25 = &dcode->i25; + + /* check trailing quiet zone */ + unsigned quiet = get_width(dcode, 0); + if((quiet && quiet < dcode25->width * 3 / 8) || + decode_e(get_width(dcode, 1), dcode25->width, 45) > 2 || + decode_e(get_width(dcode, 2), dcode25->width, 45) > 2) { + dbprintf(3, " i25: s=%d q=%d [invalid qz]\n", + dcode25->width, quiet); + return(ZBAR_NONE); + } + + /* check exit condition */ + unsigned char E = decode_e(get_width(dcode, 3), dcode25->width, 45); + if((!dcode25->direction) + ? E - 3 > 4 + : (E > 2 || + decode_e(get_width(dcode, 4), dcode25->width, 45) > 2)) + return(ZBAR_NONE); + + if(dcode25->character <= 4 && + i25_acquire_lock(dcode)) + return(ZBAR_PARTIAL); + + dcode->direction = 1 - 2 * dcode25->direction; + if(dcode25->direction) { + /* reverse buffer */ + dbprintf(2, " (rev)"); + int i; + for(i = 0; i < dcode25->character / 2; i++) { + unsigned j = dcode25->character - 1 - i; + char c = dcode->buf[i]; + dcode->buf[i] = dcode->buf[j]; + dcode->buf[j] = c; + } + } + + if(dcode25->character < CFG(*dcode25, ZBAR_CFG_MIN_LEN) || + (CFG(*dcode25, ZBAR_CFG_MAX_LEN) > 0 && + dcode25->character > CFG(*dcode25, ZBAR_CFG_MAX_LEN))) { + dbprintf(2, " [invalid len]\n"); + release_lock(dcode, ZBAR_I25); + dcode25->character = -1; + return(ZBAR_NONE); + } + + zassert(dcode25->character < dcode->buf_alloc, ZBAR_NONE, "i=%02x %s\n", + dcode25->character, + _zbar_decoder_buf_dump(dcode->buf, dcode25->character)); + dcode->buflen = dcode25->character; + dcode->buf[dcode25->character] = '\0'; + dcode->modifiers = 0; + dbprintf(2, " [valid end]\n"); + dcode25->character = -1; + return(ZBAR_I25); +} + +zbar_symbol_type_t _zbar_decode_i25 (zbar_decoder_t *dcode) +{ + i25_decoder_t *dcode25 = &dcode->i25; + + /* update latest character width */ + dcode25->s10 -= get_width(dcode, 10); + dcode25->s10 += get_width(dcode, 0); + + if(dcode25->character < 0 && + !i25_decode_start(dcode)) + return(ZBAR_NONE); + + if(--dcode25->element == 6 - dcode25->direction) + return(i25_decode_end(dcode)); + else if(dcode25->element) + return(ZBAR_NONE); + + /* FIXME check current character width against previous */ + dcode25->width = dcode25->s10; + + dbprintf(2, " i25[%c%02d+%x]", + (dcode25->direction) ? '<' : '>', + dcode25->character, dcode25->element); + + if(dcode25->character == 4 && i25_acquire_lock(dcode)) + return(ZBAR_PARTIAL); + + unsigned char c = i25_decode10(dcode, 1); + dbprintf(2, " c=%x", c); + if(c > 9) { + dbprintf(2, " [aborted]\n"); + goto reset; + } + + if(size_buf(dcode, dcode25->character + 3)) { + dbprintf(2, " [overflow]\n"); + goto reset; + } + + unsigned char *buf; + if(dcode25->character >= 4) + buf = dcode->buf; + else + buf = dcode25->buf; + buf[dcode25->character++] = c + '0'; + + c = i25_decode10(dcode, 0); + dbprintf(2, " c=%x", c); + if(c > 9) { + dbprintf(2, " [aborted]\n"); + goto reset; + } + else + dbprintf(2, "\n"); + + buf[dcode25->character++] = c + '0'; + dcode25->element = 10; + return((dcode25->character == 2) ? ZBAR_PARTIAL : ZBAR_NONE); + +reset: + if(dcode25->character >= 4) + release_lock(dcode, ZBAR_I25); + dcode25->character = -1; + return(ZBAR_NONE); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "codabar.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2011 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +#define NIBUF 6 /* initial scan buffer size */ + +static const signed char codabar_lo[12] = { + 0x0, 0x1, 0x4, 0x5, 0x2, 0xa, 0xb, 0x9, + 0x6, 0x7, 0x8, 0x3 +}; + +static const unsigned char codabar_hi[8] = { + 0x1, 0x4, 0x7, 0x6, 0x2, 0x3, 0x0, 0x5 +}; + +static const unsigned char codabar_characters[20] = + "0123456789-$:/.+ABCD"; + +static inline int +codabar_check_width (unsigned ref, + unsigned w) +{ + unsigned dref = ref; + ref *= 4; + w *= 4; + return(ref - dref <= w && w <= ref + dref); +} + +static inline signed char +codabar_decode7 (zbar_decoder_t *dcode) +{ + codabar_decoder_t *codabar = &dcode->codabar; + unsigned s = codabar->s7; + dbprintf(2, " s=%d", s); + if(s < 7) + return(-1); + + /* check width */ + if(!codabar_check_width(codabar->width, s)) { + dbprintf(2, " [width]"); + return(-1); + } + + /* extract min/max bar */ + unsigned ibar = decode_sortn(dcode, 4, 1); + dbprintf(2, " bar=%04x", ibar); + + unsigned wbmax = get_width(dcode, ibar & 0xf); + unsigned wbmin = get_width(dcode, ibar >> 12); + if(8 * wbmin < wbmax || + 3 * wbmin > 2 * wbmax) + { + dbprintf(2, " [bar outer ratio]"); + return(-1); + } + + unsigned wb1 = get_width(dcode, (ibar >> 8) & 0xf); + unsigned wb2 = get_width(dcode, (ibar >> 4) & 0xf); + unsigned long b0b3 = wbmin * wbmax; + unsigned long b1b2 = wb1 * wb2; + if(b1b2 + b1b2 / 8 < b0b3) { + /* single wide bar combinations */ + if(8 * wbmin < 5 * wb1 || + 8 * wb1 < 5 * wb2 || + 4 * wb2 > 3 * wbmax || + wb2 * wb2 >= wb1 * wbmax) + { + dbprintf(2, " [1bar inner ratios]"); + return(-1); + } + ibar = (ibar >> 1) & 0x3; + } + else if(b1b2 > b0b3 + b0b3 / 8) { + /* three wide bars, no wide spaces */ + if(4 * wbmin > 3 * wb1 || + 8 * wb1 < 5 * wb2 || + 8 * wb2 < 5 * wbmax || + wbmin * wb2 >= wb1 * wb1) + { + dbprintf(2, " [3bar inner ratios]"); + return(-1); + } + ibar = (ibar >> 13) + 4; + } + else { + dbprintf(2, " [bar inner ratios]"); + return(-1); + } + + unsigned ispc = decode_sort3(dcode, 2); + dbprintf(2, "(%x) spc=%03x", ibar, ispc); + + unsigned wsmax = get_width(dcode, ispc & 0xf); + unsigned wsmid = get_width(dcode, (ispc >> 4) & 0xf); + unsigned wsmin = get_width(dcode, (ispc >> 8) & 0xf); + if(ibar >> 2) { + /* verify no wide spaces */ + if(8 * wsmin < wsmax || + 8 * wsmin < 5 * wsmid || + 8 * wsmid < 5 * wsmax) + { + dbprintf(2, " [0space inner ratios]"); + return(-1); + } + ibar &= 0x3; + if(codabar->direction) + ibar = 3 - ibar; + int c = (0xfcde >> (ibar << 2)) & 0xf; + dbprintf(2, " ex[%d]=%x", ibar, c); + return(c); + } + else if(8 * wsmin < wsmax || + 3 * wsmin > 2 * wsmax) + { + dbprintf(2, " [space outer ratio]"); + return(-1); + } + + unsigned long s0s2 = wsmin * wsmax; + unsigned long s1s1 = wsmid * wsmid; + if(s1s1 + s1s1 / 8 < s0s2) { + /* single wide space */ + if(8 * wsmin < 5 * wsmid || + 4 * wsmid > 3 * wsmax) + { + dbprintf(2, " [1space inner ratios]"); + return(-1); + } + ispc = ((ispc & 0xf) >> 1) - 1; + unsigned ic = (ispc << 2) | ibar; + if(codabar->direction) + ic = 11 - ic; + int c = codabar_lo[ic]; + dbprintf(2, "(%d) lo[%d]=%x", ispc, ic, c); + return(c); + } + else if(s1s1 > s0s2 + s0s2 / 8) { + /* two wide spaces, check start/stop */ + if(4 * wsmin > 3 * wsmid || + 8 * wsmid < 5 * wsmax) + { + dbprintf(2, " [2space inner ratios]"); + return(-1); + } + if((ispc >> 8) == 4) { + dbprintf(2, " [space comb]"); + return(-1); + } + ispc >>= 10; + dbprintf(2, "(%d)", ispc); + unsigned ic = ispc * 4 + ibar; + zassert(ic < 8, -1, "ic=%d ispc=%d ibar=%d", ic, ispc, ibar); + unsigned char c = codabar_hi[ic]; + if(c >> 2 != codabar->direction) { + dbprintf(2, " [invalid stop]"); + return(-1); + } + c = (c & 0x3) | 0x10; + dbprintf(2, " hi[%d]=%x", ic, c); + return(c); + } + else { + dbprintf(2, " [space inner ratios]"); + return(-1); + } +} + +static inline signed char +codabar_decode_start (zbar_decoder_t *dcode) +{ + codabar_decoder_t *codabar = &dcode->codabar; + unsigned s = codabar->s7; + if(s < 8) + return(ZBAR_NONE); + dbprintf(2, " codabar: s=%d", s); + + /* check leading quiet zone - spec is 10x */ + unsigned qz = get_width(dcode, 8); + if((qz && qz * 2 < s) || + 4 * get_width(dcode, 0) > 3 * s) + { + dbprintf(2, " [invalid qz/ics]\n"); + return(ZBAR_NONE); + } + + /* check space ratios first */ + unsigned ispc = decode_sort3(dcode, 2); + dbprintf(2, " spc=%03x", ispc); + if((ispc >> 8) == 4) { + dbprintf(2, " [space comb]\n"); + return(ZBAR_NONE); + } + + /* require 2 wide and 1 narrow spaces */ + unsigned wsmax = get_width(dcode, ispc & 0xf); + unsigned wsmin = get_width(dcode, ispc >> 8); + unsigned wsmid = get_width(dcode, (ispc >> 4) & 0xf); + if(8 * wsmin < wsmax || + 3 * wsmin > 2 * wsmax || + 4 * wsmin > 3 * wsmid || + 8 * wsmid < 5 * wsmax || + wsmid * wsmid <= wsmax * wsmin) + { + dbprintf(2, " [space ratio]\n"); + return(ZBAR_NONE); + } + ispc >>= 10; + dbprintf(2, "(%d)", ispc); + + /* check bar ratios */ + unsigned ibar = decode_sortn(dcode, 4, 1); + dbprintf(2, " bar=%04x", ibar); + + unsigned wbmax = get_width(dcode, ibar & 0xf); + unsigned wbmin = get_width(dcode, ibar >> 12); + if(8 * wbmin < wbmax || + 3 * wbmin > 2 * wbmax) + { + dbprintf(2, " [bar outer ratio]\n"); + return(ZBAR_NONE); + } + + /* require 1 wide & 3 narrow bars */ + unsigned wb1 = get_width(dcode, (ibar >> 8) & 0xf); + unsigned wb2 = get_width(dcode, (ibar >> 4) & 0xf); + if(8 * wbmin < 5 * wb1 || + 8 * wb1 < 5 * wb2 || + 4 * wb2 > 3 * wbmax || + wb1 * wb2 >= wbmin * wbmax || + wb2 * wb2 >= wb1 * wbmax) + { + dbprintf(2, " [bar inner ratios]\n"); + return(ZBAR_NONE); + } + ibar = ((ibar & 0xf) - 1) >> 1; + dbprintf(2, "(%d)", ibar); + + /* decode combination */ + int ic = ispc * 4 + ibar; + zassert(ic < 8, ZBAR_NONE, "ic=%d ispc=%d ibar=%d", ic, ispc, ibar); + int c = codabar_hi[ic]; + codabar->buf[0] = (c & 0x3) | 0x10; + + /* set character direction */ + codabar->direction = c >> 2; + + codabar->element = 4; + codabar->character = 1; + codabar->width = codabar->s7; + dbprintf(1, " start=%c dir=%x [valid start]\n", + codabar->buf[0] + 0x31, codabar->direction); + return(ZBAR_PARTIAL); +} + +static inline int +codabar_checksum (zbar_decoder_t *dcode, + unsigned n) +{ + unsigned chk = 0; + unsigned char *buf = dcode->buf; + while(n--) + chk += *(buf++); + return(!!(chk & 0xf)); +} + +static inline zbar_symbol_type_t +codabar_postprocess (zbar_decoder_t *dcode) +{ + codabar_decoder_t *codabar = &dcode->codabar; + int dir = codabar->direction; + dcode->direction = 1 - 2 * dir; + int i, n = codabar->character; + for(i = 0; i < NIBUF; i++) + dcode->buf[i] = codabar->buf[i]; + if(dir) + /* reverse buffer */ + for(i = 0; i < n / 2; i++) { + unsigned j = n - 1 - i; + char code = dcode->buf[i]; + dcode->buf[i] = dcode->buf[j]; + dcode->buf[j] = code; + } + + if(TEST_CFG(codabar->config, ZBAR_CFG_ADD_CHECK)) { + /* validate checksum */ + if(codabar_checksum(dcode, n)) + return(ZBAR_NONE); + if(!TEST_CFG(codabar->config, ZBAR_CFG_EMIT_CHECK)) { + dcode->buf[n - 2] = dcode->buf[n - 1]; + n--; + } + } + + for(i = 0; i < n; i++) { + unsigned c = dcode->buf[i]; + dcode->buf[i] = ((c < 0x14) + ? codabar_characters[c] + : '?'); + } + dcode->buflen = i; + dcode->buf[i] = '\0'; + dcode->modifiers = 0; + + codabar->character = -1; + return(ZBAR_CODABAR); +} + +zbar_symbol_type_t +_zbar_decode_codabar (zbar_decoder_t *dcode) +{ + codabar_decoder_t *codabar = &dcode->codabar; + + /* update latest character width */ + codabar->s7 -= get_width(dcode, 8); + codabar->s7 += get_width(dcode, 1); + + if(get_color(dcode) != ZBAR_SPACE) + return(ZBAR_NONE); + if(codabar->character < 0) + return(codabar_decode_start(dcode)); + if(codabar->character < 2 && + codabar_decode_start(dcode)) + return(ZBAR_PARTIAL); + if(--codabar->element) + return(ZBAR_NONE); + codabar->element = 4; + + dbprintf(1, " codabar[%c%02d+%x]", + (codabar->direction) ? '<' : '>', + codabar->character, codabar->element); + + signed char c = codabar_decode7(dcode); + dbprintf(1, " %d", c); + if(c < 0) { + dbprintf(1, " [aborted]\n"); + goto reset; + } + + unsigned char *buf; + if(codabar->character < NIBUF) + buf = codabar->buf; + else { + if(codabar->character >= BUFFER_MIN && + size_buf(dcode, codabar->character + 1)) + { + dbprintf(1, " [overflow]\n"); + goto reset; + } + buf = dcode->buf; + } + buf[codabar->character++] = c; + + /* lock shared resources */ + if(codabar->character == NIBUF && + acquire_lock(dcode, ZBAR_CODABAR)) + { + codabar->character = -1; + return(ZBAR_PARTIAL); + } + + unsigned s = codabar->s7; + if(c & 0x10) { + unsigned qz = get_width(dcode, 0); + if(qz && qz * 2 < s) { + dbprintf(2, " [invalid qz]\n"); + goto reset; + } + unsigned n = codabar->character; + if(n < CFG(*codabar, ZBAR_CFG_MIN_LEN) || + (CFG(*codabar, ZBAR_CFG_MAX_LEN) > 0 && + n > CFG(*codabar, ZBAR_CFG_MAX_LEN))) + { + dbprintf(2, " [invalid len]\n"); + goto reset; + } + if(codabar->character < NIBUF && + acquire_lock(dcode, ZBAR_CODABAR)) + { + codabar->character = -1; + return(ZBAR_PARTIAL); + } + dbprintf(2, " stop=%c", c + 0x31); + + zbar_symbol_type_t sym = codabar_postprocess(dcode); + if(sym > ZBAR_PARTIAL) + dbprintf(2, " [valid stop]"); + else { + release_lock(dcode, ZBAR_CODABAR); + codabar->character = -1; + } + dbprintf(2, "\n"); + return(sym); + } + else if(4 * get_width(dcode, 0) > 3 * s) { + dbprintf(2, " [ics]\n"); + goto reset; + } + + dbprintf(2, "\n"); + return(ZBAR_NONE); + +reset: + if(codabar->character >= NIBUF) + release_lock(dcode, ZBAR_CODABAR); + codabar->character = -1; + return(ZBAR_NONE); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "databar.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +#define GS ('\035') + +enum { SCH_NUM, SCH_ALNUM, SCH_ISO646 }; + +static const signed char finder_hash[0x20] = { + 0x16, 0x1f, 0x02, 0x00, 0x03, 0x00, 0x06, 0x0b, + 0x1f, 0x0e, 0x17, 0x0c, 0x0b, 0x14, 0x11, 0x0c, + 0x1f, 0x03, 0x13, 0x08, 0x00, 0x0a, -1, 0x16, + 0x0c, 0x09, -1, 0x1a, 0x1f, 0x1c, 0x00, -1, +}; + +/* DataBar character encoding groups */ +struct group_s { + unsigned short sum; + unsigned char wmax; + unsigned char todd; + unsigned char teven; +} groups[] = { + /* (17,4) DataBar Expanded character groups */ + { 0, 7, 87, 4 }, + { 348, 5, 52, 20 }, + { 1388, 4, 30, 52 }, + { 2948, 3, 10, 104 }, + { 3988, 1, 1, 204 }, + + /* (16,4) DataBar outer character groups */ + { 0, 8, 161, 1 }, + { 161, 6, 80, 10 }, + { 961, 4, 31, 34 }, + { 2015, 3, 10, 70 }, + { 2715, 1, 1, 126 }, + + /* (15,4) DataBar inner character groups */ + { 1516, 8, 81, 1 }, + { 1036, 6, 48, 10 }, + { 336, 4, 20, 35 }, + { 0, 2, 4, 84 }, +}; + +static const unsigned char exp_sequences[] = { + /* sequence Group 1 */ + 0x01, + 0x23, + 0x25, 0x07, + 0x29, 0x47, + 0x29, 0x67, 0x0b, + 0x29, 0x87, 0xab, + /* sequence Group 2 */ + 0x21, 0x43, 0x65, 0x07, + 0x21, 0x43, 0x65, 0x89, + 0x21, 0x43, 0x65, 0xa9, 0x0b, + 0x21, 0x43, 0x67, 0x89, 0xab +}; + +/* DataBar expanded checksum multipliers */ +static const unsigned char exp_checksums[] = { + 1, 189, 62, 113, 46, 43, 109, 134, 6, 79, 161, 45 +}; + +static inline void +append_check14 (unsigned char *buf) +{ + unsigned char chk = 0, d; + int i; + for(i = 13; --i >= 0; ) { + d = *(buf++) - '0'; + chk += d; + if(!(i & 1)) + chk += d << 1; + } + chk %= 10; + if(chk) + chk = 10 - chk; + *buf = chk + '0'; +} + +static inline void +decode10 (unsigned char *buf, + unsigned long n, + int i) +{ + buf += i; + while(--i >= 0) { + unsigned char d = n % 10; + n /= 10; + *--buf = '0' + d; + } +} + +#define VAR_MAX(l, i) ((((l) * 12 + (i)) * 2 + 6) / 7) + +#define FEED_BITS(b) \ + while(i < (b) && len) { \ + d = (d << 12) | (*(data++) & 0xfff); \ + i += 12; \ + len--; \ + dbprintf(2, " %03lx", d & 0xfff); \ + } + +#define PUSH_CHAR(c) \ + *(buf++) = (c) + +#define PUSH_CHAR4(c0, c1, c2, c3) do { \ + PUSH_CHAR(c0); \ + PUSH_CHAR(c1); \ + PUSH_CHAR(c2); \ + PUSH_CHAR(c3); \ + } while(0); + +static inline int +databar_postprocess_exp (zbar_decoder_t *dcode, + int *data) +{ + int i = 0, enc; + unsigned n; + unsigned char *buf; + unsigned long d = *(data++); + int len = d / 211 + 4, buflen; + + /* grok encodation method */ + d = *(data++); + dbprintf(2, "\n len=%d %03lx", len, d & 0xfff); + n = (d >> 4) & 0x7f; + if(n >= 0x40) { + i = 10; + enc = 1; + buflen = 2 + 14 + VAR_MAX(len, 10 - 2 - 44 + 6) + 2; + } + else if(n >= 0x38) { + i = 4; + enc = 6 + (n & 7); + buflen = 2 + 14 + 4 + 6 + 2 + 6 + 2; + } + else if(n >= 0x30) { + i = 6; + enc = 2 + ((n >> 2) & 1); + buflen = 2 + 14 + 4 + 3 + VAR_MAX(len, 6 - 2 - 44 - 2 - 10) + 2; + } + else if(n >= 0x20) { + i = 7; + enc = 4 + ((n >> 3) & 1); + buflen = 2 + 14 + 4 + 6; + } + else { + i = 9; + enc = 0; + buflen = VAR_MAX(len, 9 - 2) + 2; + } + dbprintf(2, " buflen=%d enc=%d", buflen, enc); + zassert(buflen > 2, -1, "buflen=%d\n", buflen); + + if(enc < 4) { + /* grok variable length symbol bit field */ + if((len ^ (d >> (--i))) & 1) + /* even/odd length mismatch */ + return(-1); + if(((d >> (--i)) & 1) != (len > 14)) + /* size group mismatch */ + return(-1); + } + len -= 2; + dbprintf(2, " [%d+%d]", i, len); + + if(size_buf(dcode, buflen)) + return(-1); + buf = dcode->buf; + + /* handle compressed fields */ + if(enc) { + PUSH_CHAR('0'); + PUSH_CHAR('1'); + } + + if(enc == 1) { + i -= 4; + n = (d >> i) & 0xf; + if(i >= 10) + return(-1); + PUSH_CHAR('0' + n); + } + else if(enc) + PUSH_CHAR('9'); + + if(enc) { + int j; + for(j = 0; j < 4; j++) { + FEED_BITS(10); + i -= 10; + n = (d >> i) & 0x3ff; + if(n >= 1000) + return(-1); + decode10(buf, n, 3); + buf += 3; + } + append_check14(buf - 13); + buf++; + } + + switch(enc) + { + case 2: /* 01100: AI 392x */ + FEED_BITS(2); + i -= 2; + n = (d >> i) & 0x3; + PUSH_CHAR4('3', '9', '2', '0' + n); + break; + + case 3: /* 01101: AI 393x */ + FEED_BITS(12); + i -= 2; + n = (d >> i) & 0x3; + PUSH_CHAR4('3', '9', '3', '0' + n); + i -= 10; + n = (d >> i) & 0x3ff; + if(n >= 1000) + return(-1); + decode10(buf, n, 3); + buf += 3; + break; + + case 4: /* 0100: AI 3103 */ + FEED_BITS(15); + i -= 15; + n = (d >> i) & 0x7fff; + PUSH_CHAR4('3', '1', '0', '3'); + decode10(buf, n, 6); + buf += 6; + break; + + case 5: /* 0101: AI 3202/3203 */ + FEED_BITS(15); + i -= 15; + n = (d >> i) & 0x7fff; + dbprintf(2, " v=%d", n); + PUSH_CHAR4('3', '2', '0', (n >= 10000) ? '3' : '2' ); + if(n >= 10000) + n -= 10000; + decode10(buf, n, 6); + buf += 6; + break; + } + if(enc >= 6) { + /* 0111000 - 0111111: AI 310x/320x + AI 11/13/15/17 */ + PUSH_CHAR4('3', '1' + (enc & 1), '0', 'x'); + FEED_BITS(20); + i -= 20; + n = (d >> i) & 0xfffff; + dbprintf(2, " [%d+%d] %d", i, len, n); + if(n >= 1000000) + return(-1); + decode10(buf, n, 6); + *(buf - 1) = *buf; + *buf = '0'; + buf += 6; + + FEED_BITS(16); + i -= 16; + n = (d >> i) & 0xffff; + if(n < 38400) { + int dd, mm, yy; + dd = n % 32; + n /= 32; + mm = n % 12 + 1; + n /= 12; + yy = n; + PUSH_CHAR('1'); + PUSH_CHAR('0' + ((enc - 6) | 1)); + decode10(buf, yy, 2); + buf += 2; + decode10(buf, mm, 2); + buf += 2; + decode10(buf, dd, 2); + buf += 2; + } + else if(n > 38400) + return(-1); + } + + if(enc < 4) { + /* remainder is general-purpose data compaction */ + int scheme = SCH_NUM; + while(i > 0 || len > 0) { + FEED_BITS(8); + dbprintf(2, " [%d+%d]", i, len); + + if(scheme == SCH_NUM) { + int n1; + i -= 4; + if(i < 0) + break; + if(!((d >> i) & 0xf)) { + scheme = SCH_ALNUM; + dbprintf(2, ">A"); + continue; + } + if(!len && i < 3) { + /* special case last digit */ + n = ((d >> i) & 0xf) - 1; + if(n > 9) + return(-1); + *(buf++) = '0' + n; + break; + } + i -= 3; + zassert(i >= 0, -1, "\n"); + n = ((d >> i) & 0x7f) - 8; + n1 = n % 11; + n = n / 11; + dbprintf(2, "N%d%d", n, n1); + *(buf++) = (n < 10) ? '0' + n : GS; + *(buf++) = (n1 < 10) ? '0' + n1 : GS; + } + else { + unsigned c = 0; + i -= 3; + if(i < 0) + break; + if(!((d >> i) & 0x7)) { + scheme = SCH_NUM; + continue; + } + i -= 2; + if(i < 0) + break; + n = (d >> i) & 0x1f; + if(n == 0x04) { + scheme ^= 0x3; + dbprintf(2, ">%d", scheme); + } + else if(n == 0x0f) + c = GS; + else if(n < 0x0f) + c = 43 + n; + else if(scheme == SCH_ALNUM) { + i--; + if(i < 0) + return(-1); + n = (d >> i) & 0x1f; + if(n < 0x1a) + c = 'A' + n; + else if(n == 0x1a) + c = '*'; + else if(n < 0x1f) + c = ',' + n - 0x1b; + else + return(-1); + } + else if(scheme == SCH_ISO646 && n < 0x1d) { + i -= 2; + if(i < 0) + return(-1); + n = (d >> i) & 0x3f; + if(n < 0x1a) + c = 'A' + n; + else if(n < 0x34) + c = 'a' + n - 0x1a; + else + return(-1); + } + else if(scheme == SCH_ISO646) { + i -= 3; + if(i < 0) + return(-1); + n = ((d >> i) & 0x1f); + dbprintf(2, "(%02x)", n); + if(n < 0xa) + c = '!' + n - 8; + else if(n < 0x15) + c = '%' + n - 0xa; + else if(n < 0x1b) + c = ':' + n - 0x15; + else if(n == 0x1b) + c = '_'; + else if(n == 0x1c) + c = ' '; + else + return(-1); + } + else + return(-1); + + if(c) { + dbprintf(2, "%d%c", scheme, c); + *(buf++) = c; + } + } + } + /* FIXME check pad? */ + } + + i = buf - dcode->buf; + zassert(i < dcode->buf_alloc, -1, "i=%02x %s\n", i, + _zbar_decoder_buf_dump(dcode->buf, i)); + + *buf = 0; + dcode->buflen = i; + if(i && *--buf == GS) { + *buf = 0; + dcode->buflen--; + } + + dbprintf(2, "\n %s", _zbar_decoder_buf_dump(dcode->buf, dcode->buflen)); + return(0); +} +#undef FEED_BITS + +/* convert from heterogeneous base {1597,2841} + * to base 10 character representation + */ +static inline void +databar_postprocess (zbar_decoder_t *dcode, + unsigned d[4]) +{ + databar_decoder_t *db = &dcode->databar; + int i; + unsigned c, chk = 0; + unsigned char *buf = dcode->buf; + *(buf++) = '0'; + *(buf++) = '1'; + buf += 15; + *--buf = '\0'; + *--buf = '\0'; + + dbprintf(2, "\n d={%d,%d,%d,%d}", d[0], d[1], d[2], d[3]); + unsigned long r = d[0] * 1597 + d[1]; + d[1] = r / 10000; + r %= 10000; + r = r * 2841 + d[2]; + d[2] = r / 10000; + r %= 10000; + r = r * 1597 + d[3]; + d[3] = r / 10000; + dbprintf(2, " r=%ld", r); + + for(i = 4; --i >= 0; ) { + c = r % 10; + chk += c; + if(i & 1) + chk += c << 1; + *--buf = c + '0'; + if(i) + r /= 10; + } + + dbprintf(2, " d={%d,%d,%d}", d[1], d[2], d[3]); + r = d[1] * 2841 + d[2]; + d[2] = r / 10000; + r %= 10000; + r = r * 1597 + d[3]; + d[3] = r / 10000; + dbprintf(2, " r=%ld", r); + + for(i = 4; --i >= 0; ) { + c = r % 10; + chk += c; + if(i & 1) + chk += c << 1; + *--buf = c + '0'; + if(i) + r /= 10; + } + + r = d[2] * 1597 + d[3]; + dbprintf(2, " d={%d,%d} r=%ld", d[2], d[3], r); + + for(i = 5; --i >= 0; ) { + c = r % 10; + chk += c; + if(!(i & 1)) + chk += c << 1; + *--buf = c + '0'; + if(i) + r /= 10; + } + + /* NB linkage flag not supported */ + if(TEST_CFG(db->config, ZBAR_CFG_EMIT_CHECK)) { + chk %= 10; + if(chk) + chk = 10 - chk; + buf[13] = chk + '0'; + dcode->buflen = buf - dcode->buf + 14; + } + else + dcode->buflen = buf - dcode->buf + 13; + + dbprintf(2, "\n %s", _zbar_decoder_buf_dump(dcode->buf, 16)); +} + +static inline int +databar_check_width (unsigned wf, + unsigned wd, + unsigned n) +{ + unsigned dwf = wf * 3; + wd *= 14; + wf *= n; + return(wf - dwf <= wd && wd <= wf + dwf); +} + +static inline void +merge_segment (databar_decoder_t *db, + databar_segment_t *seg) +{ + unsigned csegs = db->csegs; + int i; + for(i = 0; i < csegs; i++) { + databar_segment_t *s = db->segs + i; + if(s != seg && s->finder == seg->finder && s->exp == seg->exp && + s->color == seg->color && s->side == seg->side && + s->data == seg->data && s->check == seg->check && + databar_check_width(seg->width, s->width, 14)) { + /* merge with existing segment */ + unsigned cnt = s->count; + if(cnt < 0x7f) + cnt++; + seg->count = cnt; + seg->partial &= s->partial; + seg->width = (3 * seg->width + s->width + 2) / 4; + s->finder = -1; + dbprintf(2, " dup@%d(%d,%d)", + i, cnt, (db->epoch - seg->epoch) & 0xff); + } + else if(s->finder >= 0) { + unsigned age = (db->epoch - s->epoch) & 0xff; + if(age >= 248 || (age >= 128 && s->count < 2)) + s->finder = -1; + } + } +} + +static inline zbar_symbol_type_t +match_segment (zbar_decoder_t *dcode, + databar_segment_t *seg) +{ + databar_decoder_t *db = &dcode->databar; + unsigned csegs = db->csegs, maxage = 0xfff; + int i0, i1, i2, maxcnt = 0; + databar_segment_t *smax[3] = { NULL, }; + + if(seg->partial && seg->count < 4) + return(ZBAR_PARTIAL); + + for(i0 = 0; i0 < csegs; i0++) { + databar_segment_t *s0 = db->segs + i0; + if(s0 == seg || s0->finder != seg->finder || s0->exp || + s0->color != seg->color || s0->side == seg->side || + (s0->partial && s0->count < 4) || + !databar_check_width(seg->width, s0->width, 14)) + continue; + + for(i1 = 0; i1 < csegs; i1++) { + databar_segment_t *s1 = db->segs + i1; + int chkf, chks, chk; + unsigned age1; + if(i1 == i0 || s1->finder < 0 || s1->exp || + s1->color == seg->color || + (s1->partial && s1->count < 4) || + !databar_check_width(seg->width, s1->width, 14)) + continue; + dbprintf(2, "\n\t[%d,%d] f=%d(0%xx)/%d(%x%x%x)", + i0, i1, seg->finder, seg->color, + s1->finder, s1->exp, s1->color, s1->side); + + if(seg->color) + chkf = seg->finder + s1->finder * 9; + else + chkf = s1->finder + seg->finder * 9; + if(chkf > 72) + chkf--; + if(chkf > 8) + chkf--; + + chks = (seg->check + s0->check + s1->check) % 79; + + if(chkf >= chks) + chk = chkf - chks; + else + chk = 79 + chkf - chks; + + dbprintf(2, " chk=(%d,%d) => %d", chkf, chks, chk); + age1 = (((db->epoch - s0->epoch) & 0xff) + + ((db->epoch - s1->epoch) & 0xff)); + + for(i2 = i1 + 1; i2 < csegs; i2++) { + databar_segment_t *s2 = db->segs + i2; + unsigned cnt, age2, age; + if(i2 == i0 || s2->finder != s1->finder || s2->exp || + s2->color != s1->color || s2->side == s1->side || + s2->check != chk || + (s2->partial && s2->count < 4) || + !databar_check_width(seg->width, s2->width, 14)) + continue; + age2 = (db->epoch - s2->epoch) & 0xff; + age = age1 + age2; + cnt = s0->count + s1->count + s2->count; + dbprintf(2, " [%d] MATCH cnt=%d age=%d", i2, cnt, age); + if(maxcnt < cnt || + (maxcnt == cnt && maxage > age)) { + maxcnt = cnt; + maxage = age; + smax[0] = s0; + smax[1] = s1; + smax[2] = s2; + } + } + } + } + + if(!smax[0]) + return(ZBAR_PARTIAL); + + unsigned d[4]; + d[(seg->color << 1) | seg->side] = seg->data; + for(i0 = 0; i0 < 3; i0++) { + d[(smax[i0]->color << 1) | smax[i0]->side] = smax[i0]->data; + if(!--(smax[i0]->count)) + smax[i0]->finder = -1; + } + seg->finder = -1; + + if(size_buf(dcode, 18)) + return(ZBAR_PARTIAL); + + if(acquire_lock(dcode, ZBAR_DATABAR)) + return(ZBAR_PARTIAL); + + databar_postprocess(dcode, d); + dcode->modifiers = MOD(ZBAR_MOD_GS1); + dcode->direction = 1 - 2 * (seg->side ^ seg->color ^ 1); + return(ZBAR_DATABAR); +} + +static inline unsigned +lookup_sequence (databar_segment_t *seg, + int fixed, + int seq[22]) +{ + unsigned n = seg->data / 211, i; + const unsigned char *p; + i = (n + 1) / 2 + 1; + n += 4; + i = (i * i) / 4; + dbprintf(2, " {%d,%d:", i, n); + p = exp_sequences + i; + + fixed >>= 1; + seq[0] = 0; + seq[1] = 1; + for(i = 2; i < n; ) { + int s = *p; + if(!(i & 2)) { + p++; + s >>= 4; + } + else + s &= 0xf; + if(s == fixed) + fixed = -1; + s <<= 1; + dbprintf(2, "%x", s); + seq[i++] = s++; + seq[i++] = s; + } + dbprintf(2, "}"); + seq[n] = -1; + return(fixed < 1); +} + +#define IDX(s) \ + (((s)->finder << 2) | ((s)->color << 1) | ((s)->color ^ (s)->side)) + +static inline zbar_symbol_type_t +match_segment_exp (zbar_decoder_t *dcode, + databar_segment_t *seg, + int dir) +{ + databar_decoder_t *db = &dcode->databar; + int bestsegs[22], i = 0, segs[22], seq[22]; + int ifixed = seg - db->segs, fixed = IDX(seg), maxcnt = 0; + int iseg[DATABAR_MAX_SEGMENTS]; + unsigned csegs = db->csegs, width = seg->width, maxage = 0x7fff; + + bestsegs[0] = segs[0] = seq[1] = -1; + seq[0] = 0; + + dbprintf(2, "\n fixed=%d@%d: ", fixed, ifixed); + for(i = csegs, seg = db->segs + csegs - 1; --i >= 0; seg--) { + if(seg->exp && seg->finder >= 0 && + (!seg->partial || seg->count >= 4)) + iseg[i] = IDX(seg); + else + iseg[i] = -1; + dbprintf(2, " %d", iseg[i]); + } + + for(i = 0; ; i--) { + if(!i) + dbprintf(2, "\n "); + for(; i >= 0 && seq[i] >= 0; i--) { + int j; + dbprintf(2, " [%d]%d", i, seq[i]); + + if(seq[i] == fixed) { + seg = db->segs + ifixed; + if(segs[i] < 0 && databar_check_width(width, seg->width, 14)) { + dbprintf(2, "*"); + j = ifixed; + } + else + continue; + } + else { + for(j = segs[i] + 1; j < csegs; j++) { + if(iseg[j] == seq[i] && + (!i || databar_check_width(width, db->segs[j].width, 14))) { + seg = db->segs + j; + break; + } + } + if(j == csegs) + continue; + } + + if(!i) { + if(!lookup_sequence(seg, fixed, seq)) { + dbprintf(2, "[nf]"); + continue; + } + width = seg->width; + dbprintf(2, " A00@%d", j); + } + else { + width = (width + seg->width) / 2; + dbprintf(2, " %c%x%x@%d", + 'A' + seg->finder, seg->color, seg->side, j); + } + segs[i++] = j; + segs[i++] = -1; + } + if(i < 0) + break; + + seg = db->segs + segs[0]; + unsigned cnt = 0, chk = 0, age = (db->epoch - seg->epoch) & 0xff; + for(i = 1; segs[i] >= 0; i++) { + seg = db->segs + segs[i]; + chk += seg->check; + cnt += seg->count; + age += (db->epoch - seg->epoch) & 0xff; + } + + unsigned data0 = db->segs[segs[0]].data; + unsigned chk0 = data0 % 211; + chk %= 211; + + dbprintf(2, " chk=%d ?= %d", chk, chk0); + if(chk != chk0) + continue; + + dbprintf(2, " cnt=%d age=%d", cnt, age); + if(maxcnt > cnt || (maxcnt == cnt && maxage <= age)) + continue; + + dbprintf(2, " !"); + maxcnt = cnt; + maxage = age; + for(i = 0; segs[i] >= 0; i++) + bestsegs[i] = segs[i]; + bestsegs[i] = -1; + } + + if(bestsegs[0] < 0) + return(ZBAR_PARTIAL); + + if(acquire_lock(dcode, ZBAR_DATABAR_EXP)) + return(ZBAR_PARTIAL); + + for(i = 0; bestsegs[i] >= 0; i++) + segs[i] = db->segs[bestsegs[i]].data; + + if(databar_postprocess_exp(dcode, segs)) { + release_lock(dcode, ZBAR_DATABAR_EXP); + return(ZBAR_PARTIAL); + } + + for(i = 0; bestsegs[i] >= 0; i++) + if(bestsegs[i] != ifixed) { + seg = db->segs + bestsegs[i]; + if(!--seg->count) + seg->finder = -1; + } + + /* FIXME stacked rows are frequently reversed, + * so direction is impossible to determine at this level + */ + dcode->direction = (1 - 2 * (seg->side ^ seg->color)) * dir; + dcode->modifiers = MOD(ZBAR_MOD_GS1); + return(ZBAR_DATABAR_EXP); +} +#undef IDX + +static inline unsigned +databar_calc_check (unsigned sig0, + unsigned sig1, + unsigned side, + unsigned mod) +{ + unsigned chk = 0; + int i; + for(i = 4; --i >= 0; ) { + chk = (chk * 3 + (sig1 & 0xf) + 1) * 3 + (sig0 & 0xf) + 1; + sig1 >>= 4; + sig0 >>= 4; + if(!(i & 1)) + chk %= mod; + } + dbprintf(2, " chk=%d", chk); + + if(side) + chk = (chk * (6561 % mod)) % mod; + return(chk); +} + +static inline int +calc_value4 (unsigned sig, + unsigned n, + unsigned wmax, + unsigned nonarrow) +{ + unsigned v = 0; + n--; + + unsigned w0 = (sig >> 12) & 0xf; + if(w0 > 1) { + if(w0 > wmax) + return(-1); + unsigned n0 = n - w0; + unsigned sk20 = (n - 1) * n * (2 * n - 1); + unsigned sk21 = n0 * (n0 + 1) * (2 * n0 + 1); + v = sk20 - sk21 - 3 * (w0 - 1) * (2 * n - w0); + + if(!nonarrow && w0 > 2 && n > 4) { + unsigned k = (n - 2) * (n - 1) * (2 * n - 3) - sk21; + k -= 3 * (w0 - 2) * (14 * n - 7 * w0 - 31); + v -= k; + } + + if(n - 2 > wmax) { + unsigned wm20 = 2 * wmax * (wmax + 1); + unsigned wm21 = (2 * wmax + 1); + unsigned k = sk20; + if(n0 > wmax) { + k -= sk21; + k += 3 * (w0 - 1) * (wm20 - wm21 * (2 * n - w0)); + } + else { + k -= (wmax + 1) * (wmax + 2) * (2 * wmax + 3); + k += 3 * (n - wmax - 2) * (wm20 - wm21 * (n + wmax + 1)); + } + k *= 3; + v -= k; + } + v /= 12; + } + else + nonarrow = 1; + n -= w0; + + unsigned w1 = (sig >> 8) & 0xf; + if(w1 > 1) { + if(w1 > wmax) + return(-1); + v += (2 * n - w1) * (w1 - 1) / 2; + if(!nonarrow && w1 > 2 && n > 3) + v -= (2 * n - w1 - 5) * (w1 - 2) / 2; + if(n - 1 > wmax) { + if(n - w1 > wmax) + v -= (w1 - 1) * (2 * n - w1 - 2 * wmax); + else + v -= (n - wmax) * (n - wmax - 1); + } + } + else + nonarrow = 1; + n -= w1; + + unsigned w2 = (sig >> 4) & 0xf; + if(w2 > 1) { + if(w2 > wmax) + return(-1); + v += w2 - 1; + if(!nonarrow && w2 > 2 && n > 2) + v -= n - 2; + if(n > wmax) + v -= n - wmax; + } + else + nonarrow = 1; + + unsigned w3 = sig & 0xf; + if(w3 == 1) + nonarrow = 1; + else if(w3 > wmax) + return(-1); + + if(!nonarrow) + return(-1); + + return(v); +} + +static inline zbar_symbol_type_t +decode_char (zbar_decoder_t *dcode, + databar_segment_t *seg, + int off, + int dir) +{ + databar_decoder_t *db = &dcode->databar; + unsigned s = calc_s(dcode, (dir > 0) ? off : off - 6, 8); + int n, i, emin[2] = { 0, }, sum = 0; + unsigned sig0 = 0, sig1 = 0; + + if(seg->exp) + n = 17; + else if(seg->side) + n = 15; + else + n = 16; + emin[1] = -n; + + dbprintf(2, "\n char[%c%d]: n=%d s=%d w=%d sig=", + (dir < 0) ? '>' : '<', off, n, s, seg->width); + if(s < 13 || !databar_check_width(seg->width, s, n)) + return(ZBAR_NONE); + + for(i = 4; --i >= 0; ) { + int e = decode_e(pair_width(dcode, off), s, n); + if(e < 0) + return(ZBAR_NONE); + dbprintf(2, "%d", e); + sum = e - sum; + off += dir; + sig1 <<= 4; + if(emin[1] < -sum) + emin[1] = -sum; + sig1 += sum; + if(!i) + break; + + e = decode_e(pair_width(dcode, off), s, n); + if(e < 0) + return(ZBAR_NONE); + dbprintf(2, "%d", e); + sum = e - sum; + off += dir; + sig0 <<= 4; + if(emin[0] > sum) + emin[0] = sum; + sig0 += sum; + } + + int diff = emin[~n & 1]; + diff = diff + (diff << 4); + diff = diff + (diff << 8); + + sig0 -= diff; + sig1 += diff; + + dbprintf(2, " emin=%d,%d el=%04x/%04x", emin[0], emin[1], sig0, sig1); + + unsigned sum0 = sig0 + (sig0 >> 8); + unsigned sum1 = sig1 + (sig1 >> 8); + sum0 += sum0 >> 4; + sum1 += sum1 >> 4; + sum0 &= 0xf; + sum1 &= 0xf; + + dbprintf(2, " sum=%d/%d", sum0, sum1); + + if(sum0 + sum1 + 8 != n) { + dbprintf(2, " [SUM]"); + return(ZBAR_NONE); + } + + if(((sum0 ^ (n >> 1)) | (sum1 ^ (n >> 1) ^ n)) & 1) { + dbprintf(2, " [ODD]"); + return(ZBAR_NONE); + } + + i = ((n & 0x3) ^ 1) * 5 + (sum1 >> 1); + zassert(i < sizeof(groups) / sizeof(*groups), -1, + "n=%d sum=%d/%d sig=%04x/%04x g=%d", + n, sum0, sum1, sig0, sig1, i); + struct group_s *g = groups + i; + dbprintf(2, "\n g=%d(%d,%d,%d/%d)", + i, g->sum, g->wmax, g->todd, g->teven); + + int vodd = calc_value4(sig0 + 0x1111, sum0 + 4, g->wmax, ~n & 1); + dbprintf(2, " v=%d", vodd); + if(vodd < 0 || vodd > g->todd) + return(ZBAR_NONE); + + int veven = calc_value4(sig1 + 0x1111, sum1 + 4, 9 - g->wmax, n & 1); + dbprintf(2, "/%d", veven); + if(veven < 0 || veven > g->teven) + return(ZBAR_NONE); + + int v = g->sum; + if(n & 2) + v += vodd + veven * g->todd; + else + v += veven + vodd * g->teven; + + dbprintf(2, " f=%d(%x%x%x)", seg->finder, seg->exp, seg->color, seg->side); + + unsigned chk = 0; + if(seg->exp) { + unsigned side = seg->color ^ seg->side ^ 1; + if(v >= 4096) + return(ZBAR_NONE); + /* skip A1 left */ + chk = databar_calc_check(sig0, sig1, side, 211); + if(seg->finder || seg->color || seg->side) { + i = (seg->finder << 1) - side + seg->color; + zassert(i >= 0 && i < 12, ZBAR_NONE, + "f=%d(%x%x%x) side=%d i=%d\n", + seg->finder, seg->exp, seg->color, seg->side, side, i); + chk = (chk * exp_checksums[i]) % 211; + } + else if(v >= 4009) + return(ZBAR_NONE); + else + chk = 0; + } + else { + chk = databar_calc_check(sig0, sig1, seg->side, 79); + if(seg->color) + chk = (chk * 16) % 79; + } + dbprintf(2, " => %d val=%d", chk, v); + + seg->check = chk; + seg->data = v; + + merge_segment(db, seg); + + if(seg->exp) + return(match_segment_exp(dcode, seg, dir)); + else if(dir > 0) + return(match_segment(dcode, seg)); + return(ZBAR_PARTIAL); +} + +static inline int +alloc_segment (databar_decoder_t *db) +{ + unsigned maxage = 0, csegs = db->csegs; + int i, old = -1; + for(i = 0; i < csegs; i++) { + databar_segment_t *seg = db->segs + i; + unsigned age; + if(seg->finder < 0) { + dbprintf(2, " free@%d", i); + return(i); + } + age = (db->epoch - seg->epoch) & 0xff; + if(age >= 128 && seg->count < 2) { + seg->finder = -1; + dbprintf(2, " stale@%d (%d - %d = %d)", + i, db->epoch, seg->epoch, age); + return(i); + } + + /* score based on both age and count */ + if(age > seg->count) + age = age - seg->count + 1; + else + age = 1; + + if(maxage < age) { + maxage = age; + old = i; + dbprintf(2, " old@%d(%u)", i, age); + } + } + + if(csegs < DATABAR_MAX_SEGMENTS) { + dbprintf(2, " new@%d", i); + i = csegs; + csegs *= 2; + if(csegs > DATABAR_MAX_SEGMENTS) + csegs = DATABAR_MAX_SEGMENTS; + if(csegs != db->csegs) { + databar_segment_t *seg; + db->segs = realloc(db->segs, csegs * sizeof(*db->segs)); + db->csegs = csegs; + seg = db->segs + csegs; + while(--seg, --csegs >= i) { + seg->finder = -1; + seg->exp = 0; + seg->color = 0; + seg->side = 0; + seg->partial = 0; + seg->count = 0; + seg->epoch = 0; + seg->check = 0; + } + return(i); + } + } + zassert(old >= 0, -1, "\n"); + + db->segs[old].finder = -1; + return(old); +} + +static inline zbar_symbol_type_t +decode_finder (zbar_decoder_t *dcode) +{ + databar_decoder_t *db = &dcode->databar; + databar_segment_t *seg; + unsigned e0 = pair_width(dcode, 1); + unsigned e2 = pair_width(dcode, 3); + unsigned e1, e3, s, finder, dir; + int sig, iseg; + dbprintf(2, " databar: e0=%d e2=%d", e0, e2); + if(e0 < e2) { + unsigned e = e2 * 4; + if(e < 15 * e0 || e > 34 * e0) + return(ZBAR_NONE); + dir = 0; + e3 = pair_width(dcode, 4); + } + else { + unsigned e = e0 * 4; + if(e < 15 * e2 || e > 34 * e2) + return(ZBAR_NONE); + dir = 1; + e2 = e0; + e3 = pair_width(dcode, 0); + } + e1 = pair_width(dcode, 2); + + s = e1 + e3; + dbprintf(2, " e1=%d e3=%d dir=%d s=%d", e1, e3, dir, s); + if(s < 12) + return(ZBAR_NONE); + + sig = ((decode_e(e3, s, 14) << 8) | (decode_e(e2, s, 14) << 4) | + decode_e(e1, s, 14)); + dbprintf(2, " sig=%04x", sig & 0xfff); + if(sig < 0 || + ((sig >> 4) & 0xf) < 8 || + ((sig >> 4) & 0xf) > 10 || + (sig & 0xf) >= 10 || + ((sig >> 8) & 0xf) >= 10 || + (((sig >> 8) + sig) & 0xf) != 10) + return(ZBAR_NONE); + + finder = (finder_hash[(sig - (sig >> 5)) & 0x1f] + + finder_hash[(sig >> 1) & 0x1f]) & 0x1f; + dbprintf(2, " finder=%d", finder); + if(finder == 0x1f || + !TEST_CFG((finder < 9) ? db->config : db->config_exp, ZBAR_CFG_ENABLE)) + return(ZBAR_NONE); + + zassert(finder >= 0, ZBAR_NONE, "dir=%d sig=%04x f=%d\n", + dir, sig & 0xfff, finder); + + iseg = alloc_segment(db); + if(iseg < 0) + return(ZBAR_NONE); + + seg = db->segs + iseg; + seg->finder = (finder >= 9) ? finder - 9 : finder; + seg->exp = (finder >= 9); + seg->color = get_color(dcode) ^ dir ^ 1; + seg->side = dir; + seg->partial = 0; + seg->count = 1; + seg->width = s; + seg->epoch = db->epoch; + + int rc = decode_char(dcode, seg, 12 - dir, -1); + if(!rc) + seg->partial = 1; + else + db->epoch++; + + int i = (dcode->idx + 8 + dir) & 0xf; + zassert(db->chars[i] == -1, ZBAR_NONE, "\n"); + db->chars[i] = iseg; + return(rc); +} + +zbar_symbol_type_t +_zbar_decode_databar (zbar_decoder_t *dcode) +{ + databar_decoder_t *db = &dcode->databar; + databar_segment_t *seg, *pair; + zbar_symbol_type_t sym; + int iseg, i = dcode->idx & 0xf; + + sym = decode_finder(dcode); + dbprintf(2, "\n"); + + iseg = db->chars[i]; + if(iseg < 0) + return(sym); + + db->chars[i] = -1; + seg = db->segs + iseg; + dbprintf(2, " databar: i=%d part=%d f=%d(%x%x%x)", + iseg, seg->partial, seg->finder, seg->exp, seg->color, seg->side); + zassert(seg->finder >= 0, ZBAR_NONE, "i=%d f=%d(%x%x%x) part=%x\n", + iseg, seg->finder, seg->exp, seg->color, seg->side, seg->partial); + + if(seg->partial) { + pair = NULL; + seg->side = !seg->side; + } + else { + int jseg = alloc_segment(db); + pair = db->segs + iseg; + seg = db->segs + jseg; + seg->finder = pair->finder; + seg->exp = pair->exp; + seg->color = pair->color; + seg->side = !pair->side; + seg->partial = 0; + seg->count = 1; + seg->width = pair->width; + seg->epoch = db->epoch; + } + + sym = decode_char(dcode, seg, 1, 1); + if(!sym) { + seg->finder = -1; + if(pair) + pair->partial = 1; + } + else + db->epoch++; + dbprintf(2, "\n"); + + return(sym); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "code39.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2008-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +#define NUM_CHARS (0x2c) + +static const unsigned char code39_hi[32] = { + 0x80 | 0x00, /* 2 next */ + 0x40 | 0x02, /* 4 */ + 0x80 | 0x06, /* 2 next */ + 0xc0 | 0x08, /* 2 skip */ + 0x40 | 0x0a, /* 4 */ + 0x80 | 0x0e, /* 2 next */ + 0xc0 | 0x10, /* 2 skip */ + 0x00 | 0x12, /* direct */ + + 0x80 | 0x13, /* 2 next */ + 0xc0 | 0x15, /* 2 skip */ + 0x80 | 0x17, /* 2 next */ + 0xff, + 0xc0 | 0x19, /* 2 skip */ + 0x00 | 0x1b, /* direct */ + 0xff, + 0xff, + + 0x40 | 0x1c, /* 4 */ + 0x80 | 0x20, /* 2 next */ + 0xc0 | 0x22, /* 2 skip */ + 0x00 | 0x24, /* direct */ + 0x80 | 0x25, /* 2 next */ + 0xff, + 0x00 | 0x27, /* direct */ + 0xff, + + 0xc0 | 0x28, /* 2 skip */ + 0x00 | 0x2a, /* direct */ + 0xff, + 0xff, + 0x00 | 0x2b, /* direct */ + 0xff, + 0xff, + 0xff, +}; + +typedef struct char39_s { + unsigned char chk, rev, fwd; +} char39_t; + +static const char39_t code39_encodings[NUM_CHARS] = { + { 0x07, 0x1a, 0x20 }, /* 00 */ + { 0x0d, 0x10, 0x03 }, /* 01 */ + { 0x13, 0x17, 0x22 }, /* 02 */ + { 0x16, 0x1d, 0x23 }, /* 03 */ + { 0x19, 0x0d, 0x05 }, /* 04 */ + { 0x1c, 0x13, 0x06 }, /* 05 */ + { 0x25, 0x07, 0x0c }, /* 06 */ + { 0x2a, 0x2a, 0x27 }, /* 07 */ + { 0x31, 0x04, 0x0e }, /* 08 */ + { 0x34, 0x00, 0x0f }, /* 09 */ + { 0x43, 0x15, 0x25 }, /* 0a */ + { 0x46, 0x1c, 0x26 }, /* 0b */ + { 0x49, 0x0b, 0x08 }, /* 0c */ + { 0x4c, 0x12, 0x09 }, /* 0d */ + { 0x52, 0x19, 0x2b }, /* 0e */ + { 0x58, 0x0f, 0x00 }, /* 0f */ + { 0x61, 0x02, 0x11 }, /* 10 */ + { 0x64, 0x09, 0x12 }, /* 11 */ + { 0x70, 0x06, 0x13 }, /* 12 */ + { 0x85, 0x24, 0x16 }, /* 13 */ + { 0x8a, 0x29, 0x28 }, /* 14 */ + { 0x91, 0x21, 0x18 }, /* 15 */ + { 0x94, 0x2b, 0x19 }, /* 16 */ + { 0xa2, 0x28, 0x29 }, /* 17 */ + { 0xa8, 0x27, 0x2a }, /* 18 */ + { 0xc1, 0x1f, 0x1b }, /* 19 */ + { 0xc4, 0x26, 0x1c }, /* 1a */ + { 0xd0, 0x23, 0x1d }, /* 1b */ + { 0x03, 0x14, 0x1e }, /* 1c */ + { 0x06, 0x1b, 0x1f }, /* 1d */ + { 0x09, 0x0a, 0x01 }, /* 1e */ + { 0x0c, 0x11, 0x02 }, /* 1f */ + { 0x12, 0x18, 0x21 }, /* 20 */ + { 0x18, 0x0e, 0x04 }, /* 21 */ + { 0x21, 0x01, 0x0a }, /* 22 */ + { 0x24, 0x08, 0x0b }, /* 23 */ + { 0x30, 0x05, 0x0d }, /* 24 */ + { 0x42, 0x16, 0x24 }, /* 25 */ + { 0x48, 0x0c, 0x07 }, /* 26 */ + { 0x60, 0x03, 0x10 }, /* 27 */ + { 0x81, 0x1e, 0x14 }, /* 28 */ + { 0x84, 0x25, 0x15 }, /* 29 */ + { 0x90, 0x22, 0x17 }, /* 2a */ + { 0xc0, 0x20, 0x1a }, /* 2b */ +}; + +static const unsigned char code39_characters[NUM_CHARS] = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%*"; + +static inline unsigned char code39_decode1 (unsigned char enc, + unsigned e, + unsigned s) +{ + unsigned char E = decode_e(e, s, 72); + if(E > 18) + return(0xff); + enc <<= 1; + if(E > 6) { + enc |= 1; + dbprintf(2, "1"); + } + else + dbprintf(2, "0"); + return(enc); +} + +static inline signed char code39_decode9 (zbar_decoder_t *dcode) +{ + code39_decoder_t *dcode39 = &dcode->code39; + + if(dcode39->s9 < 9) + return(-1); + + /* threshold bar width ratios */ + unsigned char i, enc = 0; + for(i = 0; i < 5; i++) { + enc = code39_decode1(enc, get_width(dcode, i), dcode39->s9); + if(enc == 0xff) + return(-1); + } + zassert(enc < 0x20, -1, " enc=%x s9=%x\n", enc, dcode39->s9); + + /* lookup first 5 encoded widths for coarse decode */ + unsigned char idx = code39_hi[enc]; + if(idx == 0xff) + return(-1); + + /* encode remaining widths (NB first encoded width is lost) */ + for(; i < 9; i++) { + enc = code39_decode1(enc, get_width(dcode, i), dcode39->s9); + if(enc == 0xff) + return(-1); + } + + if((idx & 0xc0) == 0x80) + idx = (idx & 0x3f) + ((enc >> 3) & 1); + else if((idx & 0xc0) == 0xc0) + idx = (idx & 0x3f) + ((enc >> 2) & 1); + else if(idx & 0xc0) + idx = (idx & 0x3f) + ((enc >> 2) & 3); + zassert(idx < 0x2c, -1, " idx=%x enc=%x s9=%x\n", idx, enc, dcode39->s9); + + const char39_t *c = &code39_encodings[idx]; + dbprintf(2, " i=%02x chk=%02x c=%02x/%02x", idx, c->chk, c->fwd, c->rev); + if(enc != c->chk) + return(-1); + + dcode39->width = dcode39->s9; + return((dcode39->direction) ? c->rev : c->fwd); +} + +static inline signed char code39_decode_start (zbar_decoder_t *dcode) +{ + code39_decoder_t *dcode39 = &dcode->code39; + dbprintf(2, " s=%d ", dcode39->s9); + + signed char c = code39_decode9(dcode); + if(c != 0x19 && c != 0x2b) { + dbprintf(2, "\n"); + return(ZBAR_NONE); + } + dcode39->direction ^= (c == 0x19); + + /* check leading quiet zone - spec is 10x */ + unsigned quiet = get_width(dcode, 9); + if(quiet && quiet < dcode39->s9 / 2) { + dbprintf(2, " [invalid quiet]\n"); + return(ZBAR_NONE); + } + + dcode39->element = 9; + dcode39->character = 0; + dbprintf(1, " dir=%x [valid start]\n", dcode39->direction); + return(ZBAR_PARTIAL); +} + +static inline int code39_postprocess (zbar_decoder_t *dcode) +{ + code39_decoder_t *dcode39 = &dcode->code39; + dcode->direction = 1 - 2 * dcode39->direction; + int i; + if(dcode39->direction) { + /* reverse buffer */ + dbprintf(2, " (rev)"); + for(i = 0; i < dcode39->character / 2; i++) { + unsigned j = dcode39->character - 1 - i; + char code = dcode->buf[i]; + dcode->buf[i] = dcode->buf[j]; + dcode->buf[j] = code; + } + } + for(i = 0; i < dcode39->character; i++) + dcode->buf[i] = ((dcode->buf[i] < 0x2b) + ? code39_characters[(unsigned)dcode->buf[i]] + : '?'); + zassert(i < dcode->buf_alloc, -1, "i=%02x %s\n", i, + _zbar_decoder_buf_dump(dcode->buf, dcode39->character)); + dcode->buflen = i; + dcode->buf[i] = '\0'; + dcode->modifiers = 0; + return(0); +} + +static inline int +c39_check_width (unsigned ref, + unsigned w) +{ + unsigned dref = ref; + ref *= 4; + w *= 4; + return(ref - dref <= w && w <= ref + dref); +} + +zbar_symbol_type_t _zbar_decode_code39 (zbar_decoder_t *dcode) +{ + code39_decoder_t *dcode39 = &dcode->code39; + + /* update latest character width */ + dcode39->s9 -= get_width(dcode, 9); + dcode39->s9 += get_width(dcode, 0); + + if(dcode39->character < 0) { + if(get_color(dcode) != ZBAR_BAR) + return(ZBAR_NONE); + dbprintf(2, " code39:"); + return(code39_decode_start(dcode)); + } + + if(++dcode39->element < 9) + return(ZBAR_NONE); + + dbprintf(2, " code39[%c%02d+%x]", + (dcode39->direction) ? '<' : '>', + dcode39->character, dcode39->element); + + if(dcode39->element == 10) { + unsigned space = get_width(dcode, 0); + if(dcode39->character && + dcode->buf[dcode39->character - 1] == 0x2b) { /* STOP */ + /* trim STOP character */ + dcode39->character--; + zbar_symbol_type_t sym = ZBAR_NONE; + + /* trailing quiet zone check */ + if(space && space < dcode39->width / 2) + dbprintf(2, " [invalid qz]\n"); + else if(dcode39->character < CFG(*dcode39, ZBAR_CFG_MIN_LEN) || + (CFG(*dcode39, ZBAR_CFG_MAX_LEN) > 0 && + dcode39->character > CFG(*dcode39, ZBAR_CFG_MAX_LEN))) + dbprintf(2, " [invalid len]\n"); + else if(!code39_postprocess(dcode)) { + /* FIXME checksum */ + dbprintf(2, " [valid end]\n"); + sym = ZBAR_CODE39; + } + dcode39->character = -1; + if(!sym) + release_lock(dcode, ZBAR_CODE39); + return(sym); + } + if(space > dcode39->width / 2) { + /* inter-character space check failure */ + dbprintf(2, " ics>%d [invalid ics]", dcode39->width); + if(dcode39->character) + release_lock(dcode, ZBAR_CODE39); + dcode39->character = -1; + } + dcode39->element = 0; + dbprintf(2, "\n"); + return(ZBAR_NONE); + } + + dbprintf(2, " s=%d ", dcode39->s9); + if(!c39_check_width(dcode39->width, dcode39->s9)) { + dbprintf(2, " [width]\n"); + if(dcode39->character) + release_lock(dcode, ZBAR_CODE39); + dcode39->character = -1; + return(ZBAR_NONE); + } + + signed char c = code39_decode9(dcode); + dbprintf(2, " c=%d", c); + + /* lock shared resources */ + if(!dcode39->character && acquire_lock(dcode, ZBAR_CODE39)) { + dcode39->character = -1; + return(ZBAR_PARTIAL); + } + + if(c < 0 || size_buf(dcode, dcode39->character + 1)) { + dbprintf(1, (c < 0) ? " [aborted]\n" : " [overflow]\n"); + release_lock(dcode, ZBAR_CODE39); + dcode39->character = -1; + return(ZBAR_NONE); + } + else { + zassert(c < 0x2c, ZBAR_NONE, "c=%02x s9=%x\n", c, dcode39->s9); + dbprintf(2, "\n"); + } + + dcode->buf[dcode39->character++] = c; + + return(ZBAR_NONE); +} + +#undef NUM_CHARS + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "code93.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +static const signed char code93_hash[0x40] = { + 0x0f, 0x2b, 0x30, 0x38, 0x13, 0x1b, 0x11, 0x2a, + 0x0a, -1, 0x2f, 0x0f, 0x38, 0x38, 0x2f, 0x37, + 0x24, 0x3a, 0x1b, 0x36, 0x18, 0x26, 0x02, 0x2c, + 0x2b, 0x05, 0x21, 0x3b, 0x04, 0x15, 0x12, 0x0c, + 0x00, 0x26, 0x23, 0x00, -1, 0x2e, 0x3f, 0x13, + 0x2e, 0x36, -1, 0x08, 0x09, -1, 0x15, 0x14, + -1, 0x00, 0x21, 0x3b, -1, 0x33, 0x00, -1, + 0x2d, 0x0c, 0x1b, 0x0a, 0x3f, 0x3f, 0x29, 0x1c, +}; + +static inline int +c93_check_width (unsigned cur, + unsigned prev) +{ + unsigned dw; + if(prev > cur) + dw = prev - cur; + else + dw = cur - prev; + dw *= 4; + return(dw > prev); +} + +static inline int +encode6 (zbar_decoder_t *dcode) +{ + /* build edge signature of character */ + unsigned s = dcode->s6; + int sig = 0, i; + + dbprintf(2, " s=%d ", s); + if(s < 9) + return(-1); + + for(i = 6; --i > 0; ) { + unsigned c = decode_e(pair_width(dcode, i), s, 9); + if(c > 3) + return(-1); + sig = (sig << 2) | c; + dbprintf(2, "%d", c); + } + dbprintf(2, " sig=%03x", sig); + + return(sig); +} + +static inline int +validate_sig (int sig) +{ + int i, sum = 0, emin = 0, sig0 = 0, sig1 = 0; + dbprintf(3, " sum=0"); + for(i = 3; --i >= 0; ) { + int e = sig & 3; + sig >>= 2; + sum = e - sum; + sig1 <<= 4; + sig1 += sum; + dbprintf(3, "%d", sum); + if(!i) + break; + + e = sig & 3; + sig >>= 2; + sum = e - sum; + sig0 <<= 4; + if(emin > sum) + emin = sum; + sig0 += sum; + dbprintf(3, "%d", sum); + } + + dbprintf(3, " emin=%d sig=%03x/%03x", emin, sig1 & 0xfff, sig0 & 0xfff); + + emin = emin + (emin << 4) + (emin << 8); + sig0 -= emin; + sig1 += emin; + + dbprintf(3, "=%03x/%03x", sig1 & 0xfff, sig0 & 0xfff); + return((sig0 | sig1) & 0x888); +} + +static inline int +c93_decode6 (zbar_decoder_t *dcode) +{ + int sig = encode6(dcode); + int g0, g1, c; + if(sig < 0 || + (sig & 0x3) + ((sig >> 4) & 0x3) + ((sig >> 8) & 0x3) != 3 || + validate_sig(sig)) + return(-1); + + if(dcode->code93.direction) { + /* reverse signature */ + unsigned tmp = sig & 0x030; + sig = ((sig & 0x3c0) >> 6) | ((sig & 0x00f) << 6); + sig = ((sig & 0x30c) >> 2) | ((sig & 0x0c3) << 2) | tmp; + } + + g0 = code93_hash[(sig - (sig >> 4)) & 0x3f]; + g1 = code93_hash[((sig >> 2) - (sig >> 7)) & 0x3f]; + zassert(g0 >= 0 && g1 >= 0, -1, + "dir=%x sig=%03x g0=%03x g1=%03x %s\n", + dcode->code93.direction, sig, g0, g1, + _zbar_decoder_buf_dump(dcode->buf, dcode->code93.character)); + + c = (g0 + g1) & 0x3f; + dbprintf(2, " g0=%x g1=%x c=%02x", g0, g1, c); + return(c); +} + +static inline zbar_symbol_type_t +decode_start (zbar_decoder_t *dcode) +{ + code93_decoder_t *dcode93 = &dcode->code93; + unsigned dir, qz, s = dcode->s6; + int c; + + dbprintf(2, " code93:"); + c = encode6(dcode); + if(c < 0 || (c != 0x00f && c != 0x0f0)) + return(ZBAR_NONE); + + dir = (c >> 7); + + if(dir) { + if(decode_e(pair_width(dcode, 0), s, 9)) + return(ZBAR_NONE); + qz = get_width(dcode, 8); + } + + qz = get_width(dcode, 7); + if(qz && qz < (s * 3) / 4) { + dbprintf(2, " [invalid qz %d]", qz); + return(ZBAR_NONE); + } + + /* decoded valid start/stop - initialize state */ + dcode93->direction = dir; + dcode93->element = (!dir) ? 0 : 7; + dcode93->character = 0; + dcode93->width = s; + + dbprintf(2, " dir=%x [valid start]", dir); + return(ZBAR_PARTIAL); +} + +static inline zbar_symbol_type_t +decode_abort (zbar_decoder_t *dcode, + const char *reason) +{ + code93_decoder_t *dcode93 = &dcode->code93; + if(dcode93->character > 1) + release_lock(dcode, ZBAR_CODE93); + dcode93->character = -1; + if(reason) + dbprintf(1, " [%s]\n", reason); + return(ZBAR_NONE); +} + +static inline zbar_symbol_type_t +check_stop (zbar_decoder_t *dcode) +{ + code93_decoder_t *dcode93 = &dcode->code93; + unsigned n = dcode93->character, s = dcode->s6; + int max_len = CFG(*dcode93, ZBAR_CFG_MAX_LEN); + if(n < 2 || + n < CFG(*dcode93, ZBAR_CFG_MIN_LEN) || + (max_len && n > max_len)) + return(decode_abort(dcode, "invalid len")); + + if(dcode93->direction) { + unsigned qz = get_width(dcode, 0); + if(qz && qz < (s * 3) / 4) + return(decode_abort(dcode, "invalid qz")); + } + else if(decode_e(pair_width(dcode, 0), s, 9)) + /* FIXME forward-trailing QZ check */ + return(decode_abort(dcode, "invalid stop")); + + return(ZBAR_CODE93); +} + +#define CHKMOD (47) + +static inline int +plusmod47 (int acc, + int add) +{ + acc += add; + if(acc >= CHKMOD) + acc -= CHKMOD; + return(acc); +} + +static inline int +validate_checksums (zbar_decoder_t *dcode) +{ + code93_decoder_t *dcode93 = &dcode->code93; + unsigned d, i, n = dcode93->character; + unsigned sum_c = 0, acc_c = 0, i_c = (n - 2) % 20; + unsigned sum_k = 0, acc_k = 0, i_k = (n - 1) % 15; + + for(i = 0; i < n - 2; i++) { + d = dcode->buf[(dcode93->direction) ? n - 1 - i : i]; + + if(!i_c--) { + acc_c = 0; + i_c = 19; + } + acc_c = plusmod47(acc_c, d); + sum_c = plusmod47(sum_c, acc_c); + + if(!i_k--) { + acc_k = 0; + i_k = 14; + } + acc_k = plusmod47(acc_k, d); + sum_k = plusmod47(sum_k, acc_k); + } + + d = dcode->buf[(dcode93->direction) ? 1 : n - 2]; + dbprintf(2, " C=%02x?=%02x", d, sum_c); + if(d != sum_c) + return(1); + + acc_k = plusmod47(acc_k, sum_c); + sum_k = plusmod47(sum_k, acc_k); + d = dcode->buf[(dcode93->direction) ? 0 : n - 1]; + dbprintf(2, " K=%02x?=%02x", d, sum_k); + if(d != sum_k) + return(1); + + return(0); +} + +/* resolve scan direction and convert to ASCII */ +static inline int +c93_postprocess (zbar_decoder_t *dcode) +{ + code93_decoder_t *dcode93 = &dcode->code93; + unsigned i, j, n = dcode93->character; + static const unsigned char code93_graph[] = "-. $/+%"; + static const unsigned char code93_s2[] = + "\x1b\x1c\x1d\x1e\x1f;<=>?[\\]^_{|}~\x7f\x00\x40`\x7f\x7f\x7f"; + + dbprintf(2, "\n postproc len=%d", n); + dcode->direction = 1 - 2 * dcode93->direction; + if(dcode93->direction) { + /* reverse buffer */ + dbprintf(2, " (rev)"); + for(i = 0; i < n / 2; i++) { + unsigned j = n - 1 - i; + unsigned char d = dcode->buf[i]; + dcode->buf[i] = dcode->buf[j]; + dcode->buf[j] = d; + } + } + + n -= 2; + for(i = 0, j = 0; i < n; ) { + unsigned char d = dcode->buf[i++]; + if(d < 0xa) + d = '0' + d; + else if(d < 0x24) + d = 'A' + d - 0xa; + else if(d < 0x2b) + d = code93_graph[d - 0x24]; + else { + unsigned shift = d; + zassert(shift < 0x2f, -1, "%s\n", + _zbar_decoder_buf_dump(dcode->buf, dcode93->character)); + d = dcode->buf[i++]; + if(d < 0xa || d >= 0x24) + return(1); + d -= 0xa; + switch(shift) + { + case 0x2b: d++; break; + case 0x2c: d = code93_s2[d]; break; + case 0x2d: d += 0x21; break; + case 0x2e: d += 0x61; break; + default: return(1); + } + } + dcode->buf[j++] = d; + } + + zassert(j < dcode->buf_alloc, 1, + "j=%02x %s\n", j, + _zbar_decoder_buf_dump(dcode->buf, dcode->code93.character)); + dcode->buflen = j; + dcode->buf[j] = '\0'; + dcode->modifiers = 0; + return(0); +} + +zbar_symbol_type_t +_zbar_decode_code93 (zbar_decoder_t *dcode) +{ + code93_decoder_t *dcode93 = &dcode->code93; + int c; + + if(dcode93->character < 0) { + zbar_symbol_type_t sym; + if(get_color(dcode) != ZBAR_BAR) + return(ZBAR_NONE); + sym = decode_start(dcode); + dbprintf(2, "\n"); + return(sym); + } + + if(/* process every 6th element of active symbol */ + ++dcode93->element != 6 || + /* decode color based on direction */ + get_color(dcode) == dcode93->direction) + return(ZBAR_NONE); + + dcode93->element = 0; + + dbprintf(2, " code93[%c%02d+%x]:", + (dcode93->direction) ? '<' : '>', + dcode93->character, dcode93->element); + + if(c93_check_width(dcode->s6, dcode93->width)) + return(decode_abort(dcode, "width var")); + + c = c93_decode6(dcode); + if(c < 0) + return(decode_abort(dcode, "aborted")); + + if(c == 0x2f) { + if(!check_stop(dcode)) + return(ZBAR_NONE); + if(validate_checksums(dcode)) + return(decode_abort(dcode, "checksum error")); + if(c93_postprocess(dcode)) + return(decode_abort(dcode, "invalid encoding")); + + dbprintf(2, " [valid end]\n"); + dbprintf(3, " %s\n", + _zbar_decoder_buf_dump(dcode->buf, dcode93->character)); + + dcode93->character = -1; + return(ZBAR_CODE93); + } + + if(size_buf(dcode, dcode93->character + 1)) + return(decode_abort(dcode, "overflow")); + + dcode93->width = dcode->s6; + + if(dcode93->character == 1) { + /* lock shared resources */ + if(acquire_lock(dcode, ZBAR_CODE93)) + return(decode_abort(dcode, NULL)); + dcode->buf[0] = dcode93->buf; + } + + if(!dcode93->character) + dcode93->buf = c; + else + dcode->buf[dcode93->character] = c; + dcode93->character++; + + dbprintf(2, "\n"); + return(ZBAR_NONE); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "code128.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +#define NUM_CHARS 108 /* total number of character codes */ + +typedef enum code128_char_e { + FNC3 = 0x60, + FNC2 = 0x61, + SHIFT = 0x62, + CODE_C = 0x63, + CODE_B = 0x64, + CODE_A = 0x65, + FNC1 = 0x66, + START_A = 0x67, + START_B = 0x68, + START_C = 0x69, + STOP_FWD = 0x6a, + STOP_REV = 0x6b, + FNC4 = 0x6c, +} code128_char_t; + +static const unsigned char characters[NUM_CHARS] = { + 0x5c, 0xbf, 0xa1, /* [00] 00 */ + 0x2a, 0xc5, 0x0c, 0xa4, /* [03] 01 */ + 0x2d, 0xe3, 0x0f, /* [07] 02 */ + 0x5f, 0xe4, /* [0a] 03 */ + + 0x6b, 0xe8, 0x69, 0xa7, 0xe7, /* [0c] 10 */ + 0xc1, 0x51, 0x1e, 0x83, 0xd9, 0x00, 0x84, 0x1f, /* [11] 11 */ + 0xc7, 0x0d, 0x33, 0x86, 0xb5, 0x0e, 0x15, 0x87, /* [19] 12 */ + 0x10, 0xda, 0x11, /* [21] 13 */ + + 0x36, 0xe5, 0x18, 0x37, /* [24] 20 */ + 0xcc, 0x13, 0x39, 0x89, 0x97, 0x14, 0x1b, 0x8a, 0x3a, 0xbd, /* [28] 21 */ + 0xa2, 0x5e, 0x01, 0x85, 0xb0, 0x02, 0xa3, /* [32] 22 */ + 0xa5, 0x2c, 0x16, 0x88, 0xbc, 0x12, 0xa6, /* [39] 23 */ + + 0x61, 0xe6, 0x56, 0x62, /* [40] 30 */ + 0x19, 0xdb, 0x1a, /* [44] 31 */ + 0xa8, 0x32, 0x1c, 0x8b, 0xcd, 0x1d, 0xa9, /* [47] 32 */ + 0xc3, 0x20, 0xc4, /* [4e] 33 */ + + 0x50, 0x5d, 0xc0, /* [51] 0014 0025 0034 */ + 0x2b, 0xc6, /* [54] 0134 0143 */ + 0x2e, /* [56] 0243 */ + 0x53, 0x60, /* [57] 0341 0352 */ + 0x31, /* [59] 1024 */ + 0x52, 0xc2, /* [5a] 1114 1134 */ + 0x34, 0xc8, /* [5c] 1242 1243 */ + 0x55, /* [5e] 1441 */ + + 0x57, 0x3e, 0xce, /* [5f] 4100 5200 4300 */ + 0x3b, 0xc9, /* [62] 4310 3410 */ + 0x6a, /* [64] 3420 */ + 0x54, 0x4f, /* [65] 1430 2530 */ + 0x38, /* [67] 4201 */ + 0x58, 0xcb, /* [68] 4111 4311 */ + 0x2f, 0xca, /* [6a] 2421 3421 */ +}; + +static const unsigned char lo_base[8] = { + 0x00, 0x07, 0x0c, 0x19, 0x24, 0x32, 0x40, 0x47 +}; + +static const unsigned char lo_offset[0x80] = { + 0xff, 0xf0, 0xff, 0x1f, 0xff, 0xf2, 0xff, 0xff, /* 00 [00] */ + 0xff, 0xff, 0xff, 0x3f, 0xf4, 0xf5, 0xff, 0x6f, /* 01 */ + 0xff, 0xff, 0xff, 0xff, 0xf0, 0xf1, 0xff, 0x2f, /* 02 [07] */ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x4f, /* 03 */ + 0xff, 0x0f, 0xf1, 0xf2, 0xff, 0x3f, 0xff, 0xf4, /* 10 [0c] */ + 0xf5, 0xf6, 0xf7, 0x89, 0xff, 0xab, 0xff, 0xfc, /* 11 */ + 0xff, 0xff, 0x0f, 0x1f, 0x23, 0x45, 0xf6, 0x7f, /* 12 [19] */ + 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xf9, 0xaf, /* 13 */ + + 0xf0, 0xf1, 0xff, 0x2f, 0xff, 0xf3, 0xff, 0xff, /* 20 [24] */ + 0x4f, 0x5f, 0x67, 0x89, 0xfa, 0xbf, 0xff, 0xcd, /* 21 */ + 0xf0, 0xf1, 0xf2, 0x3f, 0xf4, 0x56, 0xff, 0xff, /* 22 [32] */ + 0xff, 0xff, 0x7f, 0x8f, 0x9a, 0xff, 0xbc, 0xdf, /* 23 */ + 0x0f, 0x1f, 0xf2, 0xff, 0xff, 0x3f, 0xff, 0xff, /* 30 [40] */ + 0xf4, 0xff, 0xf5, 0x6f, 0xff, 0xff, 0xff, 0xff, /* 31 */ + 0x0f, 0x1f, 0x23, 0xff, 0x45, 0x6f, 0xff, 0xff, /* 32 [47] */ + 0xf7, 0xff, 0xf8, 0x9f, 0xff, 0xff, 0xff, 0xff, /* 33 */ +}; + +static inline signed char decode_lo (int sig) +{ + unsigned char offset = (((sig >> 1) & 0x01) | + ((sig >> 3) & 0x06) | + ((sig >> 5) & 0x18) | + ((sig >> 7) & 0x60)); + unsigned char idx = lo_offset[offset]; + unsigned char base, c; + + if(sig & 1) + idx &= 0xf; + else + idx >>= 4; + if(idx == 0xf) + return(-1); + + base = (sig >> 11) | ((sig >> 9) & 1); + zassert(base < 8, -1, "sig=%x offset=%x idx=%x base=%x\n", + sig, offset, idx, base); + idx += lo_base[base]; + + zassert(idx <= 0x50, -1, "sig=%x offset=%x base=%x idx=%x\n", + sig, offset, base, idx); + c = characters[idx]; + dbprintf(2, " %02x(%x(%02x)/%x(%02x)) => %02x", + idx, base, lo_base[base], offset, lo_offset[offset], + (unsigned char)c); + return(c); +} + +static inline signed char decode_hi (int sig) +{ + unsigned char rev = (sig & 0x4400) != 0; + unsigned char idx, c; + if(rev) + sig = (((sig >> 12) & 0x000f) | + ((sig >> 4) & 0x00f0) | + ((sig << 4) & 0x0f00) | + ((sig << 12) & 0xf000)); + dbprintf(2, " rev=%x", rev != 0); + + switch(sig) { + case 0x0014: idx = 0x0; break; + case 0x0025: idx = 0x1; break; + case 0x0034: idx = 0x2; break; + case 0x0134: idx = 0x3; break; + case 0x0143: idx = 0x4; break; + case 0x0243: idx = 0x5; break; + case 0x0341: idx = 0x6; break; + case 0x0352: idx = 0x7; break; + case 0x1024: idx = 0x8; break; + case 0x1114: idx = 0x9; break; + case 0x1134: idx = 0xa; break; + case 0x1242: idx = 0xb; break; + case 0x1243: idx = 0xc; break; + case 0x1441: idx = 0xd; rev = 0; break; + default: return(-1); + } + if(rev) + idx += 0xe; + c = characters[0x51 + idx]; + dbprintf(2, " %02x => %02x", idx, c); + return(c); +} + +static inline unsigned char calc_check (unsigned char c) +{ + if(!(c & 0x80)) + return(0x18); + c &= 0x7f; + if(c < 0x3d) + return((c < 0x30 && c != 0x17) ? 0x10 : 0x20); + if(c < 0x50) + return((c == 0x4d) ? 0x20 : 0x10); + return((c < 0x67) ? 0x20 : 0x10); +} + +static inline signed char decode6 (zbar_decoder_t *dcode) +{ + int sig; + signed char c, chk; + unsigned bars; + + /* build edge signature of character */ + unsigned s = dcode->code128.s6; + + dbprintf(2, " s=%d", s); + if(s < 5) + return(-1); + /* calculate similar edge measurements */ + sig = (get_color(dcode) == ZBAR_BAR) + ? ((decode_e(get_width(dcode, 0) + get_width(dcode, 1), s, 11) << 12) | + (decode_e(get_width(dcode, 1) + get_width(dcode, 2), s, 11) << 8) | + (decode_e(get_width(dcode, 2) + get_width(dcode, 3), s, 11) << 4) | + (decode_e(get_width(dcode, 3) + get_width(dcode, 4), s, 11))) + : ((decode_e(get_width(dcode, 5) + get_width(dcode, 4), s, 11) << 12) | + (decode_e(get_width(dcode, 4) + get_width(dcode, 3), s, 11) << 8) | + (decode_e(get_width(dcode, 3) + get_width(dcode, 2), s, 11) << 4) | + (decode_e(get_width(dcode, 2) + get_width(dcode, 1), s, 11))); + if(sig < 0) + return(-1); + dbprintf(2, " sig=%04x", sig); + /* lookup edge signature */ + c = (sig & 0x4444) ? decode_hi(sig) : decode_lo(sig); + if(c == -1) + return(-1); + + /* character validation */ + bars = (get_color(dcode) == ZBAR_BAR) + ? (get_width(dcode, 0) + get_width(dcode, 2) + get_width(dcode, 4)) + : (get_width(dcode, 1) + get_width(dcode, 3) + get_width(dcode, 5)); + bars = bars * 11 * 4 / s; + chk = calc_check(c); + dbprintf(2, " bars=%d chk=%d", bars, chk); + if(chk - 7 > bars || bars > chk + 7) + return(-1); + + return(c & 0x7f); +} + +static inline unsigned char validate_checksum (zbar_decoder_t *dcode) +{ + unsigned idx, sum, i, acc = 0; + unsigned char check, err; + + code128_decoder_t *dcode128 = &dcode->code128; + if(dcode128->character < 3) + return(1); + + /* add in irregularly weighted start character */ + idx = (dcode128->direction) ? dcode128->character - 1 : 0; + sum = dcode->buf[idx]; + if(sum >= 103) + sum -= 103; + + /* calculate sum in reverse to avoid multiply operations */ + for(i = dcode128->character - 3; i; i--) { + zassert(sum < 103, -1, "dir=%x i=%x sum=%x acc=%x %s\n", + dcode128->direction, i, sum, acc, + _zbar_decoder_buf_dump(dcode->buf, dcode128->character)); + idx = (dcode128->direction) ? dcode128->character - 1 - i : i; + acc += dcode->buf[idx]; + if(acc >= 103) + acc -= 103; + zassert(acc < 103, -1, "dir=%x i=%x sum=%x acc=%x %s\n", + dcode128->direction, i, sum, acc, + _zbar_decoder_buf_dump(dcode->buf, dcode128->character)); + sum += acc; + if(sum >= 103) + sum -= 103; + } + + /* and compare to check character */ + idx = (dcode128->direction) ? 1 : dcode128->character - 2; + check = dcode->buf[idx]; + dbprintf(2, " chk=%02x(%02x)", sum, check); + err = (sum != check); + if(err) + dbprintf(1, " [checksum error]\n"); + return(err); +} + +/* expand and decode character set C */ +static inline unsigned postprocess_c (zbar_decoder_t *dcode, + unsigned start, + unsigned end, + unsigned dst) +{ + unsigned i, j; + + /* expand buffer to accomodate 2x set C characters (2 digits per-char) */ + unsigned delta = end - start; + unsigned newlen = dcode->code128.character + delta; + size_buf(dcode, newlen); + + /* relocate unprocessed data to end of buffer */ + memmove(dcode->buf + start + delta, dcode->buf + start, + dcode->code128.character - start); + dcode->code128.character = newlen; + + for(i = 0, j = dst; i < delta; i++, j += 2) { + /* convert each set C character into two ASCII digits */ + unsigned char code = dcode->buf[start + delta + i]; + dcode->buf[j] = '0'; + if(code >= 50) { + code -= 50; + dcode->buf[j] += 5; + } + if(code >= 30) { + code -= 30; + dcode->buf[j] += 3; + } + if(code >= 20) { + code -= 20; + dcode->buf[j] += 2; + } + if(code >= 10) { + code -= 10; + dcode->buf[j] += 1; + } + zassert(dcode->buf[j] <= '9', delta, + "start=%x end=%x i=%x j=%x %s\n", start, end, i, j, + _zbar_decoder_buf_dump(dcode->buf, dcode->code128.character)); + zassert(code <= 9, delta, + "start=%x end=%x i=%x j=%x %s\n", start, end, i, j, + _zbar_decoder_buf_dump(dcode->buf, dcode->code128.character)); + dcode->buf[j + 1] = '0' + code; + } + return(delta); +} + +/* resolve scan direction and convert to ASCII */ +static inline unsigned char postprocess (zbar_decoder_t *dcode) +{ + unsigned i, j, cexp; + unsigned char code = 0, charset; + code128_decoder_t *dcode128 = &dcode->code128; + dbprintf(2, "\n postproc len=%d", dcode128->character); + dcode->modifiers = 0; + dcode->direction = 1 - 2 * dcode128->direction; + if(dcode128->direction) { + /* reverse buffer */ + dbprintf(2, " (rev)"); + for(i = 0; i < dcode128->character / 2; i++) { + unsigned j = dcode128->character - 1 - i; + code = dcode->buf[i]; + dcode->buf[i] = dcode->buf[j]; + dcode->buf[j] = code; + } + zassert(dcode->buf[dcode128->character - 1] == STOP_REV, 1, + "dir=%x %s\n", dcode128->direction, + _zbar_decoder_buf_dump(dcode->buf, dcode->code128.character)); + } + else + zassert(dcode->buf[dcode128->character - 1] == STOP_FWD, 1, + "dir=%x %s\n", dcode128->direction, + _zbar_decoder_buf_dump(dcode->buf, dcode->code128.character)); + + code = dcode->buf[0]; + zassert(code >= START_A && code <= START_C, 1, "%s\n", + _zbar_decoder_buf_dump(dcode->buf, dcode->code128.character)); + + charset = code - START_A; + cexp = (code == START_C) ? 1 : 0; + dbprintf(2, " start=%c", 'A' + charset); + + for(i = 1, j = 0; i < dcode128->character - 2; i++) { + unsigned char code = dcode->buf[i]; + zassert(!(code & 0x80), 1, + "i=%x j=%x code=%02x charset=%x cexp=%x %s\n", + i, j, code, charset, cexp, + _zbar_decoder_buf_dump(dcode->buf, dcode->code128.character)); + + if((charset & 0x2) && (code < 100)) + /* defer character set C for expansion */ + continue; + else if(code < 0x60) { + /* convert character set B to ASCII */ + code = code + 0x20; + if((!charset || (charset == 0x81)) && (code >= 0x60)) + /* convert character set A to ASCII */ + code -= 0x60; + dcode->buf[j++] = code; + if(charset & 0x80) + charset &= 0x7f; + } + else { + dbprintf(2, " %02x", code); + if(charset & 0x2) { + unsigned delta; + /* expand character set C to ASCII */ + zassert(cexp, 1, "i=%x j=%x code=%02x charset=%x cexp=%x %s\n", + i, j, code, charset, cexp, + _zbar_decoder_buf_dump(dcode->buf, + dcode->code128.character)); + delta = postprocess_c(dcode, cexp, i, j); + i += delta; + j += delta * 2; + cexp = 0; + } + if(code < CODE_C) { + if(code == SHIFT) + charset |= 0x80; + else if(code == FNC2) { + /* FIXME FNC2 - message append */ + } + else if(code == FNC3) { + /* FIXME FNC3 - initialize */ + } + } + else if(code == FNC1) { + /* FNC1 - Code 128 subsets or ASCII 0x1d */ + if(i == 1) + dcode->modifiers |= MOD(ZBAR_MOD_GS1); + else if(i == 2) + dcode->modifiers |= MOD(ZBAR_MOD_AIM); + else if(i < dcode->code128.character - 3) + dcode->buf[j++] = 0x1d; + /*else drop trailing FNC1 */ + } + else if(code >= START_A) { + dbprintf(1, " [truncated]\n"); + return(1); + } + else { + unsigned char newset = CODE_A - code; + zassert(code >= CODE_C && code <= CODE_A, 1, + "i=%x j=%x code=%02x charset=%x cexp=%x %s\n", + i, j, code, charset, cexp, + _zbar_decoder_buf_dump(dcode->buf, + dcode->code128.character)); + if(newset != charset) + charset = newset; + else { + /* FIXME FNC4 - extended ASCII */ + } + } + if(charset & 0x2) + cexp = i + 1; + } + } + if(charset & 0x2) { + zassert(cexp, 1, "i=%x j=%x code=%02x charset=%x cexp=%x %s\n", + i, j, code, charset, cexp, + _zbar_decoder_buf_dump(dcode->buf, + dcode->code128.character)); + j += postprocess_c(dcode, cexp, i, j) * 2; + } + zassert(j < dcode->buf_alloc, 1, "j=%02x %s\n", j, + _zbar_decoder_buf_dump(dcode->buf, dcode->code128.character)); + dcode->buflen = j; + dcode->buf[j] = '\0'; + dcode->code128.character = j; + return(0); +} + +zbar_symbol_type_t _zbar_decode_code128 (zbar_decoder_t *dcode) +{ + code128_decoder_t *dcode128 = &dcode->code128; + signed char c; + + /* update latest character width */ + dcode128->s6 -= get_width(dcode, 6); + dcode128->s6 += get_width(dcode, 0); + + if((dcode128->character < 0) + ? get_color(dcode) != ZBAR_SPACE + : (/* process every 6th element of active symbol */ + ++dcode128->element != 6 || + /* decode color based on direction */ + get_color(dcode) != dcode128->direction)) + return(0); + dcode128->element = 0; + + dbprintf(2, " code128[%c%02d+%x]:", + (dcode128->direction) ? '<' : '>', + dcode128->character, dcode128->element); + + c = decode6(dcode); + if(dcode128->character < 0) { + unsigned qz; + dbprintf(2, " c=%02x", c); + if(c < START_A || c > STOP_REV || c == STOP_FWD) { + dbprintf(2, " [invalid]\n"); + return(0); + } + qz = get_width(dcode, 6); + if(qz && qz < (dcode128->s6 * 3) / 4) { + dbprintf(2, " [invalid qz %d]\n", qz); + return(0); + } + /* decoded valid start/stop */ + /* initialize state */ + dcode128->character = 1; + if(c == STOP_REV) { + dcode128->direction = ZBAR_BAR; + dcode128->element = 7; + } + else + dcode128->direction = ZBAR_SPACE; + dcode128->start = c; + dcode128->width = dcode128->s6; + dbprintf(2, " dir=%x [valid start]\n", dcode128->direction); + return(0); + } + else if(c < 0 || size_buf(dcode, dcode128->character + 1)) { + dbprintf(1, (c < 0) ? " [aborted]\n" : " [overflow]\n"); + if(dcode128->character > 1) + release_lock(dcode, ZBAR_CODE128); + dcode128->character = -1; + return(0); + } + else { + unsigned dw; + if(dcode128->width > dcode128->s6) + dw = dcode128->width - dcode128->s6; + else + dw = dcode128->s6 - dcode128->width; + dw *= 4; + if(dw > dcode128->width) { + dbprintf(1, " [width var]\n"); + if(dcode128->character > 1) + release_lock(dcode, ZBAR_CODE128); + dcode128->character = -1; + return(0); + } + } + dcode128->width = dcode128->s6; + + zassert(dcode->buf_alloc > dcode128->character, 0, + "alloc=%x idx=%x c=%02x %s\n", + dcode->buf_alloc, dcode128->character, c, + _zbar_decoder_buf_dump(dcode->buf, dcode->buf_alloc)); + + if(dcode128->character == 1) { + /* lock shared resources */ + if(acquire_lock(dcode, ZBAR_CODE128)) { + dcode128->character = -1; + return(0); + } + dcode->buf[0] = dcode128->start; + } + + dcode->buf[dcode128->character++] = c; + + if(dcode128->character > 2 && + ((dcode128->direction) + ? c >= START_A && c <= START_C + : c == STOP_FWD)) { + /* FIXME STOP_FWD should check extra bar (and QZ!) */ + zbar_symbol_type_t sym = ZBAR_CODE128; + if(validate_checksum(dcode) || postprocess(dcode)) + sym = ZBAR_NONE; + else if(dcode128->character < CFG(*dcode128, ZBAR_CFG_MIN_LEN) || + (CFG(*dcode128, ZBAR_CFG_MAX_LEN) > 0 && + dcode128->character > CFG(*dcode128, ZBAR_CFG_MAX_LEN))) { + dbprintf(2, " [invalid len]\n"); + sym = ZBAR_NONE; + } + else + dbprintf(2, " [valid end]\n"); + dcode128->character = -1; + if(!sym) + release_lock(dcode, ZBAR_CODE128); + return(sym); + } + + dbprintf(2, "\n"); + return(0); +} + +#undef NUM_CHARS + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "pdf417_hash.h" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2008-2009 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +/* PDF417 bar to codeword decode table */ + +#define PDF417_HASH_MASK 0xfff + +static const signed short pdf417_hash[PDF417_HASH_MASK + 1] = { + 0x170, 0xd8e, 0x02e, 0x000, 0xa21, 0xc99, 0x000, 0xf06, + 0xdaa, 0x7a1, 0xc5f, 0x7ff, 0xbcf, 0xac8, 0x000, 0xc51, + 0x49a, 0x5c7, 0x000, 0xef2, 0x000, 0x7dd, 0x9ee, 0xe32, + 0x1b7, 0x489, 0x3b7, 0xe70, 0x9c8, 0xe5e, 0xdf4, 0x599, + 0x4e0, 0x608, 0x639, 0xead, 0x0ac, 0x57c, 0x000, 0x20d, + 0x61b, 0x000, 0x7d1, 0x80f, 0x803, 0x000, 0x946, 0x093, + 0x79c, 0xf9c, 0xb34, 0x6d8, 0x4f1, 0x975, 0x886, 0x313, + 0xe8a, 0xf20, 0x3c9, 0xa92, 0xb90, 0xa1d, 0x091, 0x0ac, + 0xb50, 0x3af, 0x90a, 0x45a, 0x815, 0xf29, 0xb20, 0xb6d, + 0xc5c, 0x1cd, 0x1e2, 0x1bf, 0x963, 0x80b, 0xa7c, 0x9b7, + 0xb65, 0x6b7, 0x117, 0xc04, 0x000, 0x18e, 0x000, 0x77f, + 0xe0e, 0xf48, 0x370, 0x818, 0x379, 0x000, 0x090, 0xe77, + 0xd99, 0x8b8, 0xb95, 0x8a9, 0x94c, 0xc48, 0x679, 0x000, + 0x41a, 0x9ea, 0xb0e, 0x9c1, 0x1b4, 0x000, 0x630, 0x811, + 0x4b1, 0xc05, 0x98f, 0xa68, 0x485, 0x706, 0xfff, 0x0d9, + 0xddc, 0x000, 0x83f, 0x54e, 0x290, 0xfe7, 0x64f, 0xf36, + 0x000, 0x151, 0xb9b, 0x5cd, 0x961, 0x690, -1, 0xa7a, + 0x328, 0x707, 0xe6d, 0xe1f, -1, 0x6a0, 0xf3e, 0xb27, + 0x315, 0xc8c, 0x6de, 0x996, 0x2f9, 0xc4c, 0x90f, -1, + 0xaa7, 0x9e9, 0xfff, 0x0bb, 0x33b, 0xbc6, 0xe17, 0x000, + 0x85d, 0x912, 0x5f7, 0x000, 0xff1, 0xba1, 0x086, 0xa1e, + 0x85a, 0x4cf, 0xd47, 0x5a9, 0x5dc, 0x0bc, -1, 0x544, + 0x522, 0x1ff, 0xfa6, 0xa83, 0xc7d, 0x545, 0xd75, 0xb6f, + 0x284, 0xf11, 0xe46, -1, 0x900, 0x0f3, 0xe31, 0x705, + 0x06d, 0xd59, 0x67b, 0xe56, -1, 0xde2, 0x000, 0xd42, + -1, 0x24b, 0x000, 0xf87, 0x842, -1, 0xbb9, 0x065, + 0x626, 0x86a, 0x9f8, -1, 0x7ac, 0xe20, 0xbe9, 0x357, + 0xfff, 0xf82, 0x219, 0x9d4, 0x269, 0x8a6, 0x251, 0x0af, + 0xd02, 0x09a, 0x803, 0x0a5, 0xfed, 0x278, -1, 0x338, + 0x1e5, 0xcad, 0xf9e, 0x73e, 0xb39, 0xe48, 0x754, -1, + 0x680, 0xd99, 0x4d4, 0x80b, 0x4be, 0xb0d, 0x5f2, -1, + 0x4b1, 0x38a, 0xff5, 0x000, 0xa1b, 0xece, 0xa06, 0x8e6, + 0xdcb, 0xcb8, 0xc63, 0x98c, 0x346, 0x69c, 0x299, 0xa52, + 0xfff, 0x000, -1, 0x7b2, 0xbf8, 0x2d1, 0xaff, 0x2f2, + 0xd69, 0xf20, -1, 0xdcf, 0x9fb, 0x68f, 0x24e, 0xfd7, + 0xfdb, 0x894, 0xc8f, 0x615, 0xa25, 0x36d, 0x1bb, 0x064, + 0xb80, 0x280, 0xd7a, -1, 0xd75, 0xc90, 0xdce, 0xdce, + 0x011, 0x869, 0xb2f, 0xd24, 0xe26, 0x492, 0xe0a, 0xcae, + -1, 0x2ac, 0x38c, 0x0b9, 0xc4f, -1, 0x32b, 0x415, + 0x49c, 0x11c, 0x816, 0xd08, 0xf5c, 0x356, 0x2b3, 0xfbf, + 0x7ff, 0x35d, 0x276, 0x292, 0x4f5, 0x0e2, 0xc68, 0x4c4, + 0x000, 0xb5e, 0xd0b, 0xca7, 0x624, 0x247, 0xf0d, 0x017, + 0x7ec, 0x2a6, 0x62c, 0x192, 0x610, 0xd98, 0x7a4, 0xfa3, + 0x80b, 0x043, 0xd7b, 0x301, 0x69d, 0x7e4, 0x10c, 0xacb, + 0x6eb, 0xea7, 0xe65, 0x75d, 0x4f5, 0x5b0, 0xa50, 0x7b6, + 0x0ec, -1, 0xcf9, 0x4b4, 0x639, 0x111, 0xbdf, 0xe89, + 0x9fa, 0x76b, 0xdf6, 0x2d0, 0x857, 0x3a3, 0x000, 0xa3e, + 0x8cb, 0x35f, 0x4f0, 0x022, 0xb38, 0xc12, 0x93c, 0x2fc, + 0x546, 0xe6e, 0x91f, 0x145, 0xfff, 0x1af, 0x957, 0xbde, + 0x09d, 0xfd2, 0x9df, 0x2dc, 0x07f, 0x115, 0x7bf, 0xa35, + 0x061, 0x9bf, 0xc85, 0x918, 0x0c8, 0x317, 0xce5, 0xf28, + 0x108, 0x51b, 0x621, 0x188, 0x000, 0x28c, 0xf67, 0x6ef, + 0x000, 0xd72, 0xce2, 0x1be, 0x000, 0x000, 0x282, 0x357, + -1, 0x4e5, 0x246, 0x859, 0x66c, 0x5d3, 0x9fd, 0x000, + 0x000, 0x82f, 0xc29, 0x331, 0xa93, 0x000, 0xae4, 0x48a, + 0x254, 0x000, 0x0ba, 0xe83, 0x7c7, 0xb6e, 0x88e, 0x774, + 0xf6f, 0x85d, 0x47f, 0xcd6, 0xe41, 0xdb6, 0x000, 0x0f4, + 0xb4d, 0x77f, 0x000, 0x901, 0x1a2, 0x44a, 0x482, 0x000, + 0xe99, 0xa75, 0x000, 0x7ab, 0x000, 0x0b6, 0x35c, 0x306, + 0x11c, 0x08e, 0x6eb, 0x11c, 0x771, 0xff9, 0x1c8, 0x63b, + 0x58b, 0x9d2, 0x250, 0x198, 0xfe7, 0xebc, 0x000, 0xa97, + 0xacc, 0xd4b, 0x28b, 0x892, 0x150, 0xcf4, 0xbc1, 0x000, + 0x662, 0xdd8, 0x61f, 0x903, 0x083, 0x000, 0xc55, 0x02f, + 0xc29, 0x4f5, 0xbcf, 0xe27, 0x9e3, 0xb13, 0xadc, 0x845, + 0x415, 0x0ae, 0x000, 0xe30, 0x931, 0x84a, 0xb09, 0x250, + 0x631, 0x7aa, 0x026, 0xdc9, 0x486, 0x3a7, 0xab0, 0xe04, + 0xe1a, 0xe17, 0x611, 0x556, 0xfac, 0x3c6, 0x5ab, 0x002, + 0xc16, 0xe60, -1, 0xc51, 0x772, 0x67f, 0xfa9, 0x83c, + 0x974, 0x96a, 0xe94, 0x250, 0xa20, 0xc95, 0x65b, 0x479, + 0xe48, 0xa35, 0x23f, 0x5cf, 0x40a, 0xcf0, 0xe82, 0x1da, + 0x390, 0xc86, 0xa92, 0x433, 0xbed, 0x4a7, 0x09a, 0x15a, + 0xb8d, 0x9c7, 0x5fb, 0x8a0, 0x000, 0xf9a, 0xf3c, 0x11c, + 0x20c, 0xf23, 0x79d, 0xc79, 0xb71, 0x7af, 0xc5b, 0x771, + 0x629, 0x834, 0xb34, 0x20c, 0x940, 0x2ca, 0x60b, 0x000, + 0x4cb, 0x70b, 0x000, 0x000, 0x9e8, 0x000, 0xdca, 0x000, + 0x1ae, 0xb21, 0xfe3, 0x191, 0x9e1, 0x7f6, 0x04f, 0x64a, + 0xba2, 0x59e, 0x1ae, 0x000, 0x728, 0x000, 0x081, 0xecd, + 0x946, 0x000, 0xdee, 0x3ff, 0xdf9, 0x1bf, 0x01a, 0x1a9, + 0xc58, 0xe05, 0x3bf, 0x5e8, 0x39d, 0xbfa, 0x23f, 0xb8d, + -1, 0x000, 0x779, 0x540, 0xf2c, 0x7cc, 0x340, 0x77a, + 0xa8e, 0xe8d, 0x2fd, 0xfed, 0x5d1, 0x308, 0x00f, 0xf4a, + 0x39b, 0xbe2, 0x0e5, -1, 0xf4d, 0x1fe, 0xf00, 0x867, + 0x195, 0x2de, 0x712, 0x000, 0x00c, 0x0a3, 0x1f3, 0x4ee, + 0x317, 0x665, 0x000, 0x5d8, 0x291, 0x6c4, 0xa46, 0x492, + 0x8d4, 0x647, 0x57f, 0x000, 0x259, 0xd87, 0x5c2, 0x1d8, + 0xfad, -1, -1, 0x79f, 0x43a, 0xfd1, 0x164, 0x6e1, + 0x350, 0xf00, 0x0e9, 0xac4, 0xe35, 0x307, 0xfff, 0xabb, + 0xc1a, 0x768, 0x000, 0x372, 0x839, 0xf4b, 0x1c3, 0xab0, + 0xcb6, 0x943, 0xbe9, 0x20f, 0xddc, 0xe18, 0x4eb, 0x21d, + 0x530, 0x24c, 0x000, 0xf79, -1, 0x1bd, -1, 0x155, + 0x435, -1, 0x132, 0x5c2, 0xb3d, 0x802, 0x733, -1, + 0x336, 0xf19, 0xfea, 0xd2a, 0x07f, 0x8e9, 0x000, 0xdab, + -1, 0x088, 0x4b1, 0x7ac, 0x000, 0xe66, 0xde0, 0x73c, + 0xfff, 0x02f, -1, 0x000, -1, 0x000, 0x562, 0x389, + 0xb20, 0x9ea, -1, 0x3f8, 0x567, 0x035, 0xa55, 0x255, + 0xc98, 0x65f, -1, 0x1ac, 0x571, 0x13d, 0xf57, 0x32a, + 0xbdb, 0x0ec, 0x47d, 0x43a, -1, 0x1aa, 0x9d6, 0x843, + -1, 0x244, 0xb03, 0xd0d, 0x579, 0x1b1, 0xea7, 0x000, + 0x062, -1, 0x533, 0x1db, 0xf1f, 0x2f7, 0x2df, 0x3e5, + 0xdec, 0xc5c, 0x55a, 0xf6c, 0x4c1, 0x5a8, 0xcd4, 0x6fd, + 0x1a6, 0x4b8, 0x98a, 0xe17, 0xeb9, 0xfd1, -1, 0x175, + 0x4d6, 0xba2, 0x000, 0x614, 0x147, 0x429, 0xfee, -1, + 0x0d8, -1, 0x98a, 0xdd2, 0xedd, 0x255, 0xef3, 0x345, + 0x000, 0xf3e, -1, -1, 0x210, 0x88a, 0x699, -1, + 0x02c, 0xfee, 0x1c1, 0xb38, 0x000, 0x7cc, 0x165, 0x536, + -1, 0x1ae, 0xefb, 0x734, -1, 0x1a4, 0x984, 0x804, + 0x487, -1, -1, 0x31e, 0x9f2, 0x966, 0x000, 0xcb0, + 0x552, 0x0c9, -1, 0x750, 0x650, 0x064, 0xffe, 0xe84, + 0x537, 0xee7, 0x834, -1, 0x998, 0xa03, -1, 0xcdf, + 0x4be, 0x310, 0x051, 0xf3f, 0x040, 0x973, 0x925, 0x000, + 0x000, 0xe51, 0x8b1, 0x468, 0xe11, 0xd4f, 0x374, 0x33a, + 0x126, 0x88b, 0x43a, 0xc9b, 0xdb9, 0x3c2, 0x3bd, 0x1ae, + 0x000, 0xc4a, 0x000, 0x4c4, 0x859, 0xe5a, 0x000, 0xeb4, + 0xd40, 0x87d, 0xc79, 0xe13, 0x50b, -1, 0x724, 0x000, + 0x7be, 0x062, 0xe7f, 0xad0, 0x5f3, 0x69e, 0x381, 0x272, + 0x50f, 0xac8, 0x053, 0x55e, 0xf19, 0xd71, 0x75b, 0xbf2, + 0x000, 0x3ac, 0xdf0, 0xd75, 0x7e3, 0xe75, 0xa13, 0xfd8, + 0xbdc, 0x1d9, 0x15f, 0x8cc, 0xba4, 0xb79, 0xb7f, 0x812, + 0xfe6, 0x000, 0x2d3, 0xd7b, 0x5d4, 0xad2, 0x316, 0x908, + 0x323, 0x758, 0xb0b, 0x965, 0x1a9, 0xdce, 0x660, 0x625, + 0xeff, 0x0ed, 0x000, 0x323, 0x986, 0x831, 0x5c5, 0x22f, + 0xd49, 0xec6, 0x90e, 0x234, 0x000, 0x80f, 0x16c, 0x528, + 0x1f8, 0x2bd, 0x97d, 0xe20, 0xf29, 0x97d, 0x3a0, 0x7fc, + 0x086, 0x720, 0x1f9, 0x3eb, 0xf67, 0x423, 0xa55, 0x69e, + 0xede, 0x206, 0x7fa, 0x809, 0xfa8, 0xe22, 0x15e, 0x2a0, + 0x04a, 0xf7b, 0x4ea, 0xd9a, -1, 0x1d8, 0x0b4, 0xb87, + 0x406, -1, 0xcdf, 0x187, 0xf6d, 0x914, 0x4b1, 0x000, + 0x104, 0x67e, 0xc74, 0x6da, 0xe67, 0x7d2, 0xd1f, 0x64c, + 0x19d, 0x000, 0xa17, 0xfd5, 0x000, 0x8ad, 0xf38, 0xd65, + 0xabd, 0x75e, 0x667, 0x632, 0x346, 0xc48, 0xa77, 0x45e, + 0x2b5, 0xded, 0x7da, 0x160, 0x560, -1, 0xf4e, 0xb0c, + 0xdb0, 0x287, 0x34a, 0x065, 0x439, 0x2ec, 0x679, 0xefa, + 0x208, 0xeb1, 0x1b0, 0x8c8, 0xca6, 0x62c, 0xa10, 0x673, + 0x000, 0x000, 0xc6a, 0x7b2, 0xbd7, 0xb2b, 0x17a, 0x6f3, + 0x1ab, 0xffa, 0x5e0, 0x1fa, 0xb8f, 0xe5c, 0xcab, 0xdbc, + 0x10f, 0x000, 0x000, 0xefe, 0x34b, 0x1d9, 0x834, 0x52f, + 0xb58, 0x82b, 0x6e8, 0x1f3, 0x719, 0x64e, 0xf55, 0xccd, + 0x531, 0x0de, 0x3aa, 0x150, 0x89a, 0x3b9, 0x26e, 0xebc, + 0x7ae, 0x670, 0x315, 0x8a9, 0x03b, 0x896, 0x247, 0x2f4, + 0x450, 0xd10, 0xb79, 0x0ed, 0x041, -1, 0x707, 0x9e1, + 0xed6, 0x6d2, 0x000, 0xfff, 0xb1a, 0x084, 0xaf3, 0x47f, + 0x02f, 0xac3, 0x751, 0x8c4, 0x291, 0xadd, 0x000, 0xea1, + 0x8ec, 0xf9f, 0x5c2, 0x000, 0xd6b, 0x71e, 0x000, 0xcea, + 0x971, 0x5f8, 0x4b9, 0x7c6, 0xb7e, 0x353, 0xd25, 0x423, + 0x6ec, 0xb71, 0xf93, 0x000, 0x795, 0xc43, 0xaa2, 0x96a, + 0xcbd, 0xb55, 0x184, 0xdf0, 0x3d9, 0xbfe, 0xf79, 0x8f0, + 0x22c, 0xeeb, 0x000, 0xa4b, 0xe07, 0xf34, 0xc9d, 0x4be, + 0x95b, 0x371, 0x78c, 0x9e9, 0xde6, 0x072, 0xf0d, 0x60b, + 0x5a5, 0xab1, 0x000, 0x260, 0x000, 0xd2a, 0xd90, 0x154, + 0x4c6, 0x438, 0x5d9, 0x736, 0x062, 0x000, 0x000, 0xb84, + 0x72e, 0x0b7, 0x000, 0x050, 0x063, 0xa95, 0x89b, 0x917, + 0x049, 0xb14, 0x9a0, 0x734, 0x0c3, 0xd50, 0x917, 0xb02, + 0x8cf, 0x453, 0x0af, 0x8e5, 0x000, 0x7aa, 0x5d5, 0x81b, + 0x788, 0xb9c, 0x01a, 0x974, 0x000, 0x000, 0x37f, 0xd9f, + 0x000, 0xec4, 0x4f4, 0xbff, 0x4fe, 0x860, 0x11c, 0x74e, + 0x34a, 0x281, 0x52f, 0xb05, 0xa89, 0xbee, 0x6ad, 0x9fc, + 0x9ba, 0xb0b, 0x515, 0x1c7, 0x330, 0xfde, 0x97e, 0x6e7, + 0xc45, -1, 0x658, 0x710, 0x28a, 0x921, 0x1de, 0x4a1, + 0x9d7, 0xe32, 0xa2d, 0xb0f, 0x545, 0xd6f, 0x329, 0x9b8, + 0xb4d, 0x9a0, 0x938, 0x783, 0xfa7, 0xd0a, 0xdc9, 0x0fe, + 0x000, 0x249, 0x000, 0x8cd, 0x922, 0x7cd, 0x021, 0xa89, + 0x3d5, 0xcee, 0x0a1, 0x6d6, 0x000, -1, 0x48b, 0x000, + 0x87a, 0x8bb, 0x9ed, 0x01f, 0xe20, 0xb7f, -1, 0xe95, + 0x593, 0x1da, 0x57a, -1, 0xf3a, 0x000, 0x000, -1, + -1, 0x160, 0x501, 0x7a3, 0xb59, -1, -1, 0xc7f, + -1, 0xf79, -1, -1, 0x48d, 0x781, -1, -1, + 0xb74, -1, 0x3c4, 0xbe9, -1, -1, 0x9a4, 0x9ae, + 0xa75, -1, -1, 0x9cd, 0x000, -1, -1, -1, + 0xc3c, 0x2d4, -1, 0x173, 0xf38, 0x000, -1, 0xee9, + -1, 0xb91, 0xcc1, 0x86d, 0x8ab, 0xeb0, 0xec7, 0x687, + 0xd98, 0xa95, 0x744, 0xe7c, 0x826, 0x80e, 0x599, 0x3d9, + 0xf2f, -1, 0x96a, 0xfd1, 0x174, -1, 0x000, 0x1aa, + 0x50e, -1, 0x5a2, 0xbcd, 0x000, -1, 0x019, 0x588, + 0x18d, 0x470, 0x812, 0xeec, 0xf63, 0x05c, -1, 0x000, + 0xb7f, 0x357, 0x436, 0xbb4, 0x1fb, 0x425, 0x1ed, 0xe13, + 0x66c, 0x555, 0xb11, 0x7b5, 0x48d, 0x38d, 0xf72, 0x000, + 0x000, 0xa66, 0x4fa, 0xf36, 0x1eb, 0x000, 0x95f, 0x000, + 0xd9a, 0x82f, 0x07f, 0x253, 0x70f, 0x915, -1, 0x12d, + 0x040, 0x2ca, 0x446, 0x90a, 0x7a8, 0x687, 0x000, 0x04e, + 0x74f, 0x1ca, 0x793, 0x3c7, 0x3f0, 0x4c7, 0x000, 0xc30, + 0x533, 0x889, 0x9ef, 0xebd, 0x984, 0x18f, 0xfe1, 0x8ea, + 0x185, 0x410, 0x107, 0x000, 0x73e, 0xd4b, 0x8fc, 0xd34, + 0x1e6, 0x4bf, 0xbac, 0x7c3, 0x000, 0x7c8, 0xb2f, 0x02c, + 0xa46, 0x000, 0x0f9, 0x680, 0x94d, 0x6ad, 0x767, 0xfeb, + 0x6c7, 0x2d5, 0x43f, 0x9af, 0x261, 0xe83, 0xfa7, 0xb7b, + 0xf2d, 0x2f5, 0x4d7, 0x494, 0xbc2, 0x45b, 0x000, 0x17d, + 0x5c6, 0xe2b, 0xb20, 0x19e, 0x6ba, 0x973, 0xedd, 0xea8, + 0x000, 0x9f3, 0xd9a, 0x7fa, 0xb78, 0x556, 0xbb6, 0xc58, + 0x210, 0x000, 0xf9a, 0x56d, 0x48b, 0xf12, 0x000, 0x54d, + 0x5f4, 0x1ad, 0x86e, 0xe16, 0x6ff, 0xa35, 0x47e, 0x4c7, + 0x93c, -1, -1, 0xc98, 0xd3f, 0x000, 0x788, 0x6ef, + 0x959, 0xec2, 0x45e, 0xa4d, 0xa90, 0x000, 0x768, 0x8bb, + 0x6ee, 0x7f5, 0x770, 0xfa8, 0xba4, 0xf49, 0x7b8, 0x616, + 0x2bd, 0x23f, 0xe8c, 0x9fa, 0xa49, 0x213, 0x98a, 0x2c1, + 0x595, 0x885, 0x6de, 0x057, 0x1bc, 0x000, 0xc58, 0x7a8, + 0x5c1, 0x3d0, 0xa78, 0xb80, 0x000, 0xc06, -1, 0x428, + 0xe92, 0xfa3, 0x341, -1, 0x000, 0x000, 0x1ca, 0x27c, + 0xdeb, 0x835, 0x4c8, 0xdb3, 0x000, 0xf9d, 0x000, 0xe81, + 0xc22, 0xfce, -1, 0xe6e, 0x96e, 0x161, -1, 0x3b9, + 0x945, 0xa95, 0x13d, 0x748, 0x184, 0x588, 0x636, 0xf7e, + 0xb44, 0x2b7, 0x217, 0xee5, 0x65a, 0xc47, -1, 0xca3, + 0x83e, 0x431, 0xc64, 0x636, 0x06e, 0x404, 0x993, -1, + 0xeb3, 0x134, 0x8a3, 0xca9, -1, -1, 0x2ab, 0x000, + 0x8ed, 0x877, 0x1a8, 0xc89, 0x000, 0x000, 0xf94, 0x000, + 0x709, 0x249, 0x9ac, 0x22a, 0x605, 0x000, 0x000, 0x6b4, + 0x00c, 0xc53, 0xf23, 0x005, 0x29f, 0x865, 0xf79, 0x000, + 0x5fa, 0x764, 0xe51, 0xbdc, 0xb64, 0x0f3, 0xf29, 0x2f7, + 0x5da, 0x000, 0x16f, 0xb8b, 0x255, 0x9cc, 0xe43, 0x279, + 0x2c2, 0x483, -1, 0xf7d, 0x7bb, 0x000, 0x9e3, 0xd84, + 0xe36, 0x6e6, 0x000, -1, 0x33f, 0x41d, 0x5b5, 0x83e, + 0x2f4, 0xf5b, 0x9fc, 0xb1e, -1, 0x8f4, 0xb26, 0x856, + 0x3b6, 0x126, 0x4c2, 0x274, 0x0c1, 0xfa9, 0x57d, 0x000, + 0x100, 0x7af, 0xc62, 0x000, 0xa55, 0x416, 0x93f, 0x78c, + 0xfba, 0x5a2, 0x0c2, 0x4d4, 0xa3e, 0xcc3, 0xe73, 0xd02, + 0x8df, 0x3e9, 0xe9a, 0x0f6, 0x32c, 0x23d, 0xdab, 0xf50, + 0xfc2, 0x000, 0x065, 0xc23, 0xd3d, 0xc84, 0x35e, 0x000, + 0xa24, 0x634, 0x4b4, 0xa52, 0x098, 0xb39, 0x9a4, 0xe71, + 0x8aa, 0x741, 0x000, 0xb16, 0x5c2, 0xea1, 0xc01, 0x5c1, + 0x30d, 0xca4, 0x201, 0xc9c, 0x717, 0x000, 0xba0, 0x537, + 0x619, 0x000, 0xfd9, 0x6dc, 0xdaa, 0x1da, 0xe51, 0xd39, + 0xb4c, 0x8a1, 0x098, 0x2f8, 0x191, 0x9dc, 0xdb0, 0x5e1, + 0x000, 0xe97, 0xef1, 0x8d3, 0xb0d, 0xfce, 0x336, 0xee1, + 0x7a2, 0xbc8, 0x494, 0x580, 0xba7, 0x000, 0x62a, 0x96a, + 0x527, 0x859, 0x811, 0xef0, 0x429, 0xef4, 0xf3d, 0x000, + 0x9d6, 0xb71, 0x000, 0x14b, 0xf3d, 0xb16, 0x204, 0x0c1, + 0xcd4, 0x339, 0x39d, 0xfe3, 0x837, 0x8c7, 0x955, 0x69a, + 0x5f6, 0x4c6, -1, 0x3d5, 0x000, 0x0e7, 0x4b1, -1, + 0xa3e, 0xb03, 0x1ea, 0xac8, -1, 0x000, 0xed8, -1, + 0x4e0, 0x9f7, 0xc91, 0x6b3, -1, -1, 0xa53, 0x290, + 0xa64, 0x0e3, 0x3dc, 0xed3, 0xf2f, 0x000, 0xd7c, 0xf44, + -1, 0x205, 0x900, 0x864, -1, -1, 0xed3, 0x7d2, + 0x000, -1, 0xdd2, 0x79b, 0x000, -1, 0xae6, 0x5cf, + 0xde8, 0x000, 0x1f2, -1, 0x2f3, 0x000, -1, 0x2ce, + 0xcf2, 0x8f4, 0xee8, 0x165, 0x309, 0x15f, -1, 0x714, + 0xbfc, 0x532, 0xad0, 0x151, 0x2d5, 0x0a4, 0x391, -1, + 0x0dc, 0x0c1, 0x451, -1, -1, 0x6a0, 0x250, -1, + 0xab8, 0x977, 0xa86, 0x407, 0x72f, -1, 0x05f, 0x000, + 0xefe, 0x950, 0x4f4, 0x957, -1, 0xd68, 0x26c, 0xa30, + 0x4f1, 0x279, 0x584, 0xb34, -1, 0x4d7, 0x258, 0x000, + 0x518, 0x685, 0x91c, 0x3ac, 0x0fa, -1, 0x979, 0x40c, + 0x506, 0x000, -1, 0x7bd, 0xb97, 0x87f, 0xc06, 0x050, + 0x7bf, 0xe3e, 0xc81, 0x000, 0x65e, 0x000, -1, 0xb76, + 0xc37, 0x4c4, 0xfc9, 0x336, 0x9fa, 0xaa2, 0x32c, 0xb8b, + 0xaa9, 0xc95, 0x85a, 0xa9a, 0x260, 0x4cd, 0x8fe, 0xd3c, + 0x982, 0x0d7, 0xbc1, 0xdcf, 0xe62, 0xe0d, 0xf8f, 0xd7b, + 0x91a, 0x3e0, 0x33a, 0x1c5, 0xf00, 0xde5, 0xad1, 0xebc, + 0xebc, 0x942, 0xd86, 0x3bf, 0x8ce, 0xb8c, 0x000, 0x8d6, + 0x784, 0xb74, -1, 0x818, 0x000, 0xfff, 0x07e, 0x029, + 0xf48, 0xb65, 0xd81, 0x220, 0x095, 0x21f, 0xac4, 0xb31, + -1, 0x864, 0x000, 0x3bd, 0xf85, 0x237, 0x369, 0x2d9, + 0xfdf, 0x25a, 0x782, 0x7b8, 0xabd, 0x5e3, 0x438, 0x230, + 0xbc4, 0x7ad, 0x00a, 0x441, 0x6dc, 0x2c4, 0xf16, 0x0b3, + 0x04c, 0xfd2, 0x8aa, 0xad8, 0x3e4, 0x142, 0x585, 0xc8f, + 0x9bf, 0x29b, 0xac9, 0x743, 0xfb5, 0x7fc, 0x05e, 0xd38, + 0x002, -1, 0xb4e, 0xd0c, 0x84c, 0xf93, 0x91f, 0xcd2, + 0x04f, 0x569, 0xd1b, 0xfc6, 0x630, 0x6f6, 0x1d8, 0x91a, + 0x4da, 0x9f5, 0x07a, 0xcf5, 0x634, 0x42f, 0xfff, 0x951, + 0x0f9, 0xc01, 0x491, 0xbd6, 0x730, 0xfea, 0x9f4, 0xbfc, + 0xf1a, 0x413, 0xa2a, 0xdc6, 0xc87, 0x9db, 0xc2c, 0x30f, + 0xdb5, 0x785, 0xbaa, 0x000, 0x000, 0xa49, 0x000, 0x61d, + 0xf6f, -1, 0x031, -1, 0x441, 0x7bf, 0x53e, -1, + 0x6fd, 0x0f6, -1, 0xadb, -1, 0x000, 0x432, 0x187, + 0xd37, 0x154, 0x539, 0xc08, 0xe51, 0x219, 0x1e9, 0x897, + 0xa0e, 0x201, 0x447, 0x89f, 0x000, 0x463, 0x726, 0xa05, + 0xab9, 0xd01, 0x1e4, 0xfea, 0x895, 0x816, 0x313, 0xae3, + 0x3a4, -1, 0x70f, -1, 0xa42, 0x5e9, 0x78e, -1, + 0x317, 0x6c8, 0x000, 0xbf7, 0xefd, -1, 0xb17, 0x382, + 0xd26, 0x5ff, 0xf81, 0x20b, 0x373, 0x774, 0x081, 0xaae, + 0xfdb, 0xe5d, -1, -1, 0xcb7, 0x738, 0x919, 0x933, + 0x398, 0x000, 0x14e, -1, 0xe14, 0xbf8, 0x11c, 0x94b, + 0x031, -1, 0x000, 0x2d4, 0xd41, 0xdc6, 0x9f6, 0xea7, + 0x9e8, 0x2ec, 0x10a, 0x50d, 0xeae, 0xdb0, 0xef0, 0x9c8, + 0x000, -1, 0x82e, 0x9d3, 0xdb7, 0x46d, -1, 0x230, + 0x73b, 0x45b, -1, -1, 0x000, 0x4a7, -1, -1, + 0x47c, 0x10e, 0x4b4, -1, -1, -1, 0x1d7, 0xa5d, + 0x233, 0x6b2, 0x6bd, 0x387, 0x7ca, 0xb1a, 0xf75, 0xea4, + 0xdc9, 0x98b, 0x80c, 0x702, 0xe22, 0xa6e, 0x6f8, 0x05b, + 0x17c, -1, 0x000, 0xebe, 0xc8e, 0xaec, -1, 0x42b, + 0xdce, -1, -1, -1, 0xef3, 0xc52, 0x31b, -1, + 0xdff, 0xbd0, 0x000, 0xa72, 0x525, 0x9cf, 0x2ff, 0xfc8, + 0x37c, 0xbce, 0xd8c, 0xd88, 0x3a6, 0xed8, 0x4ab, 0x000, + 0x449, 0x9d7, -1, -1, 0x9be, 0x59f, 0x000, 0x882, + -1, 0x742, 0x000, -1, -1, -1, 0xe8b, 0x0f3, + 0x771, -1, 0x3ea, 0x8f9, 0xcbb, 0x548, 0x46d, 0x000, + -1, 0xf74, 0xa23, 0x15b, -1, 0xaeb, 0x7f8, 0xbe2, + 0x000, -1, 0x023, 0x61e, 0x95d, 0x7ac, 0x024, 0x141, + 0x561, 0x9fe, 0xb10, -1, 0x623, 0xc47, 0x413, 0x0e7, + 0x663, 0xcdf, 0xebe, 0x5c9, 0x573, 0x21d, 0xb28, 0x280, + 0xb9f, 0xd1a, 0xecf, 0xff0, 0x000, 0xfc0, 0x085, 0x9c4, + 0x48c, 0x000, 0xb0b, 0x43d, -1, 0x73b, 0x802, 0x633, + 0x6ef, -1, -1, 0x5c1, 0xea6, 0x0a9, 0xab4, 0xacd, + 0xb81, 0xa32, -1, -1, 0xa26, 0x9d5, 0xf7c, -1, + 0xf69, 0xdbb, 0x6d5, 0x405, -1, 0xd0a, 0xfe0, 0xf5e, + 0xbd7, -1, 0x89a, 0x868, 0xeb2, 0x792, 0x7fe, 0x115, + 0x000, 0x8bb, 0xdd1, 0xc40, 0x453, 0xbb3, 0x7cc, 0x3e6, + 0x071, 0x0f1, 0xbae, 0xf67, 0x896, 0x38e, 0x86e, 0xfaa, + 0xccc, -1, 0x411, 0x8e5, 0x699, 0x2ef, 0x785, 0x9d4, + 0xe30, 0xb2e, 0x976, 0x203, 0x035, 0x75d, 0x8f1, 0x144, + 0x092, 0x1a5, -1, 0x55f, 0x000, 0xa43, 0x5be, 0x68d, + 0x852, 0xb87, 0x9af, 0x0c0, -1, 0xa50, 0x9ca, 0x15f, + 0xf06, 0x869, 0x0f3, -1, 0x000, -1, 0x9a9, -1, + -1, -1, -1, 0xf05, 0x000, -1, 0x000, 0x4a9, + 0xf9d, -1, 0x000, 0xab1, 0x04c, -1, 0xd17, 0x893, + 0x763, 0x332, -1, 0xc41, 0x5bd, 0xa72, 0x67c, 0xb78, + 0x973, 0x6c7, 0x569, -1, 0x96a, 0xc68, 0x48c, -1, + 0x6fa, -1, 0xa2a, 0x44f, -1, 0x73f, 0x28f, 0x536, + 0xd91, 0xc86, 0xef8, 0x1f5, 0xfb4, 0x060, 0x230, 0xe10, + -1, 0x000, 0x305, 0x0e6, 0xb19, 0x1e2, 0x7fc, 0xf35, + -1, 0x7d9, 0x000, 0x000, 0xd2f, 0xb3a, 0x0a2, 0x7c9, + 0xda6, 0x37c, 0xe43, -1, 0x7da, 0x0d6, 0x000, -1, + 0xd40, -1, 0x156, 0xee9, -1, 0x239, 0x10f, 0x60c, + 0x9d4, 0x663, 0x565, 0x603, 0x38b, -1, 0x606, 0x13c, + 0x681, 0x436, 0xc29, 0x9c7, 0x1d9, 0x000, 0x0a6, 0x996, + 0x231, 0x055, 0x01f, 0x0a3, 0xd96, 0x7c8, 0x0f3, 0xaa7, + 0xd99, -1, 0x3be, 0x476, 0x25f, 0x38c, 0xdf3, 0x6d5, + 0xcb5, 0xadd, 0x000, 0x136, 0x64d, 0xc0d, 0xe61, 0xd0b, + -1, 0x000, 0x535, 0x9c3, 0x279, 0x00c, 0xa87, 0xa31, + 0xc4a, 0x167, 0x423, 0xec8, -1, 0x926, 0xa4d, 0x5ba, + -1, -1, 0x9bf, 0x000, 0x47f, 0x8f3, 0xd5b, 0xc3b, + 0xa18, -1, 0x548, 0x8f7, 0x8cf, 0x000, 0x9bd, 0xaa2, + 0x7ec, 0x000, 0xfb8, 0xafd, 0xe68, -1, 0xfa7, 0x31c, + 0xef3, 0x288, 0xdf0, 0x1bc, 0xfe9, 0x1ea, 0xa9f, 0x000, + 0x53f, 0x000, 0xda6, 0x09c, 0x1bf, 0x09c, 0x31c, 0x0c8, + 0x31c, -1, 0x689, 0x211, -1, 0x77f, 0x723, 0xb8f, + 0x683, 0x351, -1, 0xb33, 0xce0, -1, 0x61c, 0x073, + 0x783, 0x6b2, 0x6a8, 0x729, 0x81b, 0xabf, 0xd15, 0x563, + 0x433, -1, 0x823, 0xf99, 0x2c5, -1, 0x367, 0xcc4, + 0x934, 0x6f2, 0xdf0, 0xa1f, 0x579, 0x012, -1, 0x508, + 0x070, -1, 0x018, 0x270, 0xa6f, -1, 0x7a7, -1, + 0x826, 0x4b5, -1, 0x7d8, 0xb20, -1, 0xc28, 0x463, + -1, 0xdf9, 0x22c, 0xe17, 0x4f2, 0xe13, 0x4ff, 0x40a, + 0xdcb, 0x9ed, 0x34a, 0xeb8, 0xa0e, 0x5f2, 0x594, 0x60d, + 0x4b6, 0xd3c, 0x675, 0x1c4, 0xbb5, 0xc73, 0xfad, 0xead, + -1, 0xfb6, -1, 0x146, 0xd40, 0x02f, 0x000, 0x302, + -1, -1, 0x6e5, 0x000, 0xed7, 0xd8c, 0x7a3, 0x0fc, + 0x259, 0x34b, 0xa1b, 0x882, -1, 0x211, 0x000, 0xd30, + 0xe02, 0x5cd, 0x53e, 0x11b, 0xa16, -1, 0x24e, -1, + 0xace, 0xe9a, -1, 0x5c6, 0x9be, 0x000, 0x169, 0x982, + -1, 0x3fd, 0x457, 0x06f, 0x7e7, 0xed1, 0x5ee, 0xcef, + 0x62b, 0x26c, 0xc9f, 0xe68, 0x59f, 0x0b5, 0x000, 0x0bc, + 0x086, 0x890, 0x005, 0xc42, 0x939, 0xaca, 0xdd9, -1, + -1, 0x6e5, 0x0dd, 0x434, 0x297, 0xe21, 0x0f5, -1, + 0xa6c, 0x4ad, 0x978, 0x433, 0xa41, 0xd6f, 0x8bf, 0xfb8, + -1, 0x928, 0x85e, 0xfb6, 0x5c7, 0x99a, 0x8ec, 0xebc, + 0x226, 0x7d4, 0xdcd, 0xc0b, 0x000, 0x7f4, 0xc6f, 0x000, + 0x3ad, 0x5b2, -1, 0x67b, -1, 0x568, 0x6e2, -1, + -1, -1, 0x3f3, 0xaf5, 0x33f, -1, 0x022, 0x1bd, + 0xae5, -1, 0x9c3, 0x000, 0x92b, 0xee5, 0x29c, 0x000, + 0x15e, 0xe71, 0xacb, 0x9d2, 0x1a3, 0xb7f, 0xa5b, 0x095, + -1, 0xb6e, 0x79f, 0x3d1, 0x7d0, 0x131, 0xcd7, -1, + 0x2c3, -1, 0x396, 0x4d2, 0x297, 0x405, 0x634, -1, + -1, -1, 0x928, 0xbca, 0xb6c, 0x011, 0xfc0, 0xbaf, + 0xbd2, -1, 0x585, 0x000, 0xb8a, 0x7f9, 0xd6b, 0x4eb, + 0x9a3, 0x000, -1, 0xaeb, 0xa47, 0xcef, 0x9c6, -1, + 0x000, -1, 0x2a9, 0x371, 0xca6, -1, 0xb7d, 0x15f, + 0x2a9, -1, 0xe80, 0x7a7, 0x23a, 0x000, 0x000, 0xcc9, + 0x60e, -1, 0x130, 0x9cd, 0x498, 0xe25, 0x366, 0x34b, + 0x899, 0xe49, 0x1a8, 0xc93, 0x94d, 0x05e, -1, 0x0c2, + 0x757, 0xb9d, 0xaa3, 0x086, 0x395, 0x3c3, 0xa2e, 0xf77, + 0xcb1, 0x45e, 0x169, 0xbba, 0x367, 0x8cb, 0x260, 0x5a0, + 0x8cb, 0x737, 0xa1f, 0xaaf, 0xf92, 0x430, 0x97d, 0x542, + 0xb09, -1, 0x774, 0x084, 0x4c0, 0x2b3, 0xaf6, 0x93c, + 0x32d, 0xee2, -1, 0x605, 0xece, 0x8eb, 0xc62, 0x01d, + 0x000, 0xaba, 0xfc5, 0xb8e, 0xc07, 0xfb6, 0xbca, 0x8f0, + 0xd33, -1, 0x283, 0x6d6, 0x6ad, 0x678, 0x51a, 0xc95, + 0xda4, 0x962, 0x9ed, 0x307, 0x94a, 0x052, 0xf4e, 0x3bd, + 0x210, 0x71a, 0x3c7, 0x5a4, 0x6e7, 0x23a, 0x523, 0x1dc, + 0x6b2, 0x5e0, 0xbb0, 0xae4, 0xdb1, 0xd40, 0xc0d, 0x59e, + 0x21b, 0x4e6, 0x8be, 0x3b1, 0xc71, 0x5e4, 0x4aa, 0xaf3, + 0xa27, 0x43c, 0x9ea, 0x2ee, 0x6b2, 0xd51, 0x59d, 0xd3a, + 0xd43, 0x59d, 0x405, 0x2d4, 0x05b, 0x1b9, 0x68b, 0xbfa, + 0xbb9, 0x77a, 0xf91, 0xfcb, -1, 0x949, 0x177, 0x68d, + 0xcc3, 0xcf2, 0x000, 0xa87, 0x2e6, 0xd2f, 0x111, 0x168, + 0x94c, 0x54c, 0x000, 0x0c5, 0x829, 0xcbc, 0xc0b, 0x1ed, + 0x836, 0x9d8, 0xbdc, 0xc5e, 0x4e5, 0xb94, 0x6f2, 0x74f, + 0x878, 0x3b2, 0x48d, 0xc72, 0xcff, 0xccb, 0x8f9, 0x7ee, + 0x869, 0x228, 0x035, 0x81e, 0xcf9, 0x309, 0xdf2, -1, + 0x047, 0xdd3, 0xcab, 0x11d, 0xe76, 0xb52, 0xbbd, 0x12d, + 0xf37, 0x552, 0x61d, 0xdd8, 0x2cd, 0x298, 0x9e2, 0xf2c, + 0x9f7, 0xf41, 0xcb4, 0x277, 0xfde, 0xe7e, 0x82a, 0x86b, + 0x9cc, 0x580, 0xfcc, 0x33a, 0x438, 0xd6e, 0x000, 0xc04, + 0xd50, 0x681, 0x1b3, 0x322, 0x86c, 0x4a6, 0x000, 0xa17, + 0xd53, 0xdc0, 0xb61, 0x323, 0x3d1, 0x3fb, 0x929, 0xa6e, + 0x919, 0xae0, 0x283, 0xe6a, 0xed3, 0x371, 0xd51, 0x309, + 0x510, 0xd50, 0x6f4, 0xc84, 0x566, 0xba7, 0x75b, 0xbeb, + 0x793, 0x58f, 0x974, 0xe77, 0x52c, 0xcef, 0x942, 0xa7c, + 0x56a, 0xaa0, 0x784, 0x0ec, 0xad3, 0xccf, 0xecf, 0xc3f, + 0x846, 0x1b2, 0x112, 0x4ee, 0x18b, 0xa76, 0xe14, -1, + 0xfb1, 0xb4c, -1, 0xd54, 0x0e0, 0x72d, 0xdf0, 0xf0c, + 0x881, 0xc60, 0xe1b, 0x000, 0x453, 0xe21, 0xb2a, 0x6df, + 0x84f, 0x000, 0x12a, 0x991, 0x587, 0xa4e, 0x522, 0x000, + 0xe17, 0xc64, 0x994, 0x4cc, 0x0e5, 0xc2b, 0x8cf, 0x458, + 0x992, 0xec0, 0xc80, 0xefb, 0x7b7, 0x863, 0xc0a, 0x250, + 0x338, 0xa44, 0x8ab, 0x873, 0x134, 0x23c, 0x4f6, 0x066, + 0xd0f, 0xdd6, 0x93d, 0xf20, 0x000, 0x9bb, 0x255, 0xe7b, + 0x916, 0x4f3, 0x68e, 0xb82, 0x2b4, 0x9d7, 0xfd1, 0x0fb, + 0x11e, 0x204, 0x241, 0x67f, 0x1c4, 0xe91, 0xf41, 0xb4a, + -1, 0x6d2, 0xce6, 0xdfb, -1, 0xdd0, 0xb8d, 0x8db, + 0x86c, 0x224, 0xdeb, 0x7bb, 0x50e, 0x000, 0xb79, 0x11e, + 0x486, 0xd4c, 0x000, 0xa54, 0x946, 0x83a, 0x537, 0x875, + 0x000, 0x8e4, 0x95a, 0xdd5, 0x9d5, 0x910, 0x95b, 0x8c8, + 0xd22, 0x07c, 0xac0, 0x000, 0x048, 0x170, 0x9b2, 0xcea, + 0xb0f, 0x958, 0x0f9, 0xa9e, 0x87a, 0x166, 0x69c, 0x112, + 0xde0, 0x487, 0xeca, 0x639, 0x4ee, 0x7fa, 0x2cc, 0x709, + 0x87a, 0x5bb, 0xf64, 0x173, 0xdc6, 0xaaf, 0xbff, 0xf2a, + 0x8fb, 0xd44, 0x0ca, 0xf34, 0xb3a, 0xeb3, 0xfc5, 0x61d, + 0x92f, 0x6fb, 0x1a1, 0xd85, 0x8fe, 0xb6a, 0x0a1, 0x44f, + 0x89a, 0xc5d, 0x13b, 0x5cc, 0x000, 0x9ac, 0x9e6, 0xf95, + 0x511, 0xf2e, 0xf3c, 0x707, 0xec5, 0xaec, 0xc72, 0xeb1, + 0x105, 0xda3, 0xbcb, 0x1d6, 0xf16, 0xd50, 0x306, 0x03f, + 0xe6a, 0x25c, 0x9fe, 0xd3f, 0x8a4, 0x7bc, 0x0bc, 0x532, + 0x62e, 0x111, 0x797, 0xc2c, 0x9d0, 0x338, 0xbc7, 0xd64, + 0xb09, 0x4d6, 0xcba, 0x62c, 0xef2, 0x32b, 0x9c5, 0xc06, + 0x38b, 0xbb2, 0x8b7, 0x6f2, 0x7ec, 0xa77, -1, 0x7a3, + 0xc95, 0xa91, 0x5d3, 0xffc, -1, 0xe27, 0xa0a, 0x071, + 0x9da, 0x000, 0xba3, 0x3fd, 0x120, 0x845, 0x151, 0xb5f, + 0x193, 0xe99, 0x0f6, 0x640, 0xef4, 0xe17, 0x46b, 0x432, + 0x8a4, 0x415, 0x252, 0x79c, 0xbe5, 0x3f0, 0xb64, 0x984, + 0x5a7, 0x1be, 0xedc, -1, 0xd7e, 0x5b4, -1, 0xd27, + 0x03e, 0x136, 0xb30, 0xfff, 0x1dc, 0xc32, 0x84a, 0x392, + 0xf4f, 0x911, 0x501, 0x836, 0x700, 0x9ac, 0x000, 0xdb5, + 0xfa3, 0x5d3, 0x1f8, 0x888, -1, 0xfa4, 0xfe7, 0xd87, + 0x9fe, 0x6af, 0x9a5, 0xfd5, 0xf49, 0xded, 0x416, 0x2fc, + 0x078, 0xd2e, 0xbf1, 0xcd9, 0x733, -1, 0xb50, 0xd57, + 0xaa1, -1, 0x9b0, 0x874, 0x18f, -1, -1, 0x2f9, + 0xfbb, 0xf73, 0x646, 0x868, 0x000, 0x000, 0xba2, 0xd74, + 0xc8f, 0xe36, 0xcfd, 0x5b8, 0x850, 0x48a, 0x689, 0x7ad, + 0xefd, 0x7e1, 0xf45, 0xd9e, 0x0f0, 0x7c0, 0x675, -1, + 0xf26, 0x3c0, 0xe2d, 0xb62, 0x276, -1, 0xf90, 0x837, + 0xc7c, 0x8ed, 0x08d, 0x1d6, 0x506, 0xac9, 0xd81, 0x1be, + 0xf7e, 0x1a3, 0xb2a, 0x5e2, 0x217, 0x1df, 0x5a3, 0x498, + 0xfb1, 0x432, 0x2ca, 0x5a1, 0x5d5, 0x135, 0x6f0, -1, + 0xf70, 0x000, 0xd4c, 0x634, -1, 0x8ca, 0x198, -1, + 0x7b9, 0x629, 0xc16, 0x68c, 0xea2, -1, 0xba0, 0x6d9, + 0xce9, 0x7b2, 0x000, 0xf59, 0x252, 0x575, 0x0a8, 0x894, + 0x000, 0x824, 0xf63, 0xd70, 0xdd8, 0x856, 0xc77, 0x334, + 0xeb2, 0x2b8, 0x307, 0xc34, 0x2e7, 0xa74, 0x6b9, 0x0e1, + 0x20f, 0x3ee, 0xf80, 0xa1f, 0x6e6, 0xa03, 0x3c7, 0x72f, + 0xfff, 0x156, 0x273, 0x1b7, 0xd90, 0x998, 0x474, 0xf1b, + 0x80f, 0xe4c, 0x0ba, 0xfff, 0x85e, 0x3af, 0x58f, 0x000, + 0xf6b, 0x71c, 0x4ed, 0xec3, 0x4cb, 0x000, 0xa68, 0x6ca, + 0x086, 0x000, 0x6e4, 0xab3, 0xff5, 0x281, 0xf0a, 0xc92, + 0x8d5, 0x486, 0xdd1, 0x903, 0x8e3, 0x9df, 0x2ab, 0xd08, + 0x144, 0xdcd, 0x281, 0x046, 0x161, 0xe83, 0xf24, 0xce7, + 0x30a, 0xf89, 0xf01, 0x308, 0x142, 0x9df, 0x44d, 0x9dd, + 0x3ed, 0x81b, 0xd9d, 0x000, 0x8c2, 0xe01, 0xfe6, 0xa20, + 0x167, 0xedd, 0xdb1, 0x470, 0xe70, 0x3aa, 0x0ff, 0x4d1, + 0xd30, 0x67a, 0xc56, 0x3d8, 0xf2e, 0x86a, 0x18b, 0x3f5, + 0x1a7, 0xd97, 0x652, 0x000, 0x00d, 0xbc7, 0xed3, 0x39e, + 0xb0d, 0xd8d, 0xc49, 0x2db, 0x44e, 0x820, 0x189, 0xd51, + 0x523, 0x161, 0x2eb, 0x41c, 0x951, -1, 0xbb7, -1, + -1, 0x0a7, 0x3ed, 0xfaa, 0x18e, 0xa34, 0x820, 0x2b4, + -1, 0x8c2, 0x3ee, 0x59d, 0x97b, 0x209, 0x3a2, 0x102, + 0x351, 0x6bf, 0xd3f, 0x4fc, 0x55f, 0x4b5, 0xe22, 0xf13, + 0x53a, 0x08a, 0x38d, 0xf4b, 0x424, 0xea6, 0x48e, 0x11c, + 0x339, 0x5bd, 0xf7c, 0x3bd, 0x15a, 0x35c, 0x854, 0x71b, + 0x30f, 0x065, 0x97e, 0x354, 0x28e, 0x344, 0x926, 0xc0b, + 0xae0, 0x5db, 0xb3e, 0x661, 0x432, 0x3c8, 0xf5e, 0x368, + 0xc85, 0xfff, 0x7f5, 0x0b6, 0x98b, -1, 0x28c, 0x784, + 0xb78, 0x50a, 0x696, 0x47c, 0x40d, -1, 0xe4d, 0x5fc, + -1, -1, 0xadb, 0x1db, 0x830, 0xd48, -1, 0xf3a, + 0xee4, 0xed4, 0xb1a, 0xa14, 0x36d, 0xf1c, 0x774, 0x000, + 0x942, 0x278, 0x7ee, 0x000, 0x550, 0x57c, 0x343, 0x22b, + 0x324, 0xa34, 0x0ea, 0x230, 0x51b, 0x2d1, 0x500, 0x59f, + 0xd56, 0x540, 0x2f4, 0x87d, 0x9e5, 0x9c5, 0x5ea, 0x771, + 0x491, 0x206, 0xa4b, 0x4bf, 0xdaf, 0x308, 0xb25, 0x261, + 0x991, 0x000, 0x88e, 0x7e8, 0x3d6, 0x15d, 0xebc, 0x6c2, + 0xd45, 0x000, 0x3c6, 0x48d, 0x622, 0x758, 0xfa9, 0x3cf, + 0x401, 0xcdb, 0xe3f, 0x544, 0xf1f, 0x000, -1, 0x4d4, + 0x000, 0x7f1, 0xba4, 0x81c, 0x92f, 0x7d1, 0xa83, 0xa31, + 0xe74, 0xa20, 0x475, 0x489, 0xf20, 0x3d1, 0xac1, 0xb2d, + 0x6b2, 0x1b6, 0x063, 0xd00, 0xfeb, 0x5ca, 0xb2c, 0xcb2, + 0x1cb, 0x251, 0x82b, 0x8ba, 0x40b, 0xf1e, 0xa8a, 0xd24, + 0x880, 0x84e, 0x8cb, 0x0a3, 0x000, 0xaf7, 0xf99, 0x6a1, + 0x156, 0x382, 0x0a0, 0x000, 0xed1, 0xd07, 0xbf5, 0x000, + 0x295, 0xe48, 0x760, 0x019, 0x97f, 0xb46, 0xff5, 0x7c9, + 0x1cf, 0xba4, 0x630, 0xe58, 0xda6, 0xd4b, 0xc02, 0xf9f, + 0x11c, 0x000, 0xb99, 0x51f, 0x43e, 0x199, 0xdfb, 0x72f, + 0x913, 0x509, 0xac5, 0xa2e, 0xcdb, 0x348, 0xb15, 0x472, + 0x95d, 0x67f, 0x000, 0x4b9, 0xd78, 0xc87, 0x0f6, 0x281, + 0x0bd, 0x924, 0x35e, 0x129, 0xffd, 0xe24, 0x000, 0x640, + 0x09b, 0xd10, 0xa0d, 0x000, 0x474, 0x189, 0x49f, 0x62d, + 0xcba, 0x561, 0x000, 0x58a, 0xaa1, 0x603, 0x5ab, 0x0c7, + 0x00a, 0x784, 0x5e4, 0x7e4, 0xe4d, -1, 0x276, 0x465, + 0xee9, 0xe51, 0xdae, 0xbb1, 0x51f, 0xcba, 0x1c3, 0xd70, + 0x000, 0x5be, 0x4ea, 0x3cc, 0xf13, 0x811, 0x813, 0x234, + 0x7e4, 0xbae, 0xd97, 0xb74, 0x000, 0x76a, 0xda1, 0x000, + 0xd8c, 0x53a, 0xc5a, 0x000, 0x000, 0x61b, 0xd87, 0x141, + 0x383, 0xe06, 0x6a3, 0x6c3, 0xbcc, 0xc44, 0xf63, 0xd8b, + 0x58d, 0x000, 0x839, 0x77a, 0x000, 0x8e4, 0x000, 0xdbe, + 0x483, 0xd5b, 0xa9d, 0xca5, 0x431, 0x491, 0x29a, 0x27d, + 0x2d2, 0x691, 0x000, 0x19a, 0xa0d, 0xb0b, 0xf32, 0xe49, + 0xfbf, 0x399, 0xd20, 0x000, 0x66a, 0x000, 0x447, 0xb20, + 0x894, 0x038, 0xc9c, 0xff0, 0x000, 0x0d4, 0xad4, 0x768, + 0x65c, 0x000, 0x27b, 0x6c6, 0x9be, 0xd35, 0xc6a, 0xdd3, + 0x000, 0x2a7, 0x158, 0x38d, 0x8ef, 0x7b6, 0xd49, 0x30c, + 0xec3, 0x211, 0x17c, 0xcd0, 0x61f, 0x000, 0xe6e, 0x1d4, + 0x6e9, 0x000, 0xc2d, 0x5c3, 0xcd4, 0x760, 0x532, 0xc94, + 0x590, 0x000, 0x4a3, 0xc33, 0x000, 0x426, 0x604, 0xa06, + 0xa99, 0x917, 0x0c4, 0xc8d, 0x9e5, 0xcc7, 0x415, 0xf79, + 0x000, 0xaf4, 0x622, 0x756, 0x9c2, 0xa51, 0xb0f, 0x4ef, + 0xbc4, 0xe15, 0x29e, 0x055, 0x6c9, 0x695, 0x94f, 0x9d6, + 0x000, 0xb9f, 0xd46, 0x1d4, 0x000, 0xcb2, 0x9e8, 0x000, + 0xa5e, 0xce0, 0x000, 0x098, 0xa98, 0x6d9, 0x5e2, 0x95f, + 0x791, 0xeb8, 0x5fa, 0x60a, 0xacc, 0x3d3, 0x4df, 0x0df, + 0x9ca, 0x972, 0x3cc, 0x583, 0xca5, 0xe1a, 0x000, 0x2d3, + 0x266, 0x000, 0x06c, 0xfff, 0x62d, 0x64e, 0x40c, 0x599, + 0x475, 0xaa9, 0xba6, 0x96f, 0xe32, 0x059, 0x342, 0x36d, + 0xfd1, 0x09b, 0x878, 0x9f8, 0x000, 0x3ad, 0xdba, 0x000, + 0x544, 0xc1a, 0x000, 0xee8, 0x492, 0xa6b, 0x447, 0xd2a, + 0xb4e, 0x02c, 0xadb, 0xde2, 0x904, 0x62d, 0xf01, 0xbb8, + 0x255, 0x382, 0xfff, 0x29e, 0x000, 0x000, 0x011, 0xfff, +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "pdf417.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2008-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +#define PDF417_STOP 0xbff + +static inline signed short pdf417_decode8 (zbar_decoder_t *dcode) +{ + /* build edge signature of character + * from similar edge measurements + */ + unsigned s = dcode->pdf417.s8; + dbprintf(2, " s=%d ", s); + if(s < 8) + return(-1); + + long sig = 0; + signed char e; + unsigned char i; + for(i = 0; i < 7; i++) { + if(get_color(dcode) == ZBAR_SPACE) + e = decode_e(get_width(dcode, i) + + get_width(dcode, i + 1), s, 17); + else + e = decode_e(get_width(dcode, 7 - i) + + get_width(dcode, 6 - i), s, 17); + dbprintf(4, "%x", e); + if(e < 0 || e > 8) + return(-1); + sig = (sig << 3) ^ e; + } + dbprintf(2, " sig=%06lx", sig); + + /* determine cluster number */ + int clst = ((sig & 7) - ((sig >> 3) & 7) + + ((sig >> 12) & 7) - ((sig >> 15) & 7)); + if(clst < 0) + clst += 9; + dbprintf(2, " k=%d", clst); + zassert(clst >= 0 && clst < 9, -1, "dir=%x sig=%lx k=%x %s\n", + dcode->pdf417.direction, sig, clst, + _zbar_decoder_buf_dump(dcode->buf, dcode->pdf417.character)); + + if(clst != 0 && clst != 3 && clst != 6) { + if(get_color(dcode) && clst == 7 && sig == 0x080007) + return(PDF417_STOP); + return(-1); + } + + signed short g[3]; + sig &= 0x3ffff; + g[0] = pdf417_hash[(sig - (sig >> 10)) & PDF417_HASH_MASK]; + g[1] = pdf417_hash[((sig >> 8) - sig) & PDF417_HASH_MASK]; + g[2] = pdf417_hash[((sig >> 14) - (sig >> 1)) & PDF417_HASH_MASK]; + zassert(g[0] >= 0 && g[1] >= 0 && g[2] >= 0, -1, + "dir=%x sig=%lx k=%x g0=%03x g1=%03x g2=%03x %s\n", + dcode->pdf417.direction, sig, clst, g[0], g[1], g[2], + _zbar_decoder_buf_dump(dcode->buf, dcode->pdf417.character)); + + unsigned short c = (g[0] + g[1] + g[2]) & PDF417_HASH_MASK; + dbprintf(2, " g0=%x g1=%x g2=%x c=%03d(%d)", + g[0], g[1], g[2], c & 0x3ff, c >> 10); + return(c); +} + +static inline signed char pdf417_decode_start(zbar_decoder_t *dcode) +{ + unsigned s = dcode->pdf417.s8; + if(s < 8) + return(0); + + int ei = decode_e(get_width(dcode, 0) + get_width(dcode, 1), s, 17); + int ex = (get_color(dcode) == ZBAR_SPACE) ? 2 : 6; + if(ei != ex) + return(0); + + ei = decode_e(get_width(dcode, 1) + get_width(dcode, 2), s, 17); + if(ei) + return(0); + + ei = decode_e(get_width(dcode, 2) + get_width(dcode, 3), s, 17); + ex = (get_color(dcode) == ZBAR_SPACE) ? 0 : 2; + if(ei != ex) + return(0); + + ei = decode_e(get_width(dcode, 3) + get_width(dcode, 4), s, 17); + ex = (get_color(dcode) == ZBAR_SPACE) ? 0 : 2; + if(ei != ex) + return(0); + + ei = decode_e(get_width(dcode, 4) + get_width(dcode, 5), s, 17); + if(ei) + return(0); + + ei = decode_e(get_width(dcode, 5) + get_width(dcode, 6), s, 17); + if(ei) + return(0); + + ei = decode_e(get_width(dcode, 6) + get_width(dcode, 7), s, 17); + ex = (get_color(dcode) == ZBAR_SPACE) ? 7 : 1; + if(ei != ex) + return(0); + + ei = decode_e(get_width(dcode, 7) + get_width(dcode, 8), s, 17); + ex = (get_color(dcode) == ZBAR_SPACE) ? 8 : 1; + + if(get_color(dcode) == ZBAR_BAR) { + /* stop character has extra bar */ + if(ei != 1) + return(0); + ei = decode_e(get_width(dcode, 8) + get_width(dcode, 9), s, 17); + } + + dbprintf(2, " pdf417[%c]: s=%d", + (get_color(dcode)) ? '<' : '>', s); + + /* check quiet zone */ + if(ei >= 0 && ei < ex) { + dbprintf(2, " [invalid quiet]\n"); + return(0); + } + + /* lock shared resources */ + if(acquire_lock(dcode, ZBAR_PDF417)) { + dbprintf(2, " [locked %d]\n", dcode->lock); + return(0); + } + + pdf417_decoder_t *dcode417 = &dcode->pdf417; + dcode417->direction = get_color(dcode); + dcode417->element = 0; + dcode417->character = 0; + + dbprintf(2, " [valid start]\n"); + return(ZBAR_PARTIAL); +} + +zbar_symbol_type_t _zbar_decode_pdf417 (zbar_decoder_t *dcode) +{ + pdf417_decoder_t *dcode417 = &dcode->pdf417; + + /* update latest character width */ + dcode417->s8 -= get_width(dcode, 8); + dcode417->s8 += get_width(dcode, 0); + + if(dcode417->character < 0) { + pdf417_decode_start(dcode); + dbprintf(4, "\n"); + return(0); + } + + /* process every 8th element of active symbol */ + if(++dcode417->element) + return(0); + dcode417->element = 0; + + dbprintf(2, " pdf417[%c%02d]:", + (dcode417->direction) ? '<' : '>', dcode417->character); + + if(get_color(dcode) != dcode417->direction) { + int c = dcode417->character; + release_lock(dcode, ZBAR_PDF417); + dcode417->character = -1; + zassert(get_color(dcode) == dcode417->direction, ZBAR_NONE, + "color=%x dir=%x char=%d elem=0 %s\n", + get_color(dcode), dcode417->direction, c, + _zbar_decoder_buf_dump(dcode->buf, c)); + } + + signed short c = pdf417_decode8(dcode); + if(c < 0 || size_buf(dcode, dcode417->character + 1)) { + dbprintf(1, (c < 0) ? " [aborted]\n" : " [overflow]\n"); + release_lock(dcode, ZBAR_PDF417); + dcode417->character = -1; + return(0); + } + + /* FIXME TBD infer dimensions, save codewords */ + + if(c == PDF417_STOP) { + dbprintf(1, " [valid stop]"); + /* FIXME check trailing bar and qz */ + dcode->direction = 1 - 2 * dcode417->direction; + dcode->modifiers = 0; + release_lock(dcode, ZBAR_PDF417); + dcode417->character = -1; + } + + dbprintf(2, "\n"); + return(0); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "decoder.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +zbar_decoder_t *zbar_decoder_create () +{ + zbar_decoder_t *dcode = calloc(1, sizeof(zbar_decoder_t)); + dcode->buf_alloc = BUFFER_MIN; + dcode->buf = malloc(dcode->buf_alloc); + + /* initialize default configs */ +#ifdef ENABLE_EAN + dcode->ean.enable = 1; + dcode->ean.ean13_config = ((1 << ZBAR_CFG_ENABLE) | + (1 << ZBAR_CFG_EMIT_CHECK)); + dcode->ean.ean8_config = ((1 << ZBAR_CFG_ENABLE) | + (1 << ZBAR_CFG_EMIT_CHECK)); + dcode->ean.upca_config = 1 << ZBAR_CFG_EMIT_CHECK; + dcode->ean.upce_config = 1 << ZBAR_CFG_EMIT_CHECK; + dcode->ean.isbn10_config = 1 << ZBAR_CFG_EMIT_CHECK; + dcode->ean.isbn13_config = 1 << ZBAR_CFG_EMIT_CHECK; +# ifdef FIXME_ADDON_SYNC + dcode->ean.ean2_config = 1 << ZBAR_CFG_ENABLE; + dcode->ean.ean5_config = 1 << ZBAR_CFG_ENABLE; +# endif +#endif +#ifdef ENABLE_I25 + dcode->i25.config = 1 << ZBAR_CFG_ENABLE; + CFG(dcode->i25, ZBAR_CFG_MIN_LEN) = 6; +#endif +#ifdef ENABLE_DATABAR + dcode->databar.config = ((1 << ZBAR_CFG_ENABLE) | + (1 << ZBAR_CFG_EMIT_CHECK)); + dcode->databar.config_exp = ((1 << ZBAR_CFG_ENABLE) | + (1 << ZBAR_CFG_EMIT_CHECK)); + dcode->databar.csegs = 4; + dcode->databar.segs = calloc(4, sizeof(*dcode->databar.segs)); +#endif +#ifdef ENABLE_CODABAR + dcode->codabar.config = 1 << ZBAR_CFG_ENABLE; + CFG(dcode->codabar, ZBAR_CFG_MIN_LEN) = 4; +#endif +#ifdef ENABLE_CODE39 + dcode->code39.config = 1 << ZBAR_CFG_ENABLE; + CFG(dcode->code39, ZBAR_CFG_MIN_LEN) = 1; +#endif +#ifdef ENABLE_CODE93 + dcode->code93.config = 1 << ZBAR_CFG_ENABLE; +#endif +#ifdef ENABLE_CODE128 + dcode->code128.config = 1 << ZBAR_CFG_ENABLE; +#endif +#ifdef ENABLE_PDF417 + dcode->pdf417.config = 1 << ZBAR_CFG_ENABLE; +#endif +#ifdef ENABLE_QRCODE + dcode->qrf.config = 1 << ZBAR_CFG_ENABLE; +#endif + + zbar_decoder_reset(dcode); + return(dcode); +} + +void zbar_decoder_destroy (zbar_decoder_t *dcode) +{ +#ifdef ENABLE_DATABAR + if(dcode->databar.segs) + free(dcode->databar.segs); +#endif + if(dcode->buf) + free(dcode->buf); + free(dcode); +} + +void zbar_decoder_reset (zbar_decoder_t *dcode) +{ + memset(dcode, 0, (long)&dcode->buf_alloc - (long)dcode); +#ifdef ENABLE_EAN + ean_reset(&dcode->ean); +#endif +#ifdef ENABLE_I25 + i25_reset(&dcode->i25); +#endif +#ifdef ENABLE_DATABAR + databar_reset(&dcode->databar); +#endif +#ifdef ENABLE_CODABAR + codabar_reset(&dcode->codabar); +#endif +#ifdef ENABLE_CODE39 + code39_reset(&dcode->code39); +#endif +#ifdef ENABLE_CODE93 + code93_reset(&dcode->code93); +#endif +#ifdef ENABLE_CODE128 + code128_reset(&dcode->code128); +#endif +#ifdef ENABLE_PDF417 + pdf417_reset(&dcode->pdf417); +#endif +#ifdef ENABLE_QRCODE + qr_finder_reset(&dcode->qrf); +#endif +} + +void zbar_decoder_new_scan (zbar_decoder_t *dcode) +{ + /* soft reset decoder */ + memset(dcode->w, 0, sizeof(dcode->w)); + dcode->lock = 0; + dcode->idx = 0; + dcode->s6 = 0; +#ifdef ENABLE_EAN + ean_new_scan(&dcode->ean); +#endif +#ifdef ENABLE_I25 + i25_reset(&dcode->i25); +#endif +#ifdef ENABLE_DATABAR + databar_new_scan(&dcode->databar); +#endif +#ifdef ENABLE_CODABAR + codabar_reset(&dcode->codabar); +#endif +#ifdef ENABLE_CODE39 + code39_reset(&dcode->code39); +#endif +#ifdef ENABLE_CODE93 + code93_reset(&dcode->code93); +#endif +#ifdef ENABLE_CODE128 + code128_reset(&dcode->code128); +#endif +#ifdef ENABLE_PDF417 + pdf417_reset(&dcode->pdf417); +#endif +#ifdef ENABLE_QRCODE + qr_finder_reset(&dcode->qrf); +#endif +} + + +zbar_color_t zbar_decoder_get_color (const zbar_decoder_t *dcode) +{ + return(get_color(dcode)); +} + +const char *zbar_decoder_get_data (const zbar_decoder_t *dcode) +{ + return((char*)dcode->buf); +} + +unsigned int zbar_decoder_get_data_length (const zbar_decoder_t *dcode) +{ + return(dcode->buflen); +} + +int zbar_decoder_get_direction (const zbar_decoder_t *dcode) +{ + return(dcode->direction); +} + +zbar_decoder_handler_t * +zbar_decoder_set_handler (zbar_decoder_t *dcode, + zbar_decoder_handler_t *handler) +{ + zbar_decoder_handler_t *result = dcode->handler; + dcode->handler = handler; + return(result); +} + +void zbar_decoder_set_userdata (zbar_decoder_t *dcode, + void *userdata) +{ + dcode->userdata = userdata; +} + +void *zbar_decoder_get_userdata (const zbar_decoder_t *dcode) +{ + return(dcode->userdata); +} + +zbar_symbol_type_t zbar_decoder_get_type (const zbar_decoder_t *dcode) +{ + return(dcode->type); +} + +unsigned int zbar_decoder_get_modifiers (const zbar_decoder_t *dcode) +{ + return(dcode->modifiers); +} + +zbar_symbol_type_t zbar_decode_width (zbar_decoder_t *dcode, + unsigned w) +{ + zbar_symbol_type_t tmp, sym = ZBAR_NONE; + + dcode->w[dcode->idx & (DECODE_WINDOW - 1)] = w; + dbprintf(1, " decode[%x]: w=%d (%g)\n", dcode->idx, w, (w / 32.)); + + /* update shared character width */ + dcode->s6 -= get_width(dcode, 7); + dcode->s6 += get_width(dcode, 1); + + /* each decoder processes width stream in parallel */ +#ifdef ENABLE_QRCODE + if(TEST_CFG(dcode->qrf.config, ZBAR_CFG_ENABLE) && + (tmp = _zbar_find_qr(dcode)) > ZBAR_PARTIAL) + sym = tmp; +#endif +#ifdef ENABLE_EAN + if((dcode->ean.enable) && + (tmp = _zbar_decode_ean(dcode))) + sym = tmp; +#endif +#ifdef ENABLE_CODE39 + if(TEST_CFG(dcode->code39.config, ZBAR_CFG_ENABLE) && + (tmp = _zbar_decode_code39(dcode)) > ZBAR_PARTIAL) + sym = tmp; +#endif +#ifdef ENABLE_CODE93 + if(TEST_CFG(dcode->code93.config, ZBAR_CFG_ENABLE) && + (tmp = _zbar_decode_code93(dcode)) > ZBAR_PARTIAL) + sym = tmp; +#endif +#ifdef ENABLE_CODE128 + if(TEST_CFG(dcode->code128.config, ZBAR_CFG_ENABLE) && + (tmp = _zbar_decode_code128(dcode)) > ZBAR_PARTIAL) + sym = tmp; +#endif +#ifdef ENABLE_DATABAR + if(TEST_CFG(dcode->databar.config | dcode->databar.config_exp, + ZBAR_CFG_ENABLE) && + (tmp = _zbar_decode_databar(dcode)) > ZBAR_PARTIAL) + sym = tmp; +#endif +#ifdef ENABLE_CODABAR + if(TEST_CFG(dcode->codabar.config, ZBAR_CFG_ENABLE) && + (tmp = _zbar_decode_codabar(dcode)) > ZBAR_PARTIAL) + sym = tmp; +#endif +#ifdef ENABLE_I25 + if(TEST_CFG(dcode->i25.config, ZBAR_CFG_ENABLE) && + (tmp = _zbar_decode_i25(dcode)) > ZBAR_PARTIAL) + sym = tmp; +#endif +#ifdef ENABLE_PDF417 + if(TEST_CFG(dcode->pdf417.config, ZBAR_CFG_ENABLE) && + (tmp = _zbar_decode_pdf417(dcode)) > ZBAR_PARTIAL) + sym = tmp; +#endif + + dcode->idx++; + dcode->type = sym; + if(sym) { + if(dcode->lock && sym > ZBAR_PARTIAL && sym != ZBAR_QRCODE) + release_lock(dcode, sym); + if(dcode->handler) + dcode->handler(dcode); + } + return(sym); +} + +static inline const unsigned int* +decoder_get_configp (const zbar_decoder_t *dcode, + zbar_symbol_type_t sym) +{ + const unsigned int *config; + switch(sym) { +#ifdef ENABLE_EAN + case ZBAR_EAN13: + config = &dcode->ean.ean13_config; + break; + + case ZBAR_EAN2: + config = &dcode->ean.ean2_config; + break; + + case ZBAR_EAN5: + config = &dcode->ean.ean5_config; + break; + + case ZBAR_EAN8: + config = &dcode->ean.ean8_config; + break; + + case ZBAR_UPCA: + config = &dcode->ean.upca_config; + break; + + case ZBAR_UPCE: + config = &dcode->ean.upce_config; + break; + + case ZBAR_ISBN10: + config = &dcode->ean.isbn10_config; + break; + + case ZBAR_ISBN13: + config = &dcode->ean.isbn13_config; + break; +#endif + +#ifdef ENABLE_I25 + case ZBAR_I25: + config = &dcode->i25.config; + break; +#endif + +#ifdef ENABLE_DATABAR + case ZBAR_DATABAR: + config = &dcode->databar.config; + break; + case ZBAR_DATABAR_EXP: + config = &dcode->databar.config_exp; + break; +#endif + +#ifdef ENABLE_CODABAR + case ZBAR_CODABAR: + config = &dcode->codabar.config; + break; +#endif + +#ifdef ENABLE_CODE39 + case ZBAR_CODE39: + config = &dcode->code39.config; + break; +#endif + +#ifdef ENABLE_CODE93 + case ZBAR_CODE93: + config = &dcode->code93.config; + break; +#endif + +#ifdef ENABLE_CODE128 + case ZBAR_CODE128: + config = &dcode->code128.config; + break; +#endif + +#ifdef ENABLE_PDF417 + case ZBAR_PDF417: + config = &dcode->pdf417.config; + break; +#endif + +#ifdef ENABLE_QRCODE + case ZBAR_QRCODE: + config = &dcode->qrf.config; + break; +#endif + + default: + config = NULL; + } + return(config); +} + +unsigned int zbar_decoder_get_configs (const zbar_decoder_t *dcode, + zbar_symbol_type_t sym) +{ + const unsigned *config = decoder_get_configp(dcode, sym); + if(!config) + return(0); + return(*config); +} + +static inline int decoder_set_config_bool (zbar_decoder_t *dcode, + zbar_symbol_type_t sym, + zbar_config_t cfg, + int val) +{ + unsigned *config = (void*)decoder_get_configp(dcode, sym); + if(!config || cfg >= ZBAR_CFG_NUM) + return(1); + + if(!val) + *config &= ~(1 << cfg); + else if(val == 1) + *config |= (1 << cfg); + else + return(1); + +#ifdef ENABLE_EAN + dcode->ean.enable = TEST_CFG(dcode->ean.ean13_config | + dcode->ean.ean2_config | + dcode->ean.ean5_config | + dcode->ean.ean8_config | + dcode->ean.upca_config | + dcode->ean.upce_config | + dcode->ean.isbn10_config | + dcode->ean.isbn13_config, + ZBAR_CFG_ENABLE); +#endif + + return(0); +} + +static inline int decoder_set_config_int (zbar_decoder_t *dcode, + zbar_symbol_type_t sym, + zbar_config_t cfg, + int val) +{ + switch(sym) { + +#ifdef ENABLE_I25 + case ZBAR_I25: + CFG(dcode->i25, cfg) = val; + break; +#endif +#ifdef ENABLE_CODABAR + case ZBAR_CODABAR: + CFG(dcode->codabar, cfg) = val; + break; +#endif +#ifdef ENABLE_CODE39 + case ZBAR_CODE39: + CFG(dcode->code39, cfg) = val; + break; +#endif +#ifdef ENABLE_CODE93 + case ZBAR_CODE93: + CFG(dcode->code93, cfg) = val; + break; +#endif +#ifdef ENABLE_CODE128 + case ZBAR_CODE128: + CFG(dcode->code128, cfg) = val; + break; +#endif +#ifdef ENABLE_PDF417 + case ZBAR_PDF417: + CFG(dcode->pdf417, cfg) = val; + break; +#endif + + default: + return(1); + } + return(0); +} + +int zbar_decoder_set_config (zbar_decoder_t *dcode, + zbar_symbol_type_t sym, + zbar_config_t cfg, + int val) +{ + if(sym == ZBAR_NONE) { + static const zbar_symbol_type_t all[] = { + ZBAR_EAN13, ZBAR_EAN2, ZBAR_EAN5, ZBAR_EAN8, + ZBAR_UPCA, ZBAR_UPCE, ZBAR_ISBN10, ZBAR_ISBN13, + ZBAR_I25, ZBAR_DATABAR, ZBAR_DATABAR_EXP, ZBAR_CODABAR, + ZBAR_CODE39, ZBAR_CODE93, ZBAR_CODE128, ZBAR_QRCODE, + ZBAR_PDF417, 0 + }; + const zbar_symbol_type_t *symp; + for(symp = all; *symp; symp++) + zbar_decoder_set_config(dcode, *symp, cfg, val); + return(0); + } + + if(cfg >= 0 && cfg < ZBAR_CFG_NUM) + return(decoder_set_config_bool(dcode, sym, cfg, val)); + else if(cfg >= ZBAR_CFG_MIN_LEN && cfg <= ZBAR_CFG_MAX_LEN) + return(decoder_set_config_int(dcode, sym, cfg, val)); + else + return(1); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////// "scanner.c" +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/*------------------------------------------------------------------------ + * Copyright 2007-2010 (c) Jeff Brown + * + * This file is part of the ZBar Bar Code Reader. + * + * The ZBar Bar Code Reader is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * The ZBar Bar Code Reader 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 Public License for more details. + * + * You should have received a copy of the GNU Lesser Public License + * along with the ZBar Bar Code Reader; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * http://sourceforge.net/projects/zbar + *------------------------------------------------------------------------*/ + +#ifndef ZBAR_FIXED +# define ZBAR_FIXED 5 +#endif +#define ROUND (1 << (ZBAR_FIXED - 1)) + +/* FIXME add runtime config API for these */ +#ifndef ZBAR_SCANNER_THRESH_MIN +# define ZBAR_SCANNER_THRESH_MIN 4 +#endif + +#ifndef ZBAR_SCANNER_THRESH_INIT_WEIGHT +# define ZBAR_SCANNER_THRESH_INIT_WEIGHT .44 +#endif +#define THRESH_INIT ((unsigned)((ZBAR_SCANNER_THRESH_INIT_WEIGHT \ + * (1 << (ZBAR_FIXED + 1)) + 1) / 2)) + +#ifndef ZBAR_SCANNER_THRESH_FADE +# define ZBAR_SCANNER_THRESH_FADE 8 +#endif + +#ifndef ZBAR_SCANNER_EWMA_WEIGHT +# define ZBAR_SCANNER_EWMA_WEIGHT .78 +#endif +#define EWMA_WEIGHT ((unsigned)((ZBAR_SCANNER_EWMA_WEIGHT \ + * (1 << (ZBAR_FIXED + 1)) + 1) / 2)) + +/* scanner state */ +struct zbar_scanner_s { + zbar_decoder_t *decoder; /* associated bar width decoder */ + unsigned y1_min_thresh; /* minimum threshold */ + + unsigned x; /* relative scan position of next sample */ + int y0[4]; /* short circular buffer of average intensities */ + + int y1_sign; /* slope at last crossing */ + unsigned y1_thresh; /* current slope threshold */ + + unsigned cur_edge; /* interpolated position of tracking edge */ + unsigned last_edge; /* interpolated position of last located edge */ + unsigned width; /* last element width */ +}; + +zbar_scanner_t *zbar_scanner_create (zbar_decoder_t *dcode) +{ + zbar_scanner_t *scn = malloc(sizeof(zbar_scanner_t)); + scn->decoder = dcode; + scn->y1_min_thresh = ZBAR_SCANNER_THRESH_MIN; + zbar_scanner_reset(scn); + return(scn); +} + +void zbar_scanner_destroy (zbar_scanner_t *scn) +{ + free(scn); +} + +zbar_symbol_type_t zbar_scanner_reset (zbar_scanner_t *scn) +{ + memset(&scn->x, 0, sizeof(zbar_scanner_t) - offsetof(zbar_scanner_t, x)); + scn->y1_thresh = scn->y1_min_thresh; + if(scn->decoder) + zbar_decoder_reset(scn->decoder); + return(ZBAR_NONE); +} + +unsigned zbar_scanner_get_width (const zbar_scanner_t *scn) +{ + return(scn->width); +} + +unsigned zbar_scanner_get_edge (const zbar_scanner_t *scn, + unsigned offset, + int prec) +{ + unsigned edge = scn->last_edge - offset - (1 << ZBAR_FIXED) - ROUND; + prec = ZBAR_FIXED - prec; + if(prec > 0) + return(edge >> prec); + else if(!prec) + return(edge); + else + return(edge << -prec); +} + +zbar_color_t zbar_scanner_get_color (const zbar_scanner_t *scn) +{ + return((scn->y1_sign <= 0) ? ZBAR_SPACE : ZBAR_BAR); +} + +static inline unsigned calc_thresh (zbar_scanner_t *scn) +{ + /* threshold 1st to improve noise rejection */ + unsigned dx, thresh = scn->y1_thresh; + unsigned long t; + if((thresh <= scn->y1_min_thresh) || !scn->width) { + dbprintf(1, " tmin=%d", scn->y1_min_thresh); + return(scn->y1_min_thresh); + } + /* slowly return threshold to min */ + dx = (scn->x << ZBAR_FIXED) - scn->last_edge; + t = thresh * dx; + t /= scn->width; + t /= ZBAR_SCANNER_THRESH_FADE; + dbprintf(1, " thr=%d t=%ld x=%d last=%d.%d (%d)", + thresh, t, scn->x, scn->last_edge >> ZBAR_FIXED, + scn->last_edge & ((1 << ZBAR_FIXED) - 1), dx); + if(thresh > t) { + thresh -= t; + if(thresh > scn->y1_min_thresh) + return(thresh); + } + scn->y1_thresh = scn->y1_min_thresh; + return(scn->y1_min_thresh); +} + +static inline zbar_symbol_type_t process_edge (zbar_scanner_t *scn, + int y1) +{ + if(!scn->y1_sign) + scn->last_edge = scn->cur_edge = (1 << ZBAR_FIXED) + ROUND; + else if(!scn->last_edge) + scn->last_edge = scn->cur_edge; + + scn->width = scn->cur_edge - scn->last_edge; + dbprintf(1, " sgn=%d cur=%d.%d w=%d (%s)\n", + scn->y1_sign, scn->cur_edge >> ZBAR_FIXED, + scn->cur_edge & ((1 << ZBAR_FIXED) - 1), scn->width, + ((y1 > 0) ? "SPACE" : "BAR")); + scn->last_edge = scn->cur_edge; + +#if DEBUG_SVG > 1 + svg_path_moveto(SVG_ABS, scn->last_edge - (1 << ZBAR_FIXED) - ROUND, 0); +#endif + + /* pass to decoder */ + if(scn->decoder) + return(zbar_decode_width(scn->decoder, scn->width)); + return(ZBAR_PARTIAL); +} + +inline zbar_symbol_type_t zbar_scanner_flush (zbar_scanner_t *scn) +{ + unsigned x; + if(!scn->y1_sign) + return(ZBAR_NONE); + + x = (scn->x << ZBAR_FIXED) + ROUND; + + if(scn->cur_edge != x || scn->y1_sign > 0) { + zbar_symbol_type_t edge = process_edge(scn, -scn->y1_sign); + dbprintf(1, "flush0:"); + scn->cur_edge = x; + scn->y1_sign = -scn->y1_sign; + return(edge); + } + + scn->y1_sign = scn->width = 0; + if(scn->decoder) + return(zbar_decode_width(scn->decoder, 0)); + return(ZBAR_PARTIAL); +} + +zbar_symbol_type_t zbar_scanner_new_scan (zbar_scanner_t *scn) +{ + zbar_symbol_type_t edge = ZBAR_NONE; + while(scn->y1_sign) { + zbar_symbol_type_t tmp = zbar_scanner_flush(scn); + if(tmp < 0 || tmp > edge) + edge = tmp; + } + + /* reset scanner and associated decoder */ + memset(&scn->x, 0, sizeof(zbar_scanner_t) - offsetof(zbar_scanner_t, x)); + scn->y1_thresh = scn->y1_min_thresh; + if(scn->decoder) + zbar_decoder_new_scan(scn->decoder); + return(edge); +} + +zbar_symbol_type_t zbar_scan_y (zbar_scanner_t *scn, + int y) +{ + /* FIXME calc and clip to max y range... */ + /* retrieve short value history */ + register int x = scn->x; + register int y0_1 = scn->y0[(x - 1) & 3]; + register int y0_0 = y0_1; + register int y0_2, y0_3, y1_1, y2_1, y2_2; + zbar_symbol_type_t edge; + if(x) { + /* update weighted moving average */ + y0_0 += ((int)((y - y0_1) * EWMA_WEIGHT)) >> ZBAR_FIXED; + scn->y0[x & 3] = y0_0; + } + else + y0_0 = y0_1 = scn->y0[0] = scn->y0[1] = scn->y0[2] = scn->y0[3] = y; + y0_2 = scn->y0[(x - 2) & 3]; + y0_3 = scn->y0[(x - 3) & 3]; + /* 1st differential @ x-1 */ + y1_1 = y0_1 - y0_2; + { + register int y1_2 = y0_2 - y0_3; + if((abs(y1_1) < abs(y1_2)) && + ((y1_1 >= 0) == (y1_2 >= 0))) + y1_1 = y1_2; + } + + /* 2nd differentials @ x-1 & x-2 */ + y2_1 = y0_0 - (y0_1 * 2) + y0_2; + y2_2 = y0_1 - (y0_2 * 2) + y0_3; + + dbprintf(1, "scan: x=%d y=%d y0=%d y1=%d y2=%d", + x, y, y0_1, y1_1, y2_1); + + edge = ZBAR_NONE; + /* 2nd zero-crossing is 1st local min/max - could be edge */ + if((!y2_1 || + ((y2_1 > 0) ? y2_2 < 0 : y2_2 > 0)) && + (calc_thresh(scn) <= abs(y1_1))) + { + /* check for 1st sign change */ + char y1_rev = (scn->y1_sign > 0) ? y1_1 < 0 : y1_1 > 0; + if(y1_rev) + /* intensity change reversal - finalize previous edge */ + edge = process_edge(scn, y1_1); + + if(y1_rev || (abs(scn->y1_sign) < abs(y1_1))) { + int d; + scn->y1_sign = y1_1; + + /* adaptive thresholding */ + /* start at multiple of new min/max */ + scn->y1_thresh = (abs(y1_1) * THRESH_INIT + ROUND) >> ZBAR_FIXED; + dbprintf(1, "\tthr=%d", scn->y1_thresh); + if(scn->y1_thresh < scn->y1_min_thresh) + scn->y1_thresh = scn->y1_min_thresh; + + /* update current edge */ + d = y2_1 - y2_2; + scn->cur_edge = 1 << ZBAR_FIXED; + if(!d) + scn->cur_edge >>= 1; + else if(y2_1) + /* interpolate zero crossing */ + scn->cur_edge -= ((y2_1 << ZBAR_FIXED) + 1) / d; + scn->cur_edge += x << ZBAR_FIXED; + dbprintf(1, "\n"); + } + } + else + dbprintf(1, "\n"); + /* FIXME add fall-thru pass to decoder after heuristic "idle" period + (eg, 6-8 * last width) */ + scn->x = x + 1; + return(edge); +} + +/* undocumented API for drawing cutesy debug graphics */ +void zbar_scanner_get_state (const zbar_scanner_t *scn, + unsigned *x, + unsigned *cur_edge, + unsigned *last_edge, + int *y0, + int *y1, + int *y2, + int *y1_thresh) +{ + register int y0_0 = scn->y0[(scn->x - 1) & 3]; + register int y0_1 = scn->y0[(scn->x - 2) & 3]; + register int y0_2 = scn->y0[(scn->x - 3) & 3]; + zbar_scanner_t *mut_scn; + if(x) *x = scn->x - 1; + if(last_edge) *last_edge = scn->last_edge; + if(y0) *y0 = y0_1; + if(y1) *y1 = y0_1 - y0_2; + if(y2) *y2 = y0_0 - (y0_1 * 2) + y0_2; + /* NB not quite accurate (uses updated x) */ + mut_scn = (zbar_scanner_t*)scn; + if(y1_thresh) *y1_thresh = calc_thresh(mut_scn); + dbprintf(1, "\n"); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void imlib_find_barcodes(list_t *out, image_t *ptr, rectangle_t *roi) +{ + uint8_t *grayscale_image = (ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? ptr->data : fb_alloc(roi->w * roi->h, FB_ALLOC_NO_HINT); + + if (ptr->pixfmt != PIXFORMAT_GRAYSCALE) { + image_t img; + img.w = roi->w; + img.h = roi->h; + img.pixfmt = PIXFORMAT_GRAYSCALE; + img.data = grayscale_image; + img.size = img.w * img.h; + imlib_pixfmt_to(&img, ptr, roi); + // imlib_draw_image(&img, ptr, 0, 0, 1.f, 1.f, roi, -1, 256, NULL, NULL, 0, NULL, NULL); + } + + umm_init_x(fb_avail()); + + zbar_image_scanner_t *scanner = zbar_image_scanner_create(); + zbar_image_scanner_set_config(scanner, 0, ZBAR_CFG_ENABLE, 1); + + zbar_image_t image; + image.format = *((int *) "Y800"); + image.width = (ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? ptr->w : roi->w; + image.height = (ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? ptr->h : roi->h; + image.data = grayscale_image; + image.datalen = ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? ptr->w : roi->w) * ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? ptr->h : roi->h); + image.crop_x = (ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? roi->x : 0; + image.crop_y = (ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? roi->y : 0; + image.crop_w = roi->w; + image.crop_h = roi->h; + image.userdata = 0; + image.seq = 0; + image.syms = 0; + + imlib_list_init(out, sizeof(find_barcodes_list_lnk_data_t)); + + if (zbar_scan_image(scanner, &image) > 0) { + for (const zbar_symbol_t *symbol = (image.syms) ? image.syms->head : NULL; symbol; symbol = zbar_symbol_next(symbol)) { + if (zbar_symbol_get_loc_size(symbol) > 0) { + find_barcodes_list_lnk_data_t lnk_data; + + rectangle_init(&(lnk_data.rect), + zbar_symbol_get_loc_x(symbol, 0) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->x), + zbar_symbol_get_loc_y(symbol, 0) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->y), + (zbar_symbol_get_loc_size(symbol) == 1) ? 1 : 0, + (zbar_symbol_get_loc_size(symbol) == 1) ? 1 : 0); + + for (size_t k = 1, l = zbar_symbol_get_loc_size(symbol); k < l; k++) { + rectangle_t temp; + rectangle_init(&temp, zbar_symbol_get_loc_x(symbol, k) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->x), + zbar_symbol_get_loc_y(symbol, k) + ((ptr->pixfmt == PIXFORMAT_GRAYSCALE) ? 0 : roi->y), 0, 0); + rectangle_united(&(lnk_data.rect), &temp); + } + + // Add corners... + lnk_data.corners[0].x = lnk_data.rect.x; // top-left + lnk_data.corners[0].y = lnk_data.rect.y; // top-left + lnk_data.corners[1].x = lnk_data.rect.x + lnk_data.rect.w; // top-right + lnk_data.corners[1].y = lnk_data.rect.y; // top-right + lnk_data.corners[2].x = lnk_data.rect.x + lnk_data.rect.w; // bottom-right + lnk_data.corners[2].y = lnk_data.rect.y + lnk_data.rect.h; // bottom-right + lnk_data.corners[3].x = lnk_data.rect.x; // bottom-left + lnk_data.corners[3].y = lnk_data.rect.y + lnk_data.rect.h; // bottom-left + + // Payload is already null terminated. + lnk_data.payload_len = zbar_symbol_get_data_length(symbol); + lnk_data.payload = xalloc(zbar_symbol_get_data_length(symbol)); + memcpy(lnk_data.payload, zbar_symbol_get_data(symbol), zbar_symbol_get_data_length(symbol)); + + switch (zbar_symbol_get_type(symbol)) { + case ZBAR_EAN2: lnk_data.type = BARCODE_EAN2; break; + case ZBAR_EAN5: lnk_data.type = BARCODE_EAN5; break; + case ZBAR_EAN8: lnk_data.type = BARCODE_EAN8; break; + case ZBAR_UPCE: lnk_data.type = BARCODE_UPCE; break; + case ZBAR_ISBN10: lnk_data.type = BARCODE_ISBN10; break; + case ZBAR_UPCA: lnk_data.type = BARCODE_UPCA; break; + case ZBAR_EAN13: lnk_data.type = BARCODE_EAN13; break; + case ZBAR_ISBN13: lnk_data.type = BARCODE_ISBN13; break; + case ZBAR_I25: lnk_data.type = BARCODE_I25; break; + case ZBAR_DATABAR: lnk_data.type = BARCODE_DATABAR; break; + case ZBAR_DATABAR_EXP: lnk_data.type = BARCODE_DATABAR_EXP; break; + case ZBAR_CODABAR: lnk_data.type = BARCODE_CODABAR; break; + case ZBAR_CODE39: lnk_data.type = BARCODE_CODE39; break; + case ZBAR_PDF417: lnk_data.type = BARCODE_PDF417; break; + case ZBAR_CODE93: lnk_data.type = BARCODE_CODE93; break; + case ZBAR_CODE128: lnk_data.type = BARCODE_CODE128; break; + default: continue; + } + + switch (zbar_symbol_get_orientation(symbol)) { + case ZBAR_ORIENT_UP: lnk_data.rotation = 0; break; + case ZBAR_ORIENT_RIGHT: lnk_data.rotation = 270; break; + case ZBAR_ORIENT_DOWN: lnk_data.rotation = 180; break; + case ZBAR_ORIENT_LEFT: lnk_data.rotation = 90; break; + default: continue; + } + + lnk_data.quality = zbar_symbol_get_quality(symbol); + + list_push_back(out, &lnk_data); + } + } + } + + for (;;) { // Merge overlapping. + bool merge_occured = false; + + list_t out_temp; + imlib_list_init(&out_temp, sizeof(find_barcodes_list_lnk_data_t)); + + while (list_size(out)) { + find_barcodes_list_lnk_data_t lnk_code; + list_pop_front(out, &lnk_code); + + for (size_t k = 0, l = list_size(out); k < l; k++) { + find_barcodes_list_lnk_data_t tmp_code; + list_pop_front(out, &tmp_code); + + if (rectangle_overlap(&(lnk_code.rect), &(tmp_code.rect)) + && (lnk_code.payload_len == tmp_code.payload_len) + && (!strcmp(lnk_code.payload, tmp_code.payload)) + && (lnk_code.type == tmp_code.type)) { + lnk_code.rotation = ((lnk_code.rect.w * lnk_code.rect.h) > (tmp_code.rect.w * tmp_code.rect.h)) ? lnk_code.rotation : tmp_code.rotation; + lnk_code.quality += tmp_code.quality; // won't overflow + rectangle_united(&(lnk_code.rect), &(tmp_code.rect)); + merge_occured = true; + } else { + list_push_back(out, &tmp_code); + } + } + + list_push_back(&out_temp, &lnk_code); + } + + list_copy(out, &out_temp); + + if (!merge_occured) { + break; + } + } + + if (image.syms) { + image.data = NULL; + zbar_symbol_set_ref(image.syms, -1); + image.syms = NULL; + } + + zbar_image_scanner_destroy(scanner); + fb_free(NULL); // umm_init_x(); + if (ptr->pixfmt != PIXFORMAT_GRAYSCALE) { + fb_free(grayscale_image); // grayscale_image; + } +} + +#pragma GCC diagnostic pop +#endif //IMLIB_ENABLE_BARCODES diff --git a/github_source/minicv2/test/.imlib_find_blobs_test.c b/github_source/minicv2/test/.imlib_find_blobs_test.c new file mode 100644 index 0000000..1e104b7 --- /dev/null +++ b/github_source/minicv2/test/.imlib_find_blobs_test.c @@ -0,0 +1,36 @@ + +#include +#include "imlib.h" + +// assets/image/565.bmp +// assets/image/888.bmp +int main() +{ + image_t img_ts; + imlib_init_all(); + memset(&img_ts, 0, sizeof(image_t)); + bmp_read(&img_ts, "../../assets/image/888.bmp"); + + imlib_draw_line(&img_ts, 10, 10, 10, 100, COLOR_R8_G8_B8_TO_RGB888(0xff, 0x00, 0x00), 4); + imlib_draw_line(&img_ts, 20, 10, 20, 100, COLOR_R8_G8_B8_TO_RGB888(0x00, 0xff, 0x00), 4); + imlib_draw_line(&img_ts, 30, 10, 30, 100, COLOR_R8_G8_B8_TO_RGB888(0x00, 0x00, 0xff), 4); + + imlib_draw_rectangle(&img_ts, 10, 120, 14, 100, COLOR_R8_G8_B8_TO_RGB888(0xff, 0x00, 0x00), 2, 0); + imlib_draw_rectangle(&img_ts, 26, 120, 14, 100, COLOR_R8_G8_B8_TO_RGB888(0x00, 0x00, 0xff), 1, 1); + + imlib_draw_circle(&img_ts, 70, 30, 20, COLOR_R8_G8_B8_TO_RGB888(0x00, 0x00, 0xff), 2, false); + imlib_draw_circle(&img_ts, 70, 100, 20, COLOR_R8_G8_B8_TO_RGB888(0x00, 0x00, 0xff), 2, true); + + imlib_draw_string(&img_ts, 70, 150, "nihao", COLOR_R8_G8_B8_TO_RGB888(0x00, 0x00, 0xff), 3.0, 0, 0, 1, 0, 0, 0, 0, 0, 0); + + + + + + bmp_write_subimg(&img_ts, "./tmp.bmp",NULL); + imlib_deinit_all(); + printf("hello\n"); + imlib_printf(5, "nihao imlib\n"); + return 0; +} + diff --git a/github_source/minicv2/test/.imlib_test.c b/github_source/minicv2/test/.imlib_test.c new file mode 100644 index 0000000..745a8f1 --- /dev/null +++ b/github_source/minicv2/test/.imlib_test.c @@ -0,0 +1,44 @@ + +#include +#include "imlib.h" +// #include "lib2_private.h" // We can't include lib2_private.h for it's compoent2's private include dir + +// /home/nihao/work/lin/minisyd/module/imlib/assets/image/565.bmp +int main() +{ + image_t img_ts; + imlib_init_all(); + imlib_image_init(&img_ts, 240, 240, PIXFORMAT_RGB888, 0, NULL); + bmp_read(&img_ts, "./lin.bmp"); + + imlib_draw_line(&img_ts, 10, 10, 200, 200, COLOR_R8_G8_B8_TO_RGB888(0x00, 0x00, 0xff), 3); + imlib_draw_line(&img_ts, 100, 10, 10, 100, COLOR_R8_G8_B8_TO_RGB888(0xff, 0x00, 0x00), 3); + + + + bmp_write_subimg(&img_ts, "./lin.bmp",NULL); + imlib_deinit_all(); + printf("hello\n"); + imlib_printf(5, "nihao imlib\n"); + return 0; +} + + +// 42 4D +// 38 A3 02 00 +// 00 00 +// 00 00 +// 36 00 00 00 + + +// 28 00 00 00 +// F0 00 00 00 +// F0 00 00 00 +// 01 00 +// 18 00 +// 00 00 00 00 +// 02 A3 02 00 +// 12 0B 00 00 +// 12 0B 00 00 +// 00 00 00 00 +// 00 00 00 00 diff --git a/github_source/minicv2/test/565.bmp b/github_source/minicv2/test/565.bmp new file mode 100644 index 0000000..00f5755 Binary files /dev/null and b/github_source/minicv2/test/565.bmp differ diff --git a/github_source/minicv2/test/888.bmp b/github_source/minicv2/test/888.bmp new file mode 100644 index 0000000..b051919 Binary files /dev/null and b/github_source/minicv2/test/888.bmp differ diff --git a/github_source/minicv2/test/Makefile b/github_source/minicv2/test/Makefile new file mode 100644 index 0000000..0e2b25b --- /dev/null +++ b/github_source/minicv2/test/Makefile @@ -0,0 +1,108 @@ + + +CROSS = +CC = $(CROSS)gcc +CXX = $(CROSS)g++ + +DEBUG = +CFLAGS = $(DEBUG) -Wall -c -Wno-comment -g +LDFLAGS = -lm +MV = mv -f +RM = rm -rf +LN = ln -sf + +TARGET = imlib_base_test + + +TOP_PATH = $(shell pwd) +SRC_PATH = $(TOP_PATH) + +INC_PATH = +INC_PATH += -I$(TOP_PATH)/../include + + + +MOD_PATH = $(TOP_PATH)/.. +MOD_LIB_PATH = $(MOD_PATH)/lib +MOD_INC_PATH = $(MOD_PATH)/include + +SRC_PATH += $(MOD_PATH)/src +DIRS = $(shell find $(SRC_PATH) -maxdepth 3 -type d) +FILES = $(foreach dir, $(DIRS), $(wildcard $(dir)/*.cpp)) + + +########################################################## +# modules +########################################################## +modules = +MODULES_PATH = $(foreach m, $(modules), $(MOD_PATH)/$(m)) + + +########################################################## +# srcs +########################################################## +SRCS_CPP += $(foreach dir, $(DIRS), $(wildcard $(dir)/*.cpp)) +SRCS_CC += $(foreach dir, $(DIRS), $(wildcard $(dir)/*.cc)) +SRCS_C += $(foreach dir, $(DIRS), $(wildcard $(dir)/*.c)) + + +########################################################## +# objs +########################################################## +OBJS_CPP = $(patsubst %.cpp, %.o, $(SRCS_CPP)) +OBJS_CC = $(patsubst %.cc, %.o, $(SRCS_CC)) +OBJS_C = $(patsubst %.c, %.o, $(SRCS_C)) + +########################################################## +# paths +########################################################## +INC_PATH += -I$(MOD_INC_PATH) +INC_PATH += -I$(MOD_PATH) +LIB_PATH += -L$(TOP_PATH)/lib +LIB_PATH += -L$(MOD_LIB_PATH) + +########################################################## +# libs +########################################################## + +########################################################## +# building +########################################################## +all:$(TARGET) + +$(TARGET) : $(OBJS_CPP) $(OBJS_CC) $(OBJS_C) + @ for i in $(MODULES_PATH); \ + do \ + make -C $$i; \ + done + + $(CXX) $^ -o $@ $(LIB_PATH) $(LIBS) $(LDFLAGS) + # $(AR) -r libimlib.a $(OBJS_C) + @ echo Create $(TARGET) ok... + +$(OBJS_CPP):%.o : %.cpp + $(CXX) $(CFLAGS) $< -o $@ $(INC_PATH) + +$(OBJS_CC):%.o : %.cc + $(CC) $(CFLAGS) $< -o $@ $(INC_PATH) + +$(OBJS_C):%.o : %.c + $(CC) $(CFLAGS) $< -o $@ $(INC_PATH) + +.PHONY : clean +clean: + @ $(RM) $(TARGET) $(OBJS_CPP) $(OBJS_CC) $(OBJS_C) + # @ $(RM) libimlib.a + @ for i in $(MODULES_PATH); \ + do \ + make clean -C $$i; \ + done + @echo clean all... + +clean_o: + @ $(RM) $(OBJS_CPP) $(OBJS_CC) $(OBJS_C) + @echo clean all... + +rebuild : + make clean + make \ No newline at end of file diff --git a/github_source/minicv2/test/imlib_base_test.c b/github_source/minicv2/test/imlib_base_test.c new file mode 100644 index 0000000..d86a416 --- /dev/null +++ b/github_source/minicv2/test/imlib_base_test.c @@ -0,0 +1,306 @@ + +#include +#include "imlib.h" + +// assets/image/565.bmp +// assets/image/888.bmp + +// #define _888_e + + + + + +void find_blobs(image_t *img_ts) +{ + image_t *arg_img = img_ts; + + list_t thresholds; + thresholds.size = 100; + list_init(&thresholds, sizeof(color_thresholds_list_lnk_data_t)); + + WORN_PRINT("list info, size:%d",thresholds.size); + + color_thresholds_list_lnk_data_t tmp_ct; + tmp_ct.LMin = 44; + tmp_ct.AMin = 10; + tmp_ct.BMin = -49; + tmp_ct.LMax = 80; + tmp_ct.AMax = 110; + tmp_ct.BMax = 115; + list_push_back(&thresholds, &tmp_ct); + + bool invert = false; + + rectangle_t roi; + roi.x = 0; + roi.y = 0; + roi.w = 240; + roi.h = 240; + + + unsigned int x_stride = 2 ; + unsigned int y_stride = 1; + + unsigned int area_threshold = 10; + unsigned int pixels_threshold = 10; + bool merge = false; + int margin = 0; + unsigned int x_hist_bins_max = 0; + unsigned int y_hist_bins_max = 0; + + list_t out; + fb_alloc_mark(); + + imlib_find_blobs(&out, arg_img, &roi, x_stride, y_stride, &thresholds, invert, + area_threshold, pixels_threshold, merge, margin, + NULL, NULL, NULL, NULL, x_hist_bins_max, y_hist_bins_max); + fb_alloc_free_till_mark(); + + list_free(&thresholds); + + for (size_t i = 0; list_size(&out); i++) { + find_blobs_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + printf("find ones!\n"); +#ifdef _888_e + imlib_draw_rectangle(img_ts, lnk_data.rect.x, lnk_data.rect.y, lnk_data.rect.w, lnk_data.rect.h, COLOR_R8_G8_B8_TO_RGB888(0x00, 0x00, 0x00), 1, 0); +#else + imlib_draw_rectangle(img_ts, lnk_data.rect.x, lnk_data.rect.y, lnk_data.rect.w, lnk_data.rect.h, COLOR_R8_G8_B8_TO_RGB565(0x00, 0x00, 0x00), 1, 0); +#endif + } + + + // for (size_t i = 0; list_size(&out); i++) { + // find_blobs_list_lnk_data_t lnk_data; + // list_pop_front(&out, &lnk_data); + + // lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].x; + // lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*0)/4].y; + // lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].x; + // lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*1)/4].y; + // lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].x; + // lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*2)/4].y; + // lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].x; + // lnk_data.corners[(FIND_BLOBS_CORNERS_RESOLUTION*3)/4].y; + // point_t min_corners[4]; + // point_min_area_rectangle(lnk_data.corners, min_corners, FIND_BLOBS_CORNERS_RESOLUTION); + + // min_corners[0].x min_corners[0].y + // min_corners[1].x min_corners[1].y + // min_corners[2].x min_corners[2].y + // min_corners[3].x min_corners[3].y + // lnk_data.rect.x); + // lnk_data.rect.y); + // lnk_data.rect.w); + // lnk_data.rect.h); + // lnk_data.pixels); + // lnk_data.centroid_x); + // lnk_data.centroid_y); + // lnk_data.rotation); + // lnk_data.code); + // lnk_data.count); + // lnk_data.perimeter); + // lnk_data.roundness); + // lnk_data.x_hist_bins_count, NULL); + // lnk_data.y_hist_bins_count, NULL); + + // for (int i = 0; i < lnk_data.x_hist_bins_count; i++) { + // lnk_data.x_hist_bins[i]; + // } + + // for (int i = 0; i < lnk_data.y_hist_bins_count; i++) { + // lnk_data.y_hist_bins[i]; + // } + + // if (lnk_data.x_hist_bins) xfree(lnk_data.x_hist_bins); + // if (lnk_data.y_hist_bins) xfree(lnk_data.y_hist_bins); + // } +} +void find_lines(image_t *img_ts) +{ + image_t *arg_img = img_ts; + + rectangle_t roi; + roi.x = 0; + roi.y = 0; + roi.w = 240; + roi.h = 240; + + + unsigned int x_stride = 2; + unsigned int y_stride = 1; + uint32_t threshold = 1000; + unsigned int theta_margin = 25; + unsigned int rho_margin = 25; + + list_t out_; + fb_alloc_mark(); + imlib_find_lines(&out_, arg_img, &roi, x_stride, y_stride, threshold, theta_margin, rho_margin); + fb_alloc_free_till_mark(); + + + for (size_t i = 0; list_size(&out_); i++) { + find_lines_list_lnk_data_t lnk_data; + list_pop_front(&out_, &lnk_data); + printf("fine line ones!\n"); + + + +#ifdef _888_e + imlib_draw_line(img_ts, lnk_data.line.x1, lnk_data.line.y1, lnk_data.line.x2, lnk_data.line.y2,COLOR_R8_G8_B8_TO_RGB888(0x00, 0xff, 0x00), 1); +#else + imlib_draw_line(img_ts, lnk_data.line.x1, lnk_data.line.y1, lnk_data.line.x2, lnk_data.line.y2,COLOR_R8_G8_B8_TO_RGB565(0x00, 0xff, 0x00), 1); + +#endif + // py_line_obj_t *o = m_new_obj(py_line_obj_t); + // o->base.type = &py_line_type; + // o->x1 = mp_obj_new_int(lnk_data.line.x1); + // o->y1 = mp_obj_new_int(lnk_data.line.y1); + // o->x2 = mp_obj_new_int(lnk_data.line.x2); + // o->y2 = mp_obj_new_int(lnk_data.line.y2); + // int x_diff = lnk_data.line.x2 - lnk_data.line.x1; + // int y_diff = lnk_data.line.y2 - lnk_data.line.y1; + // o->length = mp_obj_new_int(fast_roundf(fast_sqrtf((x_diff * x_diff) + (y_diff * y_diff)))); + // o->magnitude = mp_obj_new_int(lnk_data.magnitude); + // o->theta = mp_obj_new_int(lnk_data.theta); + // o->rho = mp_obj_new_int(lnk_data.rho); + + // objects_list->items[i] = o; + } +} + + + + + + + + +void find_cricle(image_t *img_ts) +{ + image_t *arg_img = img_ts; + + rectangle_t roi; + roi.x = 0; + roi.y = 0; + roi.w = 240; + roi.h = 240; + + unsigned int x_stride = 2; + + unsigned int y_stride = 1; + + uint32_t threshold = 3000; + unsigned int x_margin = 10; + unsigned int y_margin = 10; + unsigned int r_margin = 10; + unsigned int r_min = 15; + unsigned int r_max = 25; + unsigned int r_step =2; + + list_t out; + fb_alloc_mark(); + imlib_find_circles(&out, arg_img, &roi, x_stride, y_stride, threshold, x_margin, y_margin, r_margin, + r_min, r_max, r_step); + fb_alloc_free_till_mark(); + + + for (size_t i = 0; list_size(&out); i++) { + find_circles_list_lnk_data_t lnk_data; + list_pop_front(&out, &lnk_data); + printf("find_cricles!\n"); +#ifdef _888_e + imlib_draw_circle(img_ts, 70, 30, 20, COLOR_R8_G8_B8_TO_RGB888(0x00, 0xff, 0x00), 1, false); +#else + imlib_draw_circle(img_ts, lnk_data.p.x, lnk_data.p.y, lnk_data.r, COLOR_R8_G8_B8_TO_RGB565(0x00, 0xff, 0x00), 1, false); +#endif + + } + +} + + + + + + + + + +int main() +{ + imlib_init_all(); + image_t *img_ts = imlib_image_create(240, 240, PIXFORMAT_BINARY, 0, NULL, false); + +#ifdef _888_e + bmp_read(img_ts, "./888.bmp"); + imlib_draw_line(img_ts, 10, 10, 10, 100, COLOR_R8_G8_B8_TO_RGB888(0xff, 0x00, 0x00), 4); + imlib_draw_line(img_ts, 20, 10, 20, 100, COLOR_R8_G8_B8_TO_RGB888(0x00, 0xff, 0x00), 4); + imlib_draw_line(img_ts, 30, 10, 30, 100, COLOR_R8_G8_B8_TO_RGB888(0x00, 0x00, 0xff), 4); + imlib_draw_rectangle(img_ts, 10, 120, 14, 100, COLOR_R8_G8_B8_TO_RGB888(0xff, 0x00, 0x00), 2, 0); + imlib_draw_rectangle(img_ts, 26, 120, 14, 100, COLOR_R8_G8_B8_TO_RGB888(0x00, 0x00, 0xff), 1, 1); + imlib_draw_circle(img_ts, 70, 30, 20, COLOR_R8_G8_B8_TO_RGB888(0x00, 0x00, 0xff), 2, false); + imlib_draw_circle(img_ts, 70, 100, 20, COLOR_R8_G8_B8_TO_RGB888(0x00, 0x00, 0xff), 2, true); + imlib_draw_string(img_ts, 70, 150, "nihao", COLOR_R8_G8_B8_TO_RGB888(0x00, 0x00, 0xff), 3.0, 0, 0, 1, 0, 0, 0, 0, 0, 0); +#else + bmp_read(img_ts, "./565.bmp"); + imlib_draw_line(img_ts, 10, 10, 10, 100, COLOR_R8_G8_B8_TO_RGB565(0xff, 0x00, 0x00), 4); + imlib_draw_line(img_ts, 20, 10, 20, 100, COLOR_R8_G8_B8_TO_RGB565(0x00, 0xff, 0x00), 4); + imlib_draw_line(img_ts, 30, 10, 30, 100, COLOR_R8_G8_B8_TO_RGB565(0x00, 0x00, 0xff), 4); + imlib_draw_rectangle(img_ts, 10, 120, 14, 100, COLOR_R8_G8_B8_TO_RGB565(0xff, 0x00, 0x00), 2, 0); + imlib_draw_rectangle(img_ts, 26, 120, 14, 100, COLOR_R8_G8_B8_TO_RGB565(0x00, 0x00, 0xff), 1, 1); + imlib_draw_circle(img_ts, 70, 30, 20, COLOR_R8_G8_B8_TO_RGB565(0x00, 0x00, 0xff), 2, false); + imlib_draw_circle(img_ts, 70, 100, 20, COLOR_R8_G8_B8_TO_RGB565(0x00, 0x00, 0xff), 2, true); + imlib_draw_string(img_ts, 70, 150, "nihao", COLOR_R8_G8_B8_TO_RGB565(0x00, 0x00, 0xff), 3.0, 0, 0, 1, 0, 0, 0, 0, 0, 0); +#endif +// (44, 80, 10, 110, -49, 115) + find_blobs(img_ts); + + // find_lines(img_ts); + + // find_cricle(img_ts); +#ifdef _888_e + image_t *img_big = imlib_image_create(320, 320, PIXFORMAT_RGB888, 0, NULL, true); +#else + image_t *img_big = imlib_image_create(320, 320, PIXFORMAT_RGB565, 0, NULL, true); +#endif + imlib_image_resize(img_big, img_ts, IMAGE_HINT_BILINEAR); + + bmp_write_subimg(img_big, "./tmp_img_big.bmp",NULL); + bmp_write_subimg(img_ts, "./tmp.bmp",NULL); + imlib_printf_image_info(img_ts); + jpeg_write(img_ts, "./tmp.jpeg", 95); + + + + + + image_t *imgjpeg = imlib_image_create(0, 0, PIXFORMAT_RGB565, 0, NULL, 0); + jpeg_read(imgjpeg, "./tmp.jpeg"); + + image_t *imgrgbb = imlib_image_create(imgjpeg->w, imgjpeg->h, PIXFORMAT_RGB888, 0, NULL, true); + + + jpeg_decompress(imgrgbb, imgjpeg); + + imlib_printf_image_info(imgrgbb); + + imlib_draw_line(img_ts, 10, 10, 10, 100, COLOR_R8_G8_B8_TO_RGB565(0x00, 0xff, 0x00), 4); + jpeg_write(img_ts, "./haha.jpeg", 95); + + imlib_image_destroy(&imgjpeg); + + imlib_image_destroy(&imgrgbb); + + + + + imlib_image_destroy(&img_big); + imlib_image_destroy(&img_ts); + imlib_deinit_all(); + printf("hello\n"); + imlib_printf(5, "nihao imlib\n"); + return 0; +} + diff --git a/github_source/minicv2/test/tmp_img_big.bmp b/github_source/minicv2/test/tmp_img_big.bmp new file mode 100644 index 0000000..c8c2b0c Binary files /dev/null and b/github_source/minicv2/test/tmp_img_big.bmp differ